##// END OF EJS Templates
vcs: skip vcs detection on repo creating page
super-admin -
r5131:8d42472a default
parent child Browse files
Show More
@@ -1,290 +1,295 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.lib.middleware.utils import get_path_info
29 from rhodecode.lib.middleware.utils import get_path_info
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplehg import SimpleHg
32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.model.settings import VcsSettingsModel
34 from rhodecode.model.settings import VcsSettingsModel
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 VCS_TYPE_KEY = '_rc_vcs_type'
39 VCS_TYPE_KEY = '_rc_vcs_type'
40 VCS_TYPE_SKIP = '_rc_vcs_skip'
40 VCS_TYPE_SKIP = '_rc_vcs_skip'
41
41
42
42
43 def is_git(environ):
43 def is_git(environ):
44 """
44 """
45 Returns True if requests should be handled by GIT wsgi middleware
45 Returns True if requests should be handled by GIT wsgi middleware
46 """
46 """
47 path_info = get_path_info(environ)
47 path_info = get_path_info(environ)
48 is_git_path = GIT_PROTO_PAT.match(path_info)
48 is_git_path = GIT_PROTO_PAT.match(path_info)
49 log.debug(
49 log.debug(
50 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
50 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
51 is_git_path is not None)
51 is_git_path is not None)
52
52
53 return is_git_path
53 return is_git_path
54
54
55
55
56 def is_hg(environ):
56 def is_hg(environ):
57 """
57 """
58 Returns True if requests target is mercurial server - header
58 Returns True if requests target is mercurial server - header
59 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
59 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
60 """
60 """
61 is_hg_path = False
61 is_hg_path = False
62
62
63 http_accept = environ.get('HTTP_ACCEPT')
63 http_accept = environ.get('HTTP_ACCEPT')
64
64
65 if http_accept and http_accept.startswith('application/mercurial'):
65 if http_accept and http_accept.startswith('application/mercurial'):
66 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
66 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
67 if 'cmd' in query:
67 if 'cmd' in query:
68 is_hg_path = True
68 is_hg_path = True
69
69
70 path_info = get_path_info(environ)
70 path_info = get_path_info(environ)
71 log.debug(
71 log.debug(
72 'request path: `%s` detected as HG PROTOCOL %s', path_info,
72 'request path: `%s` detected as HG PROTOCOL %s', path_info,
73 is_hg_path)
73 is_hg_path)
74
74
75 return is_hg_path
75 return is_hg_path
76
76
77
77
78 def is_svn(environ):
78 def is_svn(environ):
79 """
79 """
80 Returns True if requests target is Subversion server
80 Returns True if requests target is Subversion server
81 """
81 """
82
82
83 http_dav = environ.get('HTTP_DAV', '')
83 http_dav = environ.get('HTTP_DAV', '')
84 magic_path_segment = rhodecode.CONFIG.get(
84 magic_path_segment = rhodecode.CONFIG.get(
85 'rhodecode_subversion_magic_path', '/!svn')
85 'rhodecode_subversion_magic_path', '/!svn')
86 path_info = get_path_info(environ)
86 path_info = get_path_info(environ)
87 is_svn_path = (
87 is_svn_path = (
88 'subversion' in http_dav or
88 'subversion' in http_dav or
89 magic_path_segment in path_info
89 magic_path_segment in path_info
90 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
90 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
91 )
91 )
92 log.debug(
92 log.debug(
93 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
93 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
94 is_svn_path)
94 is_svn_path)
95
95
96 return is_svn_path
96 return is_svn_path
97
97
98
98
99 class GunzipMiddleware(object):
99 class GunzipMiddleware(object):
100 """
100 """
101 WSGI middleware that unzips gzip-encoded requests before
101 WSGI middleware that unzips gzip-encoded requests before
102 passing on to the underlying application.
102 passing on to the underlying application.
103 """
103 """
104
104
105 def __init__(self, application):
105 def __init__(self, application):
106 self.app = application
106 self.app = application
107
107
108 def __call__(self, environ, start_response):
108 def __call__(self, environ, start_response):
109 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
109 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
110
110
111 if b'gzip' in accepts_encoding_header:
111 if b'gzip' in accepts_encoding_header:
112 log.debug('gzip detected, now running gunzip wrapper')
112 log.debug('gzip detected, now running gunzip wrapper')
113 wsgi_input = environ['wsgi.input']
113 wsgi_input = environ['wsgi.input']
114
114
115 if not hasattr(environ['wsgi.input'], 'seek'):
115 if not hasattr(environ['wsgi.input'], 'seek'):
116 # The gzip implementation in the standard library of Python 2.x
116 # The gzip implementation in the standard library of Python 2.x
117 # requires the '.seek()' and '.tell()' methods to be available
117 # requires the '.seek()' and '.tell()' methods to be available
118 # on the input stream. Read the data into a temporary file to
118 # on the input stream. Read the data into a temporary file to
119 # work around this limitation.
119 # work around this limitation.
120
120
121 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
121 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
122 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
122 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
123 wsgi_input.seek(0)
123 wsgi_input.seek(0)
124
124
125 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
125 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
126 # since we "Ungzipped" the content we say now it's no longer gzip
126 # since we "Ungzipped" the content we say now it's no longer gzip
127 # content encoding
127 # content encoding
128 del environ['HTTP_CONTENT_ENCODING']
128 del environ['HTTP_CONTENT_ENCODING']
129
129
130 # content length has changes ? or i'm not sure
130 # content length has changes ? or i'm not sure
131 if 'CONTENT_LENGTH' in environ:
131 if 'CONTENT_LENGTH' in environ:
132 del environ['CONTENT_LENGTH']
132 del environ['CONTENT_LENGTH']
133 else:
133 else:
134 log.debug('content not gzipped, gzipMiddleware passing '
134 log.debug('content not gzipped, gzipMiddleware passing '
135 'request further')
135 'request further')
136 return self.app(environ, start_response)
136 return self.app(environ, start_response)
137
137
138
138
139 def is_vcs_call(environ):
139 def is_vcs_call(environ):
140 if VCS_TYPE_KEY in environ:
140 if VCS_TYPE_KEY in environ:
141 raw_type = environ[VCS_TYPE_KEY]
141 raw_type = environ[VCS_TYPE_KEY]
142 return raw_type and raw_type != VCS_TYPE_SKIP
142 return raw_type and raw_type != VCS_TYPE_SKIP
143 return False
143 return False
144
144
145
145
146 def detect_vcs_request(environ, backends):
146 def detect_vcs_request(environ, backends):
147 checks = {
147 checks = {
148 'hg': (is_hg, SimpleHg),
148 'hg': (is_hg, SimpleHg),
149 'git': (is_git, SimpleGit),
149 'git': (is_git, SimpleGit),
150 'svn': (is_svn, SimpleSvn),
150 'svn': (is_svn, SimpleSvn),
151 }
151 }
152 handler = None
152 handler = None
153 # List of path views first chunk we don't do any checks
153 # List of path views first chunk we don't do any checks
154 white_list = [
154 white_list = [
155 # favicon often requested by browsers
155 # favicon often requested by browsers
156 'favicon.ico',
156 'favicon.ico',
157
157
158 # e.g /_file_store/download
158 # e.g /_file_store/download
159 '_file_store++',
159 '_file_store++',
160
160
161 # _admin/api is safe too
161 # _admin/api is safe too
162 '_admin/api',
162 '_admin/api',
163
163
164 # _admin/gist is safe too
164 # _admin/gist is safe too
165 '_admin/gists++',
165 '_admin/gists++',
166
166
167 # _admin/my_account is safe too
167 # _admin/my_account is safe too
168 '_admin/my_account++',
168 '_admin/my_account++',
169
169
170 # static files no detection
170 # static files no detection
171 '_static++',
171 '_static++',
172
172
173 # debug-toolbar
173 # debug-toolbar
174 '_debug_toolbar++',
174 '_debug_toolbar++',
175
175
176 # skip ops ping, status
176 # skip ops ping, status
177 '_admin/ops/ping',
177 '_admin/ops/ping',
178 '_admin/ops/status',
178 '_admin/ops/status',
179
179
180 # full channelstream connect should be VCS skipped
180 # full channelstream connect should be VCS skipped
181 '_admin/channelstream/connect',
181 '_admin/channelstream/connect',
182
183 '++/repo_creating_check'
182 ]
184 ]
183 path_info = get_path_info(environ)
185 path_info = get_path_info(environ)
184 path_url = path_info.lstrip('/')
186 path_url = path_info.lstrip('/')
185
187
186 for item in white_list:
188 for item in white_list:
187 if item.endswith('++') and path_url.startswith(item[:-2]):
189 if item.endswith('++') and path_url.startswith(item[:-2]):
188 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
190 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
189 return handler
191 return handler
192 if item.startswith('++') and path_url.endswith(item[2:]):
193 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
194 return handler
190 if item == path_url:
195 if item == path_url:
191 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
196 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
192 return handler
197 return handler
193
198
194 if VCS_TYPE_KEY in environ:
199 if VCS_TYPE_KEY in environ:
195 raw_type = environ[VCS_TYPE_KEY]
200 raw_type = environ[VCS_TYPE_KEY]
196 if raw_type == VCS_TYPE_SKIP:
201 if raw_type == VCS_TYPE_SKIP:
197 log.debug('got `skip` marker for vcs detection, skipping...')
202 log.debug('got `skip` marker for vcs detection, skipping...')
198 return handler
203 return handler
199
204
200 _check, handler = checks.get(raw_type) or [None, None]
205 _check, handler = checks.get(raw_type) or [None, None]
201 if handler:
206 if handler:
202 log.debug('got handler:%s from environ', handler)
207 log.debug('got handler:%s from environ', handler)
203
208
204 if not handler:
209 if not handler:
205 log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_url, backends)
210 log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_url, backends)
206 for vcs_type in backends:
211 for vcs_type in backends:
207 vcs_check, _handler = checks[vcs_type]
212 vcs_check, _handler = checks[vcs_type]
208 if vcs_check(environ):
213 if vcs_check(environ):
209 log.debug('vcs handler found %s', _handler)
214 log.debug('vcs handler found %s', _handler)
210 handler = _handler
215 handler = _handler
211 break
216 break
212
217
213 return handler
218 return handler
214
219
215
220
216 class VCSMiddleware(object):
221 class VCSMiddleware(object):
217
222
218 def __init__(self, app, registry, config, appenlight_client):
223 def __init__(self, app, registry, config, appenlight_client):
219 self.application = app
224 self.application = app
220 self.registry = registry
225 self.registry = registry
221 self.config = config
226 self.config = config
222 self.appenlight_client = appenlight_client
227 self.appenlight_client = appenlight_client
223 self.use_gzip = True
228 self.use_gzip = True
224 # order in which we check the middlewares, based on vcs.backends config
229 # order in which we check the middlewares, based on vcs.backends config
225 self.check_middlewares = config['vcs.backends']
230 self.check_middlewares = config['vcs.backends']
226
231
227 def vcs_config(self, repo_name=None):
232 def vcs_config(self, repo_name=None):
228 """
233 """
229 returns serialized VcsSettings
234 returns serialized VcsSettings
230 """
235 """
231 try:
236 try:
232 return VcsSettingsModel(
237 return VcsSettingsModel(
233 repo=repo_name).get_ui_settings_as_config_obj()
238 repo=repo_name).get_ui_settings_as_config_obj()
234 except Exception:
239 except Exception:
235 pass
240 pass
236
241
237 def wrap_in_gzip_if_enabled(self, app, config):
242 def wrap_in_gzip_if_enabled(self, app, config):
238 if self.use_gzip:
243 if self.use_gzip:
239 app = GunzipMiddleware(app)
244 app = GunzipMiddleware(app)
240 return app
245 return app
241
246
242 def _get_handler_app(self, environ):
247 def _get_handler_app(self, environ):
243 app = None
248 app = None
244 log.debug('VCSMiddleware: detecting vcs type.')
249 log.debug('VCSMiddleware: detecting vcs type.')
245 handler = detect_vcs_request(environ, self.check_middlewares)
250 handler = detect_vcs_request(environ, self.check_middlewares)
246 if handler:
251 if handler:
247 app = handler(self.config, self.registry)
252 app = handler(self.config, self.registry)
248
253
249 return app
254 return app
250
255
251 def __call__(self, environ, start_response):
256 def __call__(self, environ, start_response):
252 # check if we handle one of interesting protocols, optionally extract
257 # check if we handle one of interesting protocols, optionally extract
253 # specific vcsSettings and allow changes of how things are wrapped
258 # specific vcsSettings and allow changes of how things are wrapped
254 vcs_handler = self._get_handler_app(environ)
259 vcs_handler = self._get_handler_app(environ)
255 if vcs_handler:
260 if vcs_handler:
256 # translate the _REPO_ID into real repo NAME for usage
261 # translate the _REPO_ID into real repo NAME for usage
257 # in middleware
262 # in middleware
258
263
259 path_info = get_path_info(environ)
264 path_info = get_path_info(environ)
260 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
265 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
261
266
262 # Set acl, url and vcs repo names.
267 # Set acl, url and vcs repo names.
263 vcs_handler.set_repo_names(environ)
268 vcs_handler.set_repo_names(environ)
264
269
265 # register repo config back to the handler
270 # register repo config back to the handler
266 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
271 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
267 # maybe damaged/non existent settings. We still want to
272 # maybe damaged/non existent settings. We still want to
268 # pass that point to validate on is_valid_and_existing_repo
273 # pass that point to validate on is_valid_and_existing_repo
269 # and return proper HTTP Code back to client
274 # and return proper HTTP Code back to client
270 if vcs_conf:
275 if vcs_conf:
271 vcs_handler.repo_vcs_config = vcs_conf
276 vcs_handler.repo_vcs_config = vcs_conf
272
277
273 # check for type, presence in database and on filesystem
278 # check for type, presence in database and on filesystem
274 if not vcs_handler.is_valid_and_existing_repo(
279 if not vcs_handler.is_valid_and_existing_repo(
275 vcs_handler.acl_repo_name,
280 vcs_handler.acl_repo_name,
276 vcs_handler.base_path,
281 vcs_handler.base_path,
277 vcs_handler.SCM):
282 vcs_handler.SCM):
278 return HTTPNotFound()(environ, start_response)
283 return HTTPNotFound()(environ, start_response)
279
284
280 environ['REPO_NAME'] = vcs_handler.url_repo_name
285 environ['REPO_NAME'] = vcs_handler.url_repo_name
281
286
282 # Wrap handler in middlewares if they are enabled.
287 # Wrap handler in middlewares if they are enabled.
283 vcs_handler = self.wrap_in_gzip_if_enabled(
288 vcs_handler = self.wrap_in_gzip_if_enabled(
284 vcs_handler, self.config)
289 vcs_handler, self.config)
285 vcs_handler, _ = wrap_in_appenlight_if_enabled(
290 vcs_handler, _ = wrap_in_appenlight_if_enabled(
286 vcs_handler, self.config, self.appenlight_client)
291 vcs_handler, self.config, self.appenlight_client)
287
292
288 return vcs_handler(environ, start_response)
293 return vcs_handler(environ, start_response)
289
294
290 return self.application(environ, start_response)
295 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now