##// END OF EJS Templates
fix(2fa): fixed redirect loop in workflow when password reset was done.
super-admin -
r5370:6dc425cb default
parent child Browse files
Show More
@@ -1,986 +1,987 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # 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
4 # 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
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # 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,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # 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
13 # 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/>.
14 #
14 #
15 # 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
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import time
19 import time
20 import logging
20 import logging
21 import operator
21 import operator
22
22
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
24
24
25 from rhodecode.lib import helpers as h, diffs, rc_cache
25 from rhodecode.lib import helpers as h, diffs, rc_cache
26 from rhodecode.lib.str_utils import safe_str
26 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.utils import repo_name_slug
27 from rhodecode.lib.utils import repo_name_slug
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict,
29 StrictAttributeDict,
30 str2bool,
30 str2bool,
31 safe_int,
31 safe_int,
32 datetime_to_time,
32 datetime_to_time,
33 )
33 )
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
36 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
37 from rhodecode.model import repo
37 from rhodecode.model import repo
38 from rhodecode.model import repo_group
38 from rhodecode.model import repo_group
39 from rhodecode.model import user_group
39 from rhodecode.model import user_group
40 from rhodecode.model import user
40 from rhodecode.model import user
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
43 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
44 from rhodecode.model.repo import ReadmeFinder
44 from rhodecode.model.repo import ReadmeFinder
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 ADMIN_PREFIX: str = "/_admin"
49 ADMIN_PREFIX: str = "/_admin"
50 STATIC_FILE_PREFIX: str = "/_static"
50 STATIC_FILE_PREFIX: str = "/_static"
51
51
52 URL_NAME_REQUIREMENTS = {
52 URL_NAME_REQUIREMENTS = {
53 # group name can have a slash in them, but they must not end with a slash
53 # group name can have a slash in them, but they must not end with a slash
54 "group_name": r".*?[^/]",
54 "group_name": r".*?[^/]",
55 "repo_group_name": r".*?[^/]",
55 "repo_group_name": r".*?[^/]",
56 # repo names can have a slash in them, but they must not end with a slash
56 # repo names can have a slash in them, but they must not end with a slash
57 "repo_name": r".*?[^/]",
57 "repo_name": r".*?[^/]",
58 # file path eats up everything at the end
58 # file path eats up everything at the end
59 "f_path": r".*",
59 "f_path": r".*",
60 # reference types
60 # reference types
61 "source_ref_type": r"(branch|book|tag|rev|\%\(source_ref_type\)s)",
61 "source_ref_type": r"(branch|book|tag|rev|\%\(source_ref_type\)s)",
62 "target_ref_type": r"(branch|book|tag|rev|\%\(target_ref_type\)s)",
62 "target_ref_type": r"(branch|book|tag|rev|\%\(target_ref_type\)s)",
63 }
63 }
64
64
65
65
66 def add_route_with_slash(config, name, pattern, **kw):
66 def add_route_with_slash(config, name, pattern, **kw):
67 config.add_route(name, pattern, **kw)
67 config.add_route(name, pattern, **kw)
68 if not pattern.endswith("/"):
68 if not pattern.endswith("/"):
69 config.add_route(name + "_slash", pattern + "/", **kw)
69 config.add_route(name + "_slash", pattern + "/", **kw)
70
70
71
71
72 def add_route_requirements(route_path, requirements=None):
72 def add_route_requirements(route_path, requirements=None):
73 """
73 """
74 Adds regex requirements to pyramid routes using a mapping dict
74 Adds regex requirements to pyramid routes using a mapping dict
75 e.g::
75 e.g::
76 add_route_requirements('{repo_name}/settings')
76 add_route_requirements('{repo_name}/settings')
77 """
77 """
78 requirements = requirements or URL_NAME_REQUIREMENTS
78 requirements = requirements or URL_NAME_REQUIREMENTS
79 for key, regex in list(requirements.items()):
79 for key, regex in list(requirements.items()):
80 route_path = route_path.replace("{%s}" % key, "{%s:%s}" % (key, regex))
80 route_path = route_path.replace("{%s}" % key, "{%s:%s}" % (key, regex))
81 return route_path
81 return route_path
82
82
83
83
84 def get_format_ref_id(repo):
84 def get_format_ref_id(repo):
85 """Returns a `repo` specific reference formatter function"""
85 """Returns a `repo` specific reference formatter function"""
86 if h.is_svn(repo):
86 if h.is_svn(repo):
87 return _format_ref_id_svn
87 return _format_ref_id_svn
88 else:
88 else:
89 return _format_ref_id
89 return _format_ref_id
90
90
91
91
92 def _format_ref_id(name, raw_id):
92 def _format_ref_id(name, raw_id):
93 """Default formatting of a given reference `name`"""
93 """Default formatting of a given reference `name`"""
94 return name
94 return name
95
95
96
96
97 def _format_ref_id_svn(name, raw_id):
97 def _format_ref_id_svn(name, raw_id):
98 """Special way of formatting a reference for Subversion including path"""
98 """Special way of formatting a reference for Subversion including path"""
99 return f"{name}@{raw_id}"
99 return f"{name}@{raw_id}"
100
100
101
101
102 class TemplateArgs(StrictAttributeDict):
102 class TemplateArgs(StrictAttributeDict):
103 pass
103 pass
104
104
105
105
106 class BaseAppView(object):
106 class BaseAppView(object):
107 DONT_CHECKOUT_VIEWS = ["channelstream_connect", "ops_ping"]
107 DONT_CHECKOUT_VIEWS = ["channelstream_connect", "ops_ping"]
108 EXTRA_VIEWS_TO_IGNORE = ['login', 'register', 'logout']
108 EXTRA_VIEWS_TO_IGNORE = ['login', 'register', 'logout']
109 SETUP_2FA_VIEW = 'setup_2fa'
109 SETUP_2FA_VIEW = 'setup_2fa'
110 VERIFY_2FA_VIEW = 'check_2fa'
110 VERIFY_2FA_VIEW = 'check_2fa'
111
111
112 def __init__(self, context, request):
112 def __init__(self, context, request):
113 self.request = request
113 self.request = request
114 self.context = context
114 self.context = context
115 self.session = request.session
115 self.session = request.session
116 if not hasattr(request, "user"):
116 if not hasattr(request, "user"):
117 # NOTE(marcink): edge case, we ended up in matched route
117 # NOTE(marcink): edge case, we ended up in matched route
118 # but probably of web-app context, e.g API CALL/VCS CALL
118 # but probably of web-app context, e.g API CALL/VCS CALL
119 if hasattr(request, "vcs_call") or hasattr(request, "rpc_method"):
119 if hasattr(request, "vcs_call") or hasattr(request, "rpc_method"):
120 log.warning("Unable to process request `%s` in this scope", request)
120 log.warning("Unable to process request `%s` in this scope", request)
121 raise HTTPBadRequest()
121 raise HTTPBadRequest()
122
122
123 self._rhodecode_user = request.user # auth user
123 self._rhodecode_user = request.user # auth user
124 self._rhodecode_db_user = self._rhodecode_user.get_instance()
124 self._rhodecode_db_user = self._rhodecode_user.get_instance()
125 self.user_data = self._rhodecode_db_user.user_data if self._rhodecode_db_user else {}
125 self.user_data = self._rhodecode_db_user.user_data if self._rhodecode_db_user else {}
126 self._maybe_needs_password_change(
126 self._maybe_needs_password_change(
127 request.matched_route.name, self._rhodecode_db_user
127 request.matched_route.name, self._rhodecode_db_user
128 )
128 )
129 self._maybe_needs_2fa_configuration(
129 self._maybe_needs_2fa_configuration(
130 request.matched_route.name, self._rhodecode_db_user
130 request.matched_route.name, self._rhodecode_db_user
131 )
131 )
132 self._maybe_needs_2fa_check(
132 self._maybe_needs_2fa_check(
133 request.matched_route.name, self._rhodecode_db_user
133 request.matched_route.name, self._rhodecode_db_user
134 )
134 )
135
135
136 def _maybe_needs_password_change(self, view_name, user_obj):
136 def _maybe_needs_password_change(self, view_name, user_obj):
137 if view_name in self.DONT_CHECKOUT_VIEWS:
137 if view_name in self.DONT_CHECKOUT_VIEWS:
138 return
138 return
139
139
140 log.debug(
140 log.debug(
141 "Checking if user %s needs password change on view %s", user_obj, view_name
141 "Checking if user %s needs password change on view %s", user_obj, view_name
142 )
142 )
143
143
144 skip_user_views = [
144 skip_user_views = [
145 "logout",
145 "logout",
146 "login",
146 "login",
147 "check_2fa",
147 "my_account_password",
148 "my_account_password",
148 "my_account_password_update",
149 "my_account_password_update",
149 ]
150 ]
150
151
151 if not user_obj:
152 if not user_obj:
152 return
153 return
153
154
154 if user_obj.username == User.DEFAULT_USER:
155 if user_obj.username == User.DEFAULT_USER:
155 return
156 return
156
157
157 now = time.time()
158 now = time.time()
158 should_change = self.user_data.get("force_password_change")
159 should_change = self.user_data.get("force_password_change")
159 change_after = safe_int(should_change) or 0
160 change_after = safe_int(should_change) or 0
160 if should_change and now > change_after:
161 if should_change and now > change_after:
161 log.debug("User %s requires password change", user_obj)
162 log.debug("User %s requires password change", user_obj)
162 h.flash(
163 h.flash(
163 "You are required to change your password",
164 "You are required to change your password",
164 "warning",
165 "warning",
165 ignore_duplicate=True,
166 ignore_duplicate=True,
166 )
167 )
167
168
168 if view_name not in skip_user_views:
169 if view_name not in skip_user_views:
169 raise HTTPFound(self.request.route_path("my_account_password"))
170 raise HTTPFound(self.request.route_path("my_account_password"))
170
171
171 def _maybe_needs_2fa_configuration(self, view_name, user_obj):
172 def _maybe_needs_2fa_configuration(self, view_name, user_obj):
172 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
173 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
173 return
174 return
174
175
175 if not user_obj:
176 if not user_obj:
176 return
177 return
177
178
178 if user_obj.has_forced_2fa and user_obj.extern_type != 'rhodecode':
179 if user_obj.has_forced_2fa and user_obj.extern_type != 'rhodecode':
179 return
180 return
180
181
181 if user_obj.needs_2fa_configure and view_name != self.SETUP_2FA_VIEW:
182 if user_obj.needs_2fa_configure and view_name != self.SETUP_2FA_VIEW:
182 h.flash(
183 h.flash(
183 "You are required to configure 2FA",
184 "You are required to configure 2FA",
184 "warning",
185 "warning",
185 ignore_duplicate=False,
186 ignore_duplicate=False,
186 )
187 )
187 raise HTTPFound(self.request.route_path(self.SETUP_2FA_VIEW))
188 raise HTTPFound(self.request.route_path(self.SETUP_2FA_VIEW))
188
189
189 def _maybe_needs_2fa_check(self, view_name, user_obj):
190 def _maybe_needs_2fa_check(self, view_name, user_obj):
190 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
191 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
191 return
192 return
192
193
193 if not user_obj:
194 if not user_obj:
194 return
195 return
195
196
196 if user_obj.has_check_2fa_flag and view_name != self.VERIFY_2FA_VIEW:
197 if user_obj.has_check_2fa_flag and view_name != self.VERIFY_2FA_VIEW:
197 raise HTTPFound(self.request.route_path(self.VERIFY_2FA_VIEW))
198 raise HTTPFound(self.request.route_path(self.VERIFY_2FA_VIEW))
198
199
199 def _log_creation_exception(self, e, repo_name):
200 def _log_creation_exception(self, e, repo_name):
200 _ = self.request.translate
201 _ = self.request.translate
201 reason = None
202 reason = None
202 if len(e.args) == 2:
203 if len(e.args) == 2:
203 reason = e.args[1]
204 reason = e.args[1]
204
205
205 if reason == "INVALID_CERTIFICATE":
206 if reason == "INVALID_CERTIFICATE":
206 log.exception("Exception creating a repository: invalid certificate")
207 log.exception("Exception creating a repository: invalid certificate")
207 msg = _("Error creating repository %s: invalid certificate") % repo_name
208 msg = _("Error creating repository %s: invalid certificate") % repo_name
208 else:
209 else:
209 log.exception("Exception creating a repository")
210 log.exception("Exception creating a repository")
210 msg = _("Error creating repository %s") % repo_name
211 msg = _("Error creating repository %s") % repo_name
211 return msg
212 return msg
212
213
213 def _get_local_tmpl_context(self, include_app_defaults=True):
214 def _get_local_tmpl_context(self, include_app_defaults=True):
214 c = TemplateArgs()
215 c = TemplateArgs()
215 c.auth_user = self.request.user
216 c.auth_user = self.request.user
216 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
217 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
217 c.rhodecode_user = self.request.user
218 c.rhodecode_user = self.request.user
218
219
219 if include_app_defaults:
220 if include_app_defaults:
220 from rhodecode.lib.base import attach_context_attributes
221 from rhodecode.lib.base import attach_context_attributes
221
222
222 attach_context_attributes(c, self.request, self.request.user.user_id)
223 attach_context_attributes(c, self.request, self.request.user.user_id)
223
224
224 c.is_super_admin = c.auth_user.is_admin
225 c.is_super_admin = c.auth_user.is_admin
225
226
226 c.can_create_repo = c.is_super_admin
227 c.can_create_repo = c.is_super_admin
227 c.can_create_repo_group = c.is_super_admin
228 c.can_create_repo_group = c.is_super_admin
228 c.can_create_user_group = c.is_super_admin
229 c.can_create_user_group = c.is_super_admin
229
230
230 c.is_delegated_admin = False
231 c.is_delegated_admin = False
231
232
232 if not c.auth_user.is_default and not c.is_super_admin:
233 if not c.auth_user.is_default and not c.is_super_admin:
233 c.can_create_repo = h.HasPermissionAny("hg.create.repository")(
234 c.can_create_repo = h.HasPermissionAny("hg.create.repository")(
234 user=self.request.user
235 user=self.request.user
235 )
236 )
236 repositories = c.auth_user.repositories_admin or c.can_create_repo
237 repositories = c.auth_user.repositories_admin or c.can_create_repo
237
238
238 c.can_create_repo_group = h.HasPermissionAny("hg.repogroup.create.true")(
239 c.can_create_repo_group = h.HasPermissionAny("hg.repogroup.create.true")(
239 user=self.request.user
240 user=self.request.user
240 )
241 )
241 repository_groups = (
242 repository_groups = (
242 c.auth_user.repository_groups_admin or c.can_create_repo_group
243 c.auth_user.repository_groups_admin or c.can_create_repo_group
243 )
244 )
244
245
245 c.can_create_user_group = h.HasPermissionAny("hg.usergroup.create.true")(
246 c.can_create_user_group = h.HasPermissionAny("hg.usergroup.create.true")(
246 user=self.request.user
247 user=self.request.user
247 )
248 )
248 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
249 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
249 # delegated admin can create, or manage some objects
250 # delegated admin can create, or manage some objects
250 c.is_delegated_admin = repositories or repository_groups or user_groups
251 c.is_delegated_admin = repositories or repository_groups or user_groups
251 return c
252 return c
252
253
253 def _get_template_context(self, tmpl_args, **kwargs):
254 def _get_template_context(self, tmpl_args, **kwargs):
254 local_tmpl_args = {"defaults": {}, "errors": {}, "c": tmpl_args}
255 local_tmpl_args = {"defaults": {}, "errors": {}, "c": tmpl_args}
255 local_tmpl_args.update(kwargs)
256 local_tmpl_args.update(kwargs)
256 return local_tmpl_args
257 return local_tmpl_args
257
258
258 def load_default_context(self):
259 def load_default_context(self):
259 """
260 """
260 example:
261 example:
261
262
262 def load_default_context(self):
263 def load_default_context(self):
263 c = self._get_local_tmpl_context()
264 c = self._get_local_tmpl_context()
264 c.custom_var = 'foobar'
265 c.custom_var = 'foobar'
265
266
266 return c
267 return c
267 """
268 """
268 raise NotImplementedError("Needs implementation in view class")
269 raise NotImplementedError("Needs implementation in view class")
269
270
270
271
271 class RepoAppView(BaseAppView):
272 class RepoAppView(BaseAppView):
272 def __init__(self, context, request):
273 def __init__(self, context, request):
273 super().__init__(context, request)
274 super().__init__(context, request)
274 self.db_repo = request.db_repo
275 self.db_repo = request.db_repo
275 self.db_repo_name = self.db_repo.repo_name
276 self.db_repo_name = self.db_repo.repo_name
276 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
277 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
277 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
278 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
278 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
279 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
279
280
280 def _handle_missing_requirements(self, error):
281 def _handle_missing_requirements(self, error):
281 log.error(
282 log.error(
282 "Requirements are missing for repository %s: %s",
283 "Requirements are missing for repository %s: %s",
283 self.db_repo_name,
284 self.db_repo_name,
284 safe_str(error),
285 safe_str(error),
285 )
286 )
286
287
287 def _prepare_and_set_clone_url(self, c):
288 def _prepare_and_set_clone_url(self, c):
288 username = ""
289 username = ""
289 if self._rhodecode_user.username != User.DEFAULT_USER:
290 if self._rhodecode_user.username != User.DEFAULT_USER:
290 username = self._rhodecode_user.username
291 username = self._rhodecode_user.username
291
292
292 _def_clone_uri = c.clone_uri_tmpl
293 _def_clone_uri = c.clone_uri_tmpl
293 _def_clone_uri_id = c.clone_uri_id_tmpl
294 _def_clone_uri_id = c.clone_uri_id_tmpl
294 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
295 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
295
296
296 c.clone_repo_url = self.db_repo.clone_url(
297 c.clone_repo_url = self.db_repo.clone_url(
297 user=username, uri_tmpl=_def_clone_uri
298 user=username, uri_tmpl=_def_clone_uri
298 )
299 )
299 c.clone_repo_url_id = self.db_repo.clone_url(
300 c.clone_repo_url_id = self.db_repo.clone_url(
300 user=username, uri_tmpl=_def_clone_uri_id
301 user=username, uri_tmpl=_def_clone_uri_id
301 )
302 )
302 c.clone_repo_url_ssh = self.db_repo.clone_url(
303 c.clone_repo_url_ssh = self.db_repo.clone_url(
303 uri_tmpl=_def_clone_uri_ssh, ssh=True
304 uri_tmpl=_def_clone_uri_ssh, ssh=True
304 )
305 )
305
306
306 def _get_local_tmpl_context(self, include_app_defaults=True):
307 def _get_local_tmpl_context(self, include_app_defaults=True):
307 _ = self.request.translate
308 _ = self.request.translate
308 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
309 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
309
310
310 # register common vars for this type of view
311 # register common vars for this type of view
311 c.rhodecode_db_repo = self.db_repo
312 c.rhodecode_db_repo = self.db_repo
312 c.repo_name = self.db_repo_name
313 c.repo_name = self.db_repo_name
313 c.repository_pull_requests = self.db_repo_pull_requests
314 c.repository_pull_requests = self.db_repo_pull_requests
314 c.repository_artifacts = self.db_repo_artifacts
315 c.repository_artifacts = self.db_repo_artifacts
315 c.repository_is_user_following = ScmModel().is_following_repo(
316 c.repository_is_user_following = ScmModel().is_following_repo(
316 self.db_repo_name, self._rhodecode_user.user_id
317 self.db_repo_name, self._rhodecode_user.user_id
317 )
318 )
318 self.path_filter = PathFilter(None)
319 self.path_filter = PathFilter(None)
319
320
320 c.repository_requirements_missing = {}
321 c.repository_requirements_missing = {}
321 try:
322 try:
322 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
323 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
323 # NOTE(marcink):
324 # NOTE(marcink):
324 # comparison to None since if it's an object __bool__ is expensive to
325 # comparison to None since if it's an object __bool__ is expensive to
325 # calculate
326 # calculate
326 if self.rhodecode_vcs_repo is not None:
327 if self.rhodecode_vcs_repo is not None:
327 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
328 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
328 c.auth_user.username
329 c.auth_user.username
329 )
330 )
330 self.path_filter = PathFilter(path_perms)
331 self.path_filter = PathFilter(path_perms)
331 except RepositoryRequirementError as e:
332 except RepositoryRequirementError as e:
332 c.repository_requirements_missing = {"error": str(e)}
333 c.repository_requirements_missing = {"error": str(e)}
333 self._handle_missing_requirements(e)
334 self._handle_missing_requirements(e)
334 self.rhodecode_vcs_repo = None
335 self.rhodecode_vcs_repo = None
335
336
336 c.path_filter = self.path_filter # used by atom_feed_entry.mako
337 c.path_filter = self.path_filter # used by atom_feed_entry.mako
337
338
338 if self.rhodecode_vcs_repo is None:
339 if self.rhodecode_vcs_repo is None:
339 # unable to fetch this repo as vcs instance, report back to user
340 # unable to fetch this repo as vcs instance, report back to user
340 log.debug(
341 log.debug(
341 "Repository was not found on filesystem, check if it exists or is not damaged"
342 "Repository was not found on filesystem, check if it exists or is not damaged"
342 )
343 )
343 h.flash(
344 h.flash(
344 _(
345 _(
345 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
346 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
346 "Please check if it exist, or is not damaged."
347 "Please check if it exist, or is not damaged."
347 )
348 )
348 % {"repo_name": c.repo_name},
349 % {"repo_name": c.repo_name},
349 category="error",
350 category="error",
350 ignore_duplicate=True,
351 ignore_duplicate=True,
351 )
352 )
352 if c.repository_requirements_missing:
353 if c.repository_requirements_missing:
353 route = self.request.matched_route.name
354 route = self.request.matched_route.name
354 if route.startswith(("edit_repo", "repo_summary")):
355 if route.startswith(("edit_repo", "repo_summary")):
355 # allow summary and edit repo on missing requirements
356 # allow summary and edit repo on missing requirements
356 return c
357 return c
357
358
358 raise HTTPFound(
359 raise HTTPFound(
359 h.route_path("repo_summary", repo_name=self.db_repo_name)
360 h.route_path("repo_summary", repo_name=self.db_repo_name)
360 )
361 )
361
362
362 else: # redirect if we don't show missing requirements
363 else: # redirect if we don't show missing requirements
363 raise HTTPFound(h.route_path("home"))
364 raise HTTPFound(h.route_path("home"))
364
365
365 c.has_origin_repo_read_perm = False
366 c.has_origin_repo_read_perm = False
366 if self.db_repo.fork:
367 if self.db_repo.fork:
367 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
368 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
368 "repository.write", "repository.read", "repository.admin"
369 "repository.write", "repository.read", "repository.admin"
369 )(self.db_repo.fork.repo_name, "summary fork link")
370 )(self.db_repo.fork.repo_name, "summary fork link")
370
371
371 return c
372 return c
372
373
373 def _get_f_path_unchecked(self, matchdict, default=None):
374 def _get_f_path_unchecked(self, matchdict, default=None):
374 """
375 """
375 Should only be used by redirects, everything else should call _get_f_path
376 Should only be used by redirects, everything else should call _get_f_path
376 """
377 """
377 f_path = matchdict.get("f_path")
378 f_path = matchdict.get("f_path")
378 if f_path:
379 if f_path:
379 # fix for multiple initial slashes that causes errors for GIT
380 # fix for multiple initial slashes that causes errors for GIT
380 return f_path.lstrip("/")
381 return f_path.lstrip("/")
381
382
382 return default
383 return default
383
384
384 def _get_f_path(self, matchdict, default=None):
385 def _get_f_path(self, matchdict, default=None):
385 f_path_match = self._get_f_path_unchecked(matchdict, default)
386 f_path_match = self._get_f_path_unchecked(matchdict, default)
386 return self.path_filter.assert_path_permissions(f_path_match)
387 return self.path_filter.assert_path_permissions(f_path_match)
387
388
388 def _get_general_setting(self, target_repo, settings_key, default=False):
389 def _get_general_setting(self, target_repo, settings_key, default=False):
389 settings_model = VcsSettingsModel(repo=target_repo)
390 settings_model = VcsSettingsModel(repo=target_repo)
390 settings = settings_model.get_general_settings()
391 settings = settings_model.get_general_settings()
391 return settings.get(settings_key, default)
392 return settings.get(settings_key, default)
392
393
393 def _get_repo_setting(self, target_repo, settings_key, default=False):
394 def _get_repo_setting(self, target_repo, settings_key, default=False):
394 settings_model = VcsSettingsModel(repo=target_repo)
395 settings_model = VcsSettingsModel(repo=target_repo)
395 settings = settings_model.get_repo_settings_inherited()
396 settings = settings_model.get_repo_settings_inherited()
396 return settings.get(settings_key, default)
397 return settings.get(settings_key, default)
397
398
398 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/"):
399 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/"):
399 log.debug("Looking for README file at path %s", path)
400 log.debug("Looking for README file at path %s", path)
400 if commit_id:
401 if commit_id:
401 landing_commit_id = commit_id
402 landing_commit_id = commit_id
402 else:
403 else:
403 landing_commit = db_repo.get_landing_commit()
404 landing_commit = db_repo.get_landing_commit()
404 if isinstance(landing_commit, EmptyCommit):
405 if isinstance(landing_commit, EmptyCommit):
405 return None, None
406 return None, None
406 landing_commit_id = landing_commit.raw_id
407 landing_commit_id = landing_commit.raw_id
407
408
408 cache_namespace_uid = f"repo.{db_repo.repo_id}"
409 cache_namespace_uid = f"repo.{db_repo.repo_id}"
409 region = rc_cache.get_or_create_region(
410 region = rc_cache.get_or_create_region(
410 "cache_repo", cache_namespace_uid, use_async_runner=False
411 "cache_repo", cache_namespace_uid, use_async_runner=False
411 )
412 )
412 start = time.time()
413 start = time.time()
413
414
414 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
415 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
415 def generate_repo_readme(
416 def generate_repo_readme(
416 repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
417 repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
417 ):
418 ):
418 readme_data = None
419 readme_data = None
419 readme_filename = None
420 readme_filename = None
420
421
421 commit = db_repo.get_commit(_commit_id)
422 commit = db_repo.get_commit(_commit_id)
422 log.debug("Searching for a README file at commit %s.", _commit_id)
423 log.debug("Searching for a README file at commit %s.", _commit_id)
423 readme_node = ReadmeFinder(_renderer_type).search(
424 readme_node = ReadmeFinder(_renderer_type).search(
424 commit, path=_readme_search_path
425 commit, path=_readme_search_path
425 )
426 )
426
427
427 if readme_node:
428 if readme_node:
428 log.debug("Found README node: %s", readme_node)
429 log.debug("Found README node: %s", readme_node)
429
430
430 relative_urls = {
431 relative_urls = {
431 "raw": h.route_path(
432 "raw": h.route_path(
432 "repo_file_raw",
433 "repo_file_raw",
433 repo_name=_repo_name,
434 repo_name=_repo_name,
434 commit_id=commit.raw_id,
435 commit_id=commit.raw_id,
435 f_path=readme_node.path,
436 f_path=readme_node.path,
436 ),
437 ),
437 "standard": h.route_path(
438 "standard": h.route_path(
438 "repo_files",
439 "repo_files",
439 repo_name=_repo_name,
440 repo_name=_repo_name,
440 commit_id=commit.raw_id,
441 commit_id=commit.raw_id,
441 f_path=readme_node.path,
442 f_path=readme_node.path,
442 ),
443 ),
443 }
444 }
444
445
445 readme_data = self._render_readme_or_none(
446 readme_data = self._render_readme_or_none(
446 commit, readme_node, relative_urls
447 commit, readme_node, relative_urls
447 )
448 )
448 readme_filename = readme_node.str_path
449 readme_filename = readme_node.str_path
449
450
450 return readme_data, readme_filename
451 return readme_data, readme_filename
451
452
452 readme_data, readme_filename = generate_repo_readme(
453 readme_data, readme_filename = generate_repo_readme(
453 db_repo.repo_id,
454 db_repo.repo_id,
454 landing_commit_id,
455 landing_commit_id,
455 db_repo.repo_name,
456 db_repo.repo_name,
456 path,
457 path,
457 renderer_type,
458 renderer_type,
458 )
459 )
459
460
460 compute_time = time.time() - start
461 compute_time = time.time() - start
461 log.debug(
462 log.debug(
462 "Repo README for path %s generated and computed in %.4fs",
463 "Repo README for path %s generated and computed in %.4fs",
463 path,
464 path,
464 compute_time,
465 compute_time,
465 )
466 )
466 return readme_data, readme_filename
467 return readme_data, readme_filename
467
468
468 def _render_readme_or_none(self, commit, readme_node, relative_urls):
469 def _render_readme_or_none(self, commit, readme_node, relative_urls):
469 log.debug("Found README file `%s` rendering...", readme_node.path)
470 log.debug("Found README file `%s` rendering...", readme_node.path)
470 renderer = MarkupRenderer()
471 renderer = MarkupRenderer()
471 try:
472 try:
472 html_source = renderer.render(
473 html_source = renderer.render(
473 readme_node.str_content, filename=readme_node.path
474 readme_node.str_content, filename=readme_node.path
474 )
475 )
475 if relative_urls:
476 if relative_urls:
476 return relative_links(html_source, relative_urls)
477 return relative_links(html_source, relative_urls)
477 return html_source
478 return html_source
478 except Exception:
479 except Exception:
479 log.exception("Exception while trying to render the README")
480 log.exception("Exception while trying to render the README")
480
481
481 def get_recache_flag(self):
482 def get_recache_flag(self):
482 for flag_name in ["force_recache", "force-recache", "no-cache"]:
483 for flag_name in ["force_recache", "force-recache", "no-cache"]:
483 flag_val = self.request.GET.get(flag_name)
484 flag_val = self.request.GET.get(flag_name)
484 if str2bool(flag_val):
485 if str2bool(flag_val):
485 return True
486 return True
486 return False
487 return False
487
488
488 def get_commit_preload_attrs(cls):
489 def get_commit_preload_attrs(cls):
489 pre_load = [
490 pre_load = [
490 "author",
491 "author",
491 "branch",
492 "branch",
492 "date",
493 "date",
493 "message",
494 "message",
494 "parents",
495 "parents",
495 "obsolete",
496 "obsolete",
496 "phase",
497 "phase",
497 "hidden",
498 "hidden",
498 ]
499 ]
499 return pre_load
500 return pre_load
500
501
501
502
502 class PathFilter(object):
503 class PathFilter(object):
503 # Expects and instance of BasePathPermissionChecker or None
504 # Expects and instance of BasePathPermissionChecker or None
504 def __init__(self, permission_checker):
505 def __init__(self, permission_checker):
505 self.permission_checker = permission_checker
506 self.permission_checker = permission_checker
506
507
507 def assert_path_permissions(self, path):
508 def assert_path_permissions(self, path):
508 if self.path_access_allowed(path):
509 if self.path_access_allowed(path):
509 return path
510 return path
510 raise HTTPForbidden()
511 raise HTTPForbidden()
511
512
512 def path_access_allowed(self, path):
513 def path_access_allowed(self, path):
513 log.debug("Checking ACL permissions for PathFilter for `%s`", path)
514 log.debug("Checking ACL permissions for PathFilter for `%s`", path)
514 if self.permission_checker:
515 if self.permission_checker:
515 has_access = path and self.permission_checker.has_access(path)
516 has_access = path and self.permission_checker.has_access(path)
516 log.debug(
517 log.debug(
517 "ACL Permissions checker enabled, ACL Check has_access: %s", has_access
518 "ACL Permissions checker enabled, ACL Check has_access: %s", has_access
518 )
519 )
519 return has_access
520 return has_access
520
521
521 log.debug("ACL permissions checker not enabled, skipping...")
522 log.debug("ACL permissions checker not enabled, skipping...")
522 return True
523 return True
523
524
524 def filter_patchset(self, patchset):
525 def filter_patchset(self, patchset):
525 if not self.permission_checker or not patchset:
526 if not self.permission_checker or not patchset:
526 return patchset, False
527 return patchset, False
527 had_filtered = False
528 had_filtered = False
528 filtered_patchset = []
529 filtered_patchset = []
529 for patch in patchset:
530 for patch in patchset:
530 filename = patch.get("filename", None)
531 filename = patch.get("filename", None)
531 if not filename or self.permission_checker.has_access(filename):
532 if not filename or self.permission_checker.has_access(filename):
532 filtered_patchset.append(patch)
533 filtered_patchset.append(patch)
533 else:
534 else:
534 had_filtered = True
535 had_filtered = True
535 if had_filtered:
536 if had_filtered:
536 if isinstance(patchset, diffs.LimitedDiffContainer):
537 if isinstance(patchset, diffs.LimitedDiffContainer):
537 filtered_patchset = diffs.LimitedDiffContainer(
538 filtered_patchset = diffs.LimitedDiffContainer(
538 patchset.diff_limit, patchset.cur_diff_size, filtered_patchset
539 patchset.diff_limit, patchset.cur_diff_size, filtered_patchset
539 )
540 )
540 return filtered_patchset, True
541 return filtered_patchset, True
541 else:
542 else:
542 return patchset, False
543 return patchset, False
543
544
544 def render_patchset_filtered(
545 def render_patchset_filtered(
545 self, diffset, patchset, source_ref=None, target_ref=None
546 self, diffset, patchset, source_ref=None, target_ref=None
546 ):
547 ):
547 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
548 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
548 result = diffset.render_patchset(
549 result = diffset.render_patchset(
549 filtered_patchset, source_ref=source_ref, target_ref=target_ref
550 filtered_patchset, source_ref=source_ref, target_ref=target_ref
550 )
551 )
551 result.has_hidden_changes = has_hidden_changes
552 result.has_hidden_changes = has_hidden_changes
552 return result
553 return result
553
554
554 def get_raw_patch(self, diff_processor):
555 def get_raw_patch(self, diff_processor):
555 if self.permission_checker is None:
556 if self.permission_checker is None:
556 return diff_processor.as_raw()
557 return diff_processor.as_raw()
557 elif self.permission_checker.has_full_access:
558 elif self.permission_checker.has_full_access:
558 return diff_processor.as_raw()
559 return diff_processor.as_raw()
559 else:
560 else:
560 return "# Repository has user-specific filters, raw patch generation is disabled."
561 return "# Repository has user-specific filters, raw patch generation is disabled."
561
562
562 @property
563 @property
563 def is_enabled(self):
564 def is_enabled(self):
564 return self.permission_checker is not None
565 return self.permission_checker is not None
565
566
566
567
567 class RepoGroupAppView(BaseAppView):
568 class RepoGroupAppView(BaseAppView):
568 def __init__(self, context, request):
569 def __init__(self, context, request):
569 super().__init__(context, request)
570 super().__init__(context, request)
570 self.db_repo_group = request.db_repo_group
571 self.db_repo_group = request.db_repo_group
571 self.db_repo_group_name = self.db_repo_group.group_name
572 self.db_repo_group_name = self.db_repo_group.group_name
572
573
573 def _get_local_tmpl_context(self, include_app_defaults=True):
574 def _get_local_tmpl_context(self, include_app_defaults=True):
574 _ = self.request.translate
575 _ = self.request.translate
575 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
576 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
576 c.repo_group = self.db_repo_group
577 c.repo_group = self.db_repo_group
577 return c
578 return c
578
579
579 def _revoke_perms_on_yourself(self, form_result):
580 def _revoke_perms_on_yourself(self, form_result):
580 _updates = [
581 _updates = [
581 u
582 u
582 for u in form_result["perm_updates"]
583 for u in form_result["perm_updates"]
583 if self._rhodecode_user.user_id == int(u[0])
584 if self._rhodecode_user.user_id == int(u[0])
584 ]
585 ]
585 _additions = [
586 _additions = [
586 u
587 u
587 for u in form_result["perm_additions"]
588 for u in form_result["perm_additions"]
588 if self._rhodecode_user.user_id == int(u[0])
589 if self._rhodecode_user.user_id == int(u[0])
589 ]
590 ]
590 _deletions = [
591 _deletions = [
591 u
592 u
592 for u in form_result["perm_deletions"]
593 for u in form_result["perm_deletions"]
593 if self._rhodecode_user.user_id == int(u[0])
594 if self._rhodecode_user.user_id == int(u[0])
594 ]
595 ]
595 admin_perm = "group.admin"
596 admin_perm = "group.admin"
596 if (
597 if (
597 _updates
598 _updates
598 and _updates[0][1] != admin_perm
599 and _updates[0][1] != admin_perm
599 or _additions
600 or _additions
600 and _additions[0][1] != admin_perm
601 and _additions[0][1] != admin_perm
601 or _deletions
602 or _deletions
602 and _deletions[0][1] != admin_perm
603 and _deletions[0][1] != admin_perm
603 ):
604 ):
604 return True
605 return True
605 return False
606 return False
606
607
607
608
608 class UserGroupAppView(BaseAppView):
609 class UserGroupAppView(BaseAppView):
609 def __init__(self, context, request):
610 def __init__(self, context, request):
610 super().__init__(context, request)
611 super().__init__(context, request)
611 self.db_user_group = request.db_user_group
612 self.db_user_group = request.db_user_group
612 self.db_user_group_name = self.db_user_group.users_group_name
613 self.db_user_group_name = self.db_user_group.users_group_name
613
614
614
615
615 class UserAppView(BaseAppView):
616 class UserAppView(BaseAppView):
616 def __init__(self, context, request):
617 def __init__(self, context, request):
617 super().__init__(context, request)
618 super().__init__(context, request)
618 self.db_user = request.db_user
619 self.db_user = request.db_user
619 self.db_user_id = self.db_user.user_id
620 self.db_user_id = self.db_user.user_id
620
621
621 _ = self.request.translate
622 _ = self.request.translate
622 if not request.db_user_supports_default:
623 if not request.db_user_supports_default:
623 if self.db_user.username == User.DEFAULT_USER:
624 if self.db_user.username == User.DEFAULT_USER:
624 h.flash(
625 h.flash(
625 _("Editing user `{}` is disabled.".format(User.DEFAULT_USER)),
626 _("Editing user `{}` is disabled.".format(User.DEFAULT_USER)),
626 category="warning",
627 category="warning",
627 )
628 )
628 raise HTTPFound(h.route_path("users"))
629 raise HTTPFound(h.route_path("users"))
629
630
630
631
631 class DataGridAppView(object):
632 class DataGridAppView(object):
632 """
633 """
633 Common class to have re-usable grid rendering components
634 Common class to have re-usable grid rendering components
634 """
635 """
635
636
636 def _extract_ordering(self, request, column_map=None):
637 def _extract_ordering(self, request, column_map=None):
637 column_map = column_map or {}
638 column_map = column_map or {}
638 column_index = safe_int(request.GET.get("order[0][column]"))
639 column_index = safe_int(request.GET.get("order[0][column]"))
639 order_dir = request.GET.get("order[0][dir]", "desc")
640 order_dir = request.GET.get("order[0][dir]", "desc")
640 order_by = request.GET.get("columns[%s][data][sort]" % column_index, "name_raw")
641 order_by = request.GET.get("columns[%s][data][sort]" % column_index, "name_raw")
641
642
642 # translate datatable to DB columns
643 # translate datatable to DB columns
643 order_by = column_map.get(order_by) or order_by
644 order_by = column_map.get(order_by) or order_by
644
645
645 search_q = request.GET.get("search[value]")
646 search_q = request.GET.get("search[value]")
646 return search_q, order_by, order_dir
647 return search_q, order_by, order_dir
647
648
648 def _extract_chunk(self, request):
649 def _extract_chunk(self, request):
649 start = safe_int(request.GET.get("start"), 0)
650 start = safe_int(request.GET.get("start"), 0)
650 length = safe_int(request.GET.get("length"), 25)
651 length = safe_int(request.GET.get("length"), 25)
651 draw = safe_int(request.GET.get("draw"))
652 draw = safe_int(request.GET.get("draw"))
652 return draw, start, length
653 return draw, start, length
653
654
654 def _get_order_col(self, order_by, model):
655 def _get_order_col(self, order_by, model):
655 if isinstance(order_by, str):
656 if isinstance(order_by, str):
656 try:
657 try:
657 return operator.attrgetter(order_by)(model)
658 return operator.attrgetter(order_by)(model)
658 except AttributeError:
659 except AttributeError:
659 return None
660 return None
660 else:
661 else:
661 return order_by
662 return order_by
662
663
663
664
664 class BaseReferencesView(RepoAppView):
665 class BaseReferencesView(RepoAppView):
665 """
666 """
666 Base for reference view for branches, tags and bookmarks.
667 Base for reference view for branches, tags and bookmarks.
667 """
668 """
668
669
669 def load_default_context(self):
670 def load_default_context(self):
670 c = self._get_local_tmpl_context()
671 c = self._get_local_tmpl_context()
671 return c
672 return c
672
673
673 def load_refs_context(self, ref_items, partials_template):
674 def load_refs_context(self, ref_items, partials_template):
674 _render = self.request.get_partial_renderer(partials_template)
675 _render = self.request.get_partial_renderer(partials_template)
675 pre_load = ["author", "date", "message", "parents"]
676 pre_load = ["author", "date", "message", "parents"]
676
677
677 is_svn = h.is_svn(self.rhodecode_vcs_repo)
678 is_svn = h.is_svn(self.rhodecode_vcs_repo)
678 is_hg = h.is_hg(self.rhodecode_vcs_repo)
679 is_hg = h.is_hg(self.rhodecode_vcs_repo)
679
680
680 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
681 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
681
682
682 closed_refs = {}
683 closed_refs = {}
683 if is_hg:
684 if is_hg:
684 closed_refs = self.rhodecode_vcs_repo.branches_closed
685 closed_refs = self.rhodecode_vcs_repo.branches_closed
685
686
686 data = []
687 data = []
687 for ref_name, commit_id in ref_items:
688 for ref_name, commit_id in ref_items:
688 commit = self.rhodecode_vcs_repo.get_commit(
689 commit = self.rhodecode_vcs_repo.get_commit(
689 commit_id=commit_id, pre_load=pre_load
690 commit_id=commit_id, pre_load=pre_load
690 )
691 )
691 closed = ref_name in closed_refs
692 closed = ref_name in closed_refs
692
693
693 # TODO: johbo: Unify generation of reference links
694 # TODO: johbo: Unify generation of reference links
694 use_commit_id = "/" in ref_name or is_svn
695 use_commit_id = "/" in ref_name or is_svn
695
696
696 if use_commit_id:
697 if use_commit_id:
697 files_url = h.route_path(
698 files_url = h.route_path(
698 "repo_files",
699 "repo_files",
699 repo_name=self.db_repo_name,
700 repo_name=self.db_repo_name,
700 f_path=ref_name if is_svn else "",
701 f_path=ref_name if is_svn else "",
701 commit_id=commit_id,
702 commit_id=commit_id,
702 _query=dict(at=ref_name),
703 _query=dict(at=ref_name),
703 )
704 )
704
705
705 else:
706 else:
706 files_url = h.route_path(
707 files_url = h.route_path(
707 "repo_files",
708 "repo_files",
708 repo_name=self.db_repo_name,
709 repo_name=self.db_repo_name,
709 f_path=ref_name if is_svn else "",
710 f_path=ref_name if is_svn else "",
710 commit_id=ref_name,
711 commit_id=ref_name,
711 _query=dict(at=ref_name),
712 _query=dict(at=ref_name),
712 )
713 )
713
714
714 data.append(
715 data.append(
715 {
716 {
716 "name": _render("name", ref_name, files_url, closed),
717 "name": _render("name", ref_name, files_url, closed),
717 "name_raw": ref_name,
718 "name_raw": ref_name,
718 "date": _render("date", commit.date),
719 "date": _render("date", commit.date),
719 "date_raw": datetime_to_time(commit.date),
720 "date_raw": datetime_to_time(commit.date),
720 "author": _render("author", commit.author),
721 "author": _render("author", commit.author),
721 "commit": _render(
722 "commit": _render(
722 "commit", commit.message, commit.raw_id, commit.idx
723 "commit", commit.message, commit.raw_id, commit.idx
723 ),
724 ),
724 "commit_raw": commit.idx,
725 "commit_raw": commit.idx,
725 "compare": _render(
726 "compare": _render(
726 "compare", format_ref_id(ref_name, commit.raw_id)
727 "compare", format_ref_id(ref_name, commit.raw_id)
727 ),
728 ),
728 }
729 }
729 )
730 )
730
731
731 return data
732 return data
732
733
733
734
734 class RepoRoutePredicate(object):
735 class RepoRoutePredicate(object):
735 def __init__(self, val, config):
736 def __init__(self, val, config):
736 self.val = val
737 self.val = val
737
738
738 def text(self):
739 def text(self):
739 return f"repo_route = {self.val}"
740 return f"repo_route = {self.val}"
740
741
741 phash = text
742 phash = text
742
743
743 def __call__(self, info, request):
744 def __call__(self, info, request):
744 if hasattr(request, "vcs_call"):
745 if hasattr(request, "vcs_call"):
745 # skip vcs calls
746 # skip vcs calls
746 return
747 return
747
748
748 repo_name = info["match"]["repo_name"]
749 repo_name = info["match"]["repo_name"]
749
750
750 repo_name_parts = repo_name.split("/")
751 repo_name_parts = repo_name.split("/")
751 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
752 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
752
753
753 if repo_name_parts != repo_slugs:
754 if repo_name_parts != repo_slugs:
754 # short-skip if the repo-name doesn't follow slug rule
755 # short-skip if the repo-name doesn't follow slug rule
755 log.warning(
756 log.warning(
756 "repo_name: %s is different than slug %s", repo_name_parts, repo_slugs
757 "repo_name: %s is different than slug %s", repo_name_parts, repo_slugs
757 )
758 )
758 return False
759 return False
759
760
760 repo_model = repo.RepoModel()
761 repo_model = repo.RepoModel()
761
762
762 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
763 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
763
764
764 def redirect_if_creating(route_info, db_repo):
765 def redirect_if_creating(route_info, db_repo):
765 skip_views = ["edit_repo_advanced_delete"]
766 skip_views = ["edit_repo_advanced_delete"]
766 route = route_info["route"]
767 route = route_info["route"]
767 # we should skip delete view so we can actually "remove" repositories
768 # we should skip delete view so we can actually "remove" repositories
768 # if they get stuck in creating state.
769 # if they get stuck in creating state.
769 if route.name in skip_views:
770 if route.name in skip_views:
770 return
771 return
771
772
772 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
773 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
773 repo_creating_url = request.route_path(
774 repo_creating_url = request.route_path(
774 "repo_creating", repo_name=db_repo.repo_name
775 "repo_creating", repo_name=db_repo.repo_name
775 )
776 )
776 raise HTTPFound(repo_creating_url)
777 raise HTTPFound(repo_creating_url)
777
778
778 if by_name_match:
779 if by_name_match:
779 # register this as request object we can re-use later
780 # register this as request object we can re-use later
780 request.db_repo = by_name_match
781 request.db_repo = by_name_match
781 request.db_repo_name = request.db_repo.repo_name
782 request.db_repo_name = request.db_repo.repo_name
782
783
783 redirect_if_creating(info, by_name_match)
784 redirect_if_creating(info, by_name_match)
784 return True
785 return True
785
786
786 by_id_match = repo_model.get_repo_by_id(repo_name)
787 by_id_match = repo_model.get_repo_by_id(repo_name)
787 if by_id_match:
788 if by_id_match:
788 request.db_repo = by_id_match
789 request.db_repo = by_id_match
789 request.db_repo_name = request.db_repo.repo_name
790 request.db_repo_name = request.db_repo.repo_name
790 redirect_if_creating(info, by_id_match)
791 redirect_if_creating(info, by_id_match)
791 return True
792 return True
792
793
793 return False
794 return False
794
795
795
796
796 class RepoForbidArchivedRoutePredicate(object):
797 class RepoForbidArchivedRoutePredicate(object):
797 def __init__(self, val, config):
798 def __init__(self, val, config):
798 self.val = val
799 self.val = val
799
800
800 def text(self):
801 def text(self):
801 return f"repo_forbid_archived = {self.val}"
802 return f"repo_forbid_archived = {self.val}"
802
803
803 phash = text
804 phash = text
804
805
805 def __call__(self, info, request):
806 def __call__(self, info, request):
806 _ = request.translate
807 _ = request.translate
807 rhodecode_db_repo = request.db_repo
808 rhodecode_db_repo = request.db_repo
808
809
809 log.debug(
810 log.debug(
810 "%s checking if archived flag for repo for %s",
811 "%s checking if archived flag for repo for %s",
811 self.__class__.__name__,
812 self.__class__.__name__,
812 rhodecode_db_repo.repo_name,
813 rhodecode_db_repo.repo_name,
813 )
814 )
814
815
815 if rhodecode_db_repo.archived:
816 if rhodecode_db_repo.archived:
816 log.warning(
817 log.warning(
817 "Current view is not supported for archived repo:%s",
818 "Current view is not supported for archived repo:%s",
818 rhodecode_db_repo.repo_name,
819 rhodecode_db_repo.repo_name,
819 )
820 )
820
821
821 h.flash(
822 h.flash(
822 h.literal(_("Action not supported for archived repository.")),
823 h.literal(_("Action not supported for archived repository.")),
823 category="warning",
824 category="warning",
824 )
825 )
825 summary_url = request.route_path(
826 summary_url = request.route_path(
826 "repo_summary", repo_name=rhodecode_db_repo.repo_name
827 "repo_summary", repo_name=rhodecode_db_repo.repo_name
827 )
828 )
828 raise HTTPFound(summary_url)
829 raise HTTPFound(summary_url)
829 return True
830 return True
830
831
831
832
832 class RepoTypeRoutePredicate(object):
833 class RepoTypeRoutePredicate(object):
833 def __init__(self, val, config):
834 def __init__(self, val, config):
834 self.val = val or ["hg", "git", "svn"]
835 self.val = val or ["hg", "git", "svn"]
835
836
836 def text(self):
837 def text(self):
837 return f"repo_accepted_type = {self.val}"
838 return f"repo_accepted_type = {self.val}"
838
839
839 phash = text
840 phash = text
840
841
841 def __call__(self, info, request):
842 def __call__(self, info, request):
842 if hasattr(request, "vcs_call"):
843 if hasattr(request, "vcs_call"):
843 # skip vcs calls
844 # skip vcs calls
844 return
845 return
845
846
846 rhodecode_db_repo = request.db_repo
847 rhodecode_db_repo = request.db_repo
847
848
848 log.debug(
849 log.debug(
849 "%s checking repo type for %s in %s",
850 "%s checking repo type for %s in %s",
850 self.__class__.__name__,
851 self.__class__.__name__,
851 rhodecode_db_repo.repo_type,
852 rhodecode_db_repo.repo_type,
852 self.val,
853 self.val,
853 )
854 )
854
855
855 if rhodecode_db_repo.repo_type in self.val:
856 if rhodecode_db_repo.repo_type in self.val:
856 return True
857 return True
857 else:
858 else:
858 log.warning(
859 log.warning(
859 "Current view is not supported for repo type:%s",
860 "Current view is not supported for repo type:%s",
860 rhodecode_db_repo.repo_type,
861 rhodecode_db_repo.repo_type,
861 )
862 )
862 return False
863 return False
863
864
864
865
865 class RepoGroupRoutePredicate(object):
866 class RepoGroupRoutePredicate(object):
866 def __init__(self, val, config):
867 def __init__(self, val, config):
867 self.val = val
868 self.val = val
868
869
869 def text(self):
870 def text(self):
870 return f"repo_group_route = {self.val}"
871 return f"repo_group_route = {self.val}"
871
872
872 phash = text
873 phash = text
873
874
874 def __call__(self, info, request):
875 def __call__(self, info, request):
875 if hasattr(request, "vcs_call"):
876 if hasattr(request, "vcs_call"):
876 # skip vcs calls
877 # skip vcs calls
877 return
878 return
878
879
879 repo_group_name = info["match"]["repo_group_name"]
880 repo_group_name = info["match"]["repo_group_name"]
880
881
881 repo_group_name_parts = repo_group_name.split("/")
882 repo_group_name_parts = repo_group_name.split("/")
882 repo_group_slugs = [
883 repo_group_slugs = [
883 x for x in [repo_name_slug(x) for x in repo_group_name_parts]
884 x for x in [repo_name_slug(x) for x in repo_group_name_parts]
884 ]
885 ]
885 if repo_group_name_parts != repo_group_slugs:
886 if repo_group_name_parts != repo_group_slugs:
886 # short-skip if the repo-name doesn't follow slug rule
887 # short-skip if the repo-name doesn't follow slug rule
887 log.warning(
888 log.warning(
888 "repo_group_name: %s is different than slug %s",
889 "repo_group_name: %s is different than slug %s",
889 repo_group_name_parts,
890 repo_group_name_parts,
890 repo_group_slugs,
891 repo_group_slugs,
891 )
892 )
892 return False
893 return False
893
894
894 repo_group_model = repo_group.RepoGroupModel()
895 repo_group_model = repo_group.RepoGroupModel()
895 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
896 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
896
897
897 if by_name_match:
898 if by_name_match:
898 # register this as request object we can re-use later
899 # register this as request object we can re-use later
899 request.db_repo_group = by_name_match
900 request.db_repo_group = by_name_match
900 request.db_repo_group_name = request.db_repo_group.group_name
901 request.db_repo_group_name = request.db_repo_group.group_name
901 return True
902 return True
902
903
903 return False
904 return False
904
905
905
906
906 class UserGroupRoutePredicate(object):
907 class UserGroupRoutePredicate(object):
907 def __init__(self, val, config):
908 def __init__(self, val, config):
908 self.val = val
909 self.val = val
909
910
910 def text(self):
911 def text(self):
911 return f"user_group_route = {self.val}"
912 return f"user_group_route = {self.val}"
912
913
913 phash = text
914 phash = text
914
915
915 def __call__(self, info, request):
916 def __call__(self, info, request):
916 if hasattr(request, "vcs_call"):
917 if hasattr(request, "vcs_call"):
917 # skip vcs calls
918 # skip vcs calls
918 return
919 return
919
920
920 user_group_id = info["match"]["user_group_id"]
921 user_group_id = info["match"]["user_group_id"]
921 user_group_model = user_group.UserGroup()
922 user_group_model = user_group.UserGroup()
922 by_id_match = user_group_model.get(user_group_id, cache=False)
923 by_id_match = user_group_model.get(user_group_id, cache=False)
923
924
924 if by_id_match:
925 if by_id_match:
925 # register this as request object we can re-use later
926 # register this as request object we can re-use later
926 request.db_user_group = by_id_match
927 request.db_user_group = by_id_match
927 return True
928 return True
928
929
929 return False
930 return False
930
931
931
932
932 class UserRoutePredicateBase(object):
933 class UserRoutePredicateBase(object):
933 supports_default = None
934 supports_default = None
934
935
935 def __init__(self, val, config):
936 def __init__(self, val, config):
936 self.val = val
937 self.val = val
937
938
938 def text(self):
939 def text(self):
939 raise NotImplementedError()
940 raise NotImplementedError()
940
941
941 def __call__(self, info, request):
942 def __call__(self, info, request):
942 if hasattr(request, "vcs_call"):
943 if hasattr(request, "vcs_call"):
943 # skip vcs calls
944 # skip vcs calls
944 return
945 return
945
946
946 user_id = info["match"]["user_id"]
947 user_id = info["match"]["user_id"]
947 user_model = user.User()
948 user_model = user.User()
948 by_id_match = user_model.get(user_id, cache=False)
949 by_id_match = user_model.get(user_id, cache=False)
949
950
950 if by_id_match:
951 if by_id_match:
951 # register this as request object we can re-use later
952 # register this as request object we can re-use later
952 request.db_user = by_id_match
953 request.db_user = by_id_match
953 request.db_user_supports_default = self.supports_default
954 request.db_user_supports_default = self.supports_default
954 return True
955 return True
955
956
956 return False
957 return False
957
958
958
959
959 class UserRoutePredicate(UserRoutePredicateBase):
960 class UserRoutePredicate(UserRoutePredicateBase):
960 supports_default = False
961 supports_default = False
961
962
962 def text(self):
963 def text(self):
963 return f"user_route = {self.val}"
964 return f"user_route = {self.val}"
964
965
965 phash = text
966 phash = text
966
967
967
968
968 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
969 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
969 supports_default = True
970 supports_default = True
970
971
971 def text(self):
972 def text(self):
972 return f"user_with_default_route = {self.val}"
973 return f"user_with_default_route = {self.val}"
973
974
974 phash = text
975 phash = text
975
976
976
977
977 def includeme(config):
978 def includeme(config):
978 config.add_route_predicate("repo_route", RepoRoutePredicate)
979 config.add_route_predicate("repo_route", RepoRoutePredicate)
979 config.add_route_predicate("repo_accepted_types", RepoTypeRoutePredicate)
980 config.add_route_predicate("repo_accepted_types", RepoTypeRoutePredicate)
980 config.add_route_predicate(
981 config.add_route_predicate(
981 "repo_forbid_when_archived", RepoForbidArchivedRoutePredicate
982 "repo_forbid_when_archived", RepoForbidArchivedRoutePredicate
982 )
983 )
983 config.add_route_predicate("repo_group_route", RepoGroupRoutePredicate)
984 config.add_route_predicate("repo_group_route", RepoGroupRoutePredicate)
984 config.add_route_predicate("user_group_route", UserGroupRoutePredicate)
985 config.add_route_predicate("user_group_route", UserGroupRoutePredicate)
985 config.add_route_predicate("user_route_with_default", UserRouteWithDefaultPredicate)
986 config.add_route_predicate("user_route_with_default", UserRouteWithDefaultPredicate)
986 config.add_route_predicate("user_route", UserRoutePredicate)
987 config.add_route_predicate("user_route", UserRoutePredicate)
@@ -1,303 +1,308 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # 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
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # 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/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import gzip
20 import gzip
21 import shutil
21 import shutil
22 import logging
22 import logging
23 import tempfile
23 import tempfile
24 import urllib.parse
24 import urllib.parse
25
25
26 from webob.exc import HTTPNotFound
26 from webob.exc import HTTPNotFound
27
27
28 import rhodecode
28 import rhodecode
29 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.lib.middleware.utils import get_path_info
30 from rhodecode.lib.middleware.utils import get_path_info
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.lib.str_utils import safe_str
35 from rhodecode.lib.str_utils import safe_str
35 from rhodecode.model.settings import VcsSettingsModel
36 from rhodecode.model.settings import VcsSettingsModel
36
37
37
38
38 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
39
40
40 VCS_TYPE_KEY = '_rc_vcs_type'
41 VCS_TYPE_KEY = '_rc_vcs_type'
41 VCS_TYPE_SKIP = '_rc_vcs_skip'
42 VCS_TYPE_SKIP = '_rc_vcs_skip'
42
43
43
44
44 def is_git(environ):
45 def is_git(environ):
45 """
46 """
46 Returns True if requests should be handled by GIT wsgi middleware
47 Returns True if requests should be handled by GIT wsgi middleware
47 """
48 """
48 path_info = get_path_info(environ)
49 path_info = get_path_info(environ)
49 is_git_path = GIT_PROTO_PAT.match(path_info)
50 is_git_path = GIT_PROTO_PAT.match(path_info)
50 log.debug(
51 log.debug(
51 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
52 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
52 is_git_path is not None)
53 is_git_path is not None)
53
54
54 return is_git_path
55 return is_git_path
55
56
56
57
57 def is_hg(environ):
58 def is_hg(environ):
58 """
59 """
59 Returns True if requests target is mercurial server - header
60 Returns True if requests target is mercurial server - header
60 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
61 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
61 """
62 """
62 is_hg_path = False
63 is_hg_path = False
63
64
64 http_accept = environ.get('HTTP_ACCEPT')
65 http_accept = environ.get('HTTP_ACCEPT')
65
66
66 if http_accept and http_accept.startswith('application/mercurial'):
67 if http_accept and http_accept.startswith('application/mercurial'):
67 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
68 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
68 if 'cmd' in query:
69 if 'cmd' in query:
69 is_hg_path = True
70 is_hg_path = True
70
71
71 path_info = get_path_info(environ)
72 path_info = get_path_info(environ)
72 log.debug(
73 log.debug(
73 'request path: `%s` detected as HG PROTOCOL %s', path_info,
74 'request path: `%s` detected as HG PROTOCOL %s', path_info,
74 is_hg_path)
75 is_hg_path)
75
76
76 return is_hg_path
77 return is_hg_path
77
78
78
79
79 def is_svn(environ):
80 def is_svn(environ):
80 """
81 """
81 Returns True if requests target is Subversion server
82 Returns True if requests target is Subversion server
82 """
83 """
83
84
84 http_dav = environ.get('HTTP_DAV', '')
85 http_dav = environ.get('HTTP_DAV', '')
85 magic_path_segment = rhodecode.CONFIG.get(
86 magic_path_segment = rhodecode.CONFIG.get(
86 'rhodecode_subversion_magic_path', '/!svn')
87 'rhodecode_subversion_magic_path', '/!svn')
87 path_info = get_path_info(environ)
88 path_info = get_path_info(environ)
88 req_method = environ['REQUEST_METHOD']
89 req_method = environ['REQUEST_METHOD']
89
90
90 is_svn_path = (
91 is_svn_path = (
91 'subversion' in http_dav or
92 'subversion' in http_dav or
92 magic_path_segment in path_info
93 magic_path_segment in path_info
93 or req_method in ['PROPFIND', 'PROPPATCH', 'HEAD']
94 or req_method in ['PROPFIND', 'PROPPATCH', 'HEAD']
94 )
95 )
95 log.debug(
96 log.debug(
96 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
97 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
97 is_svn_path)
98 is_svn_path)
98
99
99 return is_svn_path
100 return is_svn_path
100
101
101
102
102 class GunzipMiddleware(object):
103 class GunzipMiddleware(object):
103 """
104 """
104 WSGI middleware that unzips gzip-encoded requests before
105 WSGI middleware that unzips gzip-encoded requests before
105 passing on to the underlying application.
106 passing on to the underlying application.
106 """
107 """
107
108
108 def __init__(self, application):
109 def __init__(self, application):
109 self.app = application
110 self.app = application
110
111
111 def __call__(self, environ, start_response):
112 def __call__(self, environ, start_response):
112 accepts_encoding_header = safe_str(environ.get('HTTP_CONTENT_ENCODING', ''))
113 accepts_encoding_header = safe_str(environ.get('HTTP_CONTENT_ENCODING', ''))
113
114
114 if 'gzip' in accepts_encoding_header:
115 if 'gzip' in accepts_encoding_header:
115 log.debug('gzip detected, now running gunzip wrapper')
116 log.debug('gzip detected, now running gunzip wrapper')
116 wsgi_input = environ['wsgi.input']
117 wsgi_input = environ['wsgi.input']
117
118
118 if not hasattr(environ['wsgi.input'], 'seek'):
119 if not hasattr(environ['wsgi.input'], 'seek'):
119 # The gzip implementation in the standard library of Python 2.x
120 # The gzip implementation in the standard library of Python 2.x
120 # requires the '.seek()' and '.tell()' methods to be available
121 # requires the '.seek()' and '.tell()' methods to be available
121 # on the input stream. Read the data into a temporary file to
122 # on the input stream. Read the data into a temporary file to
122 # work around this limitation.
123 # work around this limitation.
123
124
124 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
125 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
125 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
126 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
126 wsgi_input.seek(0)
127 wsgi_input.seek(0)
127
128
128 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
129 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
129 # since we "Ungzipped" the content we say now it's no longer gzip
130 # since we "Ungzipped" the content we say now it's no longer gzip
130 # content encoding
131 # content encoding
131 del environ['HTTP_CONTENT_ENCODING']
132 del environ['HTTP_CONTENT_ENCODING']
132
133
133 # content length has changes ? or i'm not sure
134 # content length has changes ? or i'm not sure
134 if 'CONTENT_LENGTH' in environ:
135 if 'CONTENT_LENGTH' in environ:
135 del environ['CONTENT_LENGTH']
136 del environ['CONTENT_LENGTH']
136 else:
137 else:
137 log.debug('content not gzipped, gzipMiddleware passing '
138 log.debug('content not gzipped, gzipMiddleware passing '
138 'request further')
139 'request further')
139 return self.app(environ, start_response)
140 return self.app(environ, start_response)
140
141
141
142
142 def is_vcs_call(environ):
143 def is_vcs_call(environ):
143 if VCS_TYPE_KEY in environ:
144 if VCS_TYPE_KEY in environ:
144 raw_type = environ[VCS_TYPE_KEY]
145 raw_type = environ[VCS_TYPE_KEY]
145 return raw_type and raw_type != VCS_TYPE_SKIP
146 return raw_type and raw_type != VCS_TYPE_SKIP
146 return False
147 return False
147
148
148
149
149 def detect_vcs_request(environ, backends):
150 def detect_vcs_request(environ, backends):
150 checks = {
151 checks = {
151 'hg': (is_hg, SimpleHg),
152 'hg': (is_hg, SimpleHg),
152 'git': (is_git, SimpleGit),
153 'git': (is_git, SimpleGit),
153 'svn': (is_svn, SimpleSvn),
154 'svn': (is_svn, SimpleSvn),
154 }
155 }
155 handler = None
156 handler = None
156 # List of path views first chunk we don't do any checks
157 # List of path views first chunk we don't do any checks
157 white_list = [
158 white_list = [
158 # favicon often requested by browsers
159 # favicon often requested by browsers
159 'favicon.ico',
160 'favicon.ico',
160
161
161 # e.g /_file_store/download
162 # e.g /_file_store/download
162 '_file_store++',
163 '_file_store++',
163
164
164 # login
165 # login
165 "_admin/login",
166 "_admin/login",
166
167
168 # 2fa
169 f"{ADMIN_PREFIX}/check_2fa",
170 f"{ADMIN_PREFIX}/setup_2fa",
171
167 # _admin/api is safe too
172 # _admin/api is safe too
168 '_admin/api',
173 f'{ADMIN_PREFIX}/api',
169
174
170 # _admin/gist is safe too
175 # _admin/gist is safe too
171 '_admin/gists++',
176 f'{ADMIN_PREFIX}/gists++',
172
177
173 # _admin/my_account is safe too
178 # _admin/my_account is safe too
174 '_admin/my_account++',
179 f'{ADMIN_PREFIX}/my_account++',
175
180
176 # static files no detection
181 # static files no detection
177 '_static++',
182 '_static++',
178
183
179 # debug-toolbar
184 # debug-toolbar
180 '_debug_toolbar++',
185 '_debug_toolbar++',
181
186
182 # skip ops ping, status
187 # skip ops ping, status
183 '_admin/ops/ping',
188 f'{ADMIN_PREFIX}/ops/ping',
184 '_admin/ops/status',
189 f'{ADMIN_PREFIX}/ops/status',
185
190
186 # full channelstream connect should be VCS skipped
191 # full channelstream connect should be VCS skipped
187 '_admin/channelstream/connect',
192 f'{ADMIN_PREFIX}/channelstream/connect',
188
193
189 '++/repo_creating_check'
194 '++/repo_creating_check'
190 ]
195 ]
191 path_info = get_path_info(environ)
196 path_info = get_path_info(environ)
192 path_url = path_info.lstrip('/')
197 path_url = path_info.lstrip('/')
193 req_method = environ.get('REQUEST_METHOD')
198 req_method = environ.get('REQUEST_METHOD')
194
199
195 for item in white_list:
200 for item in white_list:
196 if item.endswith('++') and path_url.startswith(item[:-2]):
201 if item.endswith('++') and path_url.startswith(item[:-2]):
197 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
202 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
198 return handler
203 return handler
199 if item.startswith('++') and path_url.endswith(item[2:]):
204 if item.startswith('++') and path_url.endswith(item[2:]):
200 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
205 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
201 return handler
206 return handler
202 if item == path_url:
207 if item == path_url:
203 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
208 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
204 return handler
209 return handler
205
210
206 if VCS_TYPE_KEY in environ:
211 if VCS_TYPE_KEY in environ:
207 raw_type = environ[VCS_TYPE_KEY]
212 raw_type = environ[VCS_TYPE_KEY]
208 if raw_type == VCS_TYPE_SKIP:
213 if raw_type == VCS_TYPE_SKIP:
209 log.debug('got `skip` marker for vcs detection, skipping...')
214 log.debug('got `skip` marker for vcs detection, skipping...')
210 return handler
215 return handler
211
216
212 _check, handler = checks.get(raw_type) or [None, None]
217 _check, handler = checks.get(raw_type) or [None, None]
213 if handler:
218 if handler:
214 log.debug('got handler:%s from environ', handler)
219 log.debug('got handler:%s from environ', handler)
215
220
216 if not handler:
221 if not handler:
217 log.debug('request start: checking if request for `%s:%s` is of VCS type in order: %s',
222 log.debug('request start: checking if request for `%s:%s` is of VCS type in order: %s',
218 req_method, path_url, backends)
223 req_method, path_url, backends)
219 for vcs_type in backends:
224 for vcs_type in backends:
220 vcs_check, _handler = checks[vcs_type]
225 vcs_check, _handler = checks[vcs_type]
221 if vcs_check(environ):
226 if vcs_check(environ):
222 log.debug('vcs handler found %s', _handler)
227 log.debug('vcs handler found %s', _handler)
223 handler = _handler
228 handler = _handler
224 break
229 break
225
230
226 return handler
231 return handler
227
232
228
233
229 class VCSMiddleware(object):
234 class VCSMiddleware(object):
230
235
231 def __init__(self, app, registry, config, appenlight_client):
236 def __init__(self, app, registry, config, appenlight_client):
232 self.application = app
237 self.application = app
233 self.registry = registry
238 self.registry = registry
234 self.config = config
239 self.config = config
235 self.appenlight_client = appenlight_client
240 self.appenlight_client = appenlight_client
236 self.use_gzip = True
241 self.use_gzip = True
237 # order in which we check the middlewares, based on vcs.backends config
242 # order in which we check the middlewares, based on vcs.backends config
238 self.check_middlewares = config['vcs.backends']
243 self.check_middlewares = config['vcs.backends']
239
244
240 def vcs_config(self, repo_name=None):
245 def vcs_config(self, repo_name=None):
241 """
246 """
242 returns serialized VcsSettings
247 returns serialized VcsSettings
243 """
248 """
244 try:
249 try:
245 return VcsSettingsModel(
250 return VcsSettingsModel(
246 repo=repo_name).get_ui_settings_as_config_obj()
251 repo=repo_name).get_ui_settings_as_config_obj()
247 except Exception:
252 except Exception:
248 pass
253 pass
249
254
250 def wrap_in_gzip_if_enabled(self, app, config):
255 def wrap_in_gzip_if_enabled(self, app, config):
251 if self.use_gzip:
256 if self.use_gzip:
252 app = GunzipMiddleware(app)
257 app = GunzipMiddleware(app)
253 return app
258 return app
254
259
255 def _get_handler_app(self, environ):
260 def _get_handler_app(self, environ):
256 app = None
261 app = None
257 log.debug('VCSMiddleware: detecting vcs type.')
262 log.debug('VCSMiddleware: detecting vcs type.')
258 handler = detect_vcs_request(environ, self.check_middlewares)
263 handler = detect_vcs_request(environ, self.check_middlewares)
259 if handler:
264 if handler:
260 app = handler(self.config, self.registry)
265 app = handler(self.config, self.registry)
261
266
262 return app
267 return app
263
268
264 def __call__(self, environ, start_response):
269 def __call__(self, environ, start_response):
265 # check if we handle one of interesting protocols, optionally extract
270 # check if we handle one of interesting protocols, optionally extract
266 # specific vcsSettings and allow changes of how things are wrapped
271 # specific vcsSettings and allow changes of how things are wrapped
267 vcs_handler = self._get_handler_app(environ)
272 vcs_handler = self._get_handler_app(environ)
268 if vcs_handler:
273 if vcs_handler:
269 # translate the _REPO_ID into real repo NAME for usage
274 # translate the _REPO_ID into real repo NAME for usage
270 # in middleware
275 # in middleware
271
276
272 path_info = get_path_info(environ)
277 path_info = get_path_info(environ)
273 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
278 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
274
279
275 # Set acl, url and vcs repo names.
280 # Set acl, url and vcs repo names.
276 vcs_handler.set_repo_names(environ)
281 vcs_handler.set_repo_names(environ)
277
282
278 # register repo config back to the handler
283 # register repo config back to the handler
279 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
284 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
280 # maybe damaged/non existent settings. We still want to
285 # maybe damaged/non existent settings. We still want to
281 # pass that point to validate on is_valid_and_existing_repo
286 # pass that point to validate on is_valid_and_existing_repo
282 # and return proper HTTP Code back to client
287 # and return proper HTTP Code back to client
283 if vcs_conf:
288 if vcs_conf:
284 vcs_handler.repo_vcs_config = vcs_conf
289 vcs_handler.repo_vcs_config = vcs_conf
285
290
286 # check for type, presence in database and on filesystem
291 # check for type, presence in database and on filesystem
287 if not vcs_handler.is_valid_and_existing_repo(
292 if not vcs_handler.is_valid_and_existing_repo(
288 vcs_handler.acl_repo_name,
293 vcs_handler.acl_repo_name,
289 vcs_handler.base_path,
294 vcs_handler.base_path,
290 vcs_handler.SCM):
295 vcs_handler.SCM):
291 return HTTPNotFound()(environ, start_response)
296 return HTTPNotFound()(environ, start_response)
292
297
293 environ['REPO_NAME'] = vcs_handler.url_repo_name
298 environ['REPO_NAME'] = vcs_handler.url_repo_name
294
299
295 # Wrap handler in middlewares if they are enabled.
300 # Wrap handler in middlewares if they are enabled.
296 vcs_handler = self.wrap_in_gzip_if_enabled(
301 vcs_handler = self.wrap_in_gzip_if_enabled(
297 vcs_handler, self.config)
302 vcs_handler, self.config)
298 vcs_handler, _ = wrap_in_appenlight_if_enabled(
303 vcs_handler, _ = wrap_in_appenlight_if_enabled(
299 vcs_handler, self.config, self.appenlight_client)
304 vcs_handler, self.config, self.appenlight_client)
300
305
301 return vcs_handler(environ, start_response)
306 return vcs_handler(environ, start_response)
302
307
303 return self.application(environ, start_response)
308 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now