##// END OF EJS Templates
fix: auth. Disable httpostargs until auth problems are resolved
super-admin -
r1285:b2098028 stable
parent child Browse files
Show More
@@ -1,258 +1,258 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2023 RhodeCode GmbH
2 # Copyright (C) 2014-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 General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import logging
19 import logging
20 import itertools
20 import itertools
21
21
22 import mercurial
22 import mercurial
23 import mercurial.error
23 import mercurial.error
24 import mercurial.wireprotoserver
24 import mercurial.wireprotoserver
25 import mercurial.hgweb.common
25 import mercurial.hgweb.common
26 import mercurial.hgweb.hgweb_mod
26 import mercurial.hgweb.hgweb_mod
27 import webob.exc
27 import webob.exc
28
28
29 from vcsserver import pygrack, exceptions, settings, git_lfs
29 from vcsserver import pygrack, exceptions, settings, git_lfs
30 from vcsserver.lib.str_utils import ascii_bytes, safe_bytes
30 from vcsserver.lib.str_utils import ascii_bytes, safe_bytes
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 # propagated from mercurial documentation
35 # propagated from mercurial documentation
36 HG_UI_SECTIONS = [
36 HG_UI_SECTIONS = [
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
40 ]
40 ]
41
41
42
42
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
44 """Extension of hgweb that simplifies some functions."""
44 """Extension of hgweb that simplifies some functions."""
45
45
46 def _get_view(self, repo):
46 def _get_view(self, repo):
47 """Views are not supported."""
47 """Views are not supported."""
48 return repo
48 return repo
49
49
50 def loadsubweb(self):
50 def loadsubweb(self):
51 """The result is only used in the templater method which is not used."""
51 """The result is only used in the templater method which is not used."""
52 return None
52 return None
53
53
54 def run(self):
54 def run(self):
55 """Unused function so raise an exception if accidentally called."""
55 """Unused function so raise an exception if accidentally called."""
56 raise NotImplementedError
56 raise NotImplementedError
57
57
58 def templater(self, req):
58 def templater(self, req):
59 """Function used in an unreachable code path.
59 """Function used in an unreachable code path.
60
60
61 This code is unreachable because we guarantee that the HTTP request,
61 This code is unreachable because we guarantee that the HTTP request,
62 corresponds to a Mercurial command. See the is_hg method. So, we are
62 corresponds to a Mercurial command. See the is_hg method. So, we are
63 never going to get a user-visible url.
63 never going to get a user-visible url.
64 """
64 """
65 raise NotImplementedError
65 raise NotImplementedError
66
66
67 def archivelist(self, nodeid):
67 def archivelist(self, nodeid):
68 """Unused function so raise an exception if accidentally called."""
68 """Unused function so raise an exception if accidentally called."""
69 raise NotImplementedError
69 raise NotImplementedError
70
70
71 def __call__(self, environ, start_response):
71 def __call__(self, environ, start_response):
72 """Run the WSGI application.
72 """Run the WSGI application.
73
73
74 This may be called by multiple threads.
74 This may be called by multiple threads.
75 """
75 """
76 from mercurial.hgweb import request as requestmod
76 from mercurial.hgweb import request as requestmod
77 req = requestmod.parserequestfromenv(environ)
77 req = requestmod.parserequestfromenv(environ)
78 res = requestmod.wsgiresponse(req, start_response)
78 res = requestmod.wsgiresponse(req, start_response)
79 gen = self.run_wsgi(req, res)
79 gen = self.run_wsgi(req, res)
80
80
81 first_chunk = None
81 first_chunk = None
82
82
83 try:
83 try:
84 data = next(gen)
84 data = next(gen)
85
85
86 def first_chunk():
86 def first_chunk():
87 yield data
87 yield data
88 except StopIteration:
88 except StopIteration:
89 pass
89 pass
90
90
91 if first_chunk:
91 if first_chunk:
92 return itertools.chain(first_chunk(), gen)
92 return itertools.chain(first_chunk(), gen)
93 return gen
93 return gen
94
94
95 def _runwsgi(self, req, res, repo):
95 def _runwsgi(self, req, res, repo):
96
96
97 cmd = req.qsparams.get(b'cmd', '')
97 cmd = req.qsparams.get(b'cmd', '')
98 if not mercurial.wireprotoserver.iscmd(cmd):
98 if not mercurial.wireprotoserver.iscmd(cmd):
99 # NOTE(marcink): for unsupported commands, we return bad request
99 # NOTE(marcink): for unsupported commands, we return bad request
100 # internally from HG
100 # internally from HG
101 log.warning('cmd: `%s` is not supported by the mercurial wireprotocol v1', cmd)
101 log.warning('cmd: `%s` is not supported by the mercurial wireprotocol v1', cmd)
102 from mercurial.hgweb.common import statusmessage
102 from mercurial.hgweb.common import statusmessage
103 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
103 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
104 res.setbodybytes(b'')
104 res.setbodybytes(b'')
105 return res.sendresponse()
105 return res.sendresponse()
106
106
107 return super()._runwsgi(req, res, repo)
107 return super()._runwsgi(req, res, repo)
108
108
109
109
110 def sanitize_hg_ui(baseui):
110 def sanitize_hg_ui(baseui):
111 # NOTE(marcink): since python3 hgsubversion is deprecated.
111 # NOTE(marcink): since python3 hgsubversion is deprecated.
112 # From old installations we might still have this set enabled
112 # From old installations we might still have this set enabled
113 # we explicitly remove this now here to make sure it wont propagate further
113 # we explicitly remove this now here to make sure it wont propagate further
114
114
115 if baseui.config(b'extensions', b'hgsubversion') is not None:
115 if baseui.config(b'extensions', b'hgsubversion') is not None:
116 for cfg in (baseui._ocfg, baseui._tcfg, baseui._ucfg):
116 for cfg in (baseui._ocfg, baseui._tcfg, baseui._ucfg):
117 if b'extensions' in cfg:
117 if b'extensions' in cfg:
118 if b'hgsubversion' in cfg[b'extensions']:
118 if b'hgsubversion' in cfg[b'extensions']:
119 del cfg[b'extensions'][b'hgsubversion']
119 del cfg[b'extensions'][b'hgsubversion']
120
120
121
121
122 def make_hg_ui_from_config(repo_config):
122 def make_hg_ui_from_config(repo_config):
123 baseui = mercurial.ui.ui()
123 baseui = mercurial.ui.ui()
124
124
125 # clean the baseui object
125 # clean the baseui object
126 baseui._ocfg = mercurial.config.config()
126 baseui._ocfg = mercurial.config.config()
127 baseui._ucfg = mercurial.config.config()
127 baseui._ucfg = mercurial.config.config()
128 baseui._tcfg = mercurial.config.config()
128 baseui._tcfg = mercurial.config.config()
129
129
130 for section, option, value in repo_config:
130 for section, option, value in repo_config:
131 baseui.setconfig(
131 baseui.setconfig(
132 ascii_bytes(section, allow_bytes=True),
132 ascii_bytes(section, allow_bytes=True),
133 ascii_bytes(option, allow_bytes=True),
133 ascii_bytes(option, allow_bytes=True),
134 ascii_bytes(value, allow_bytes=True))
134 ascii_bytes(value, allow_bytes=True))
135
135
136 # make our hgweb quiet so it doesn't print output
136 # make our hgweb quiet so it doesn't print output
137 baseui.setconfig(b'ui', b'quiet', b'true')
137 baseui.setconfig(b'ui', b'quiet', b'true')
138
138
139 # use POST requests with args instead of GET with headers - fixes issues with big repos with lots of branches
139 # use POST requests with args instead of GET with headers - fixes issues with big repos with lots of branches
140 baseui.setconfig(b'experimental', b'httppostargs', b'true')
140 baseui.setconfig(b'experimental', b'httppostargs', b'false')
141
141
142 return baseui
142 return baseui
143
143
144
144
145 def update_hg_ui_from_hgrc(baseui, repo_path):
145 def update_hg_ui_from_hgrc(baseui, repo_path):
146 path = os.path.join(repo_path, '.hg', 'hgrc')
146 path = os.path.join(repo_path, '.hg', 'hgrc')
147
147
148 if not os.path.isfile(path):
148 if not os.path.isfile(path):
149 log.debug('hgrc file is not present at %s, skipping...', path)
149 log.debug('hgrc file is not present at %s, skipping...', path)
150 return
150 return
151 log.debug('reading hgrc from %s', path)
151 log.debug('reading hgrc from %s', path)
152 cfg = mercurial.config.config()
152 cfg = mercurial.config.config()
153 cfg.read(ascii_bytes(path))
153 cfg.read(ascii_bytes(path))
154 for section in HG_UI_SECTIONS:
154 for section in HG_UI_SECTIONS:
155 for k, v in cfg.items(section):
155 for k, v in cfg.items(section):
156 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
156 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
157 baseui.setconfig(
157 baseui.setconfig(
158 ascii_bytes(section, allow_bytes=True),
158 ascii_bytes(section, allow_bytes=True),
159 ascii_bytes(k, allow_bytes=True),
159 ascii_bytes(k, allow_bytes=True),
160 ascii_bytes(v, allow_bytes=True))
160 ascii_bytes(v, allow_bytes=True))
161
161
162
162
163 def create_hg_wsgi_app(repo_path, repo_name, config):
163 def create_hg_wsgi_app(repo_path, repo_name, config):
164 """
164 """
165 Prepares a WSGI application to handle Mercurial requests.
165 Prepares a WSGI application to handle Mercurial requests.
166
166
167 :param config: is a list of 3-item tuples representing a ConfigObject
167 :param config: is a list of 3-item tuples representing a ConfigObject
168 (it is the serialized version of the config object).
168 (it is the serialized version of the config object).
169 """
169 """
170 log.debug("Creating Mercurial WSGI application")
170 log.debug("Creating Mercurial WSGI application")
171
171
172 baseui = make_hg_ui_from_config(config)
172 baseui = make_hg_ui_from_config(config)
173 update_hg_ui_from_hgrc(baseui, repo_path)
173 update_hg_ui_from_hgrc(baseui, repo_path)
174 sanitize_hg_ui(baseui)
174 sanitize_hg_ui(baseui)
175
175
176 try:
176 try:
177 return HgWeb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
177 return HgWeb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
178 except mercurial.error.RequirementError as e:
178 except mercurial.error.RequirementError as e:
179 raise exceptions.RequirementException(e)(e)
179 raise exceptions.RequirementException(e)(e)
180
180
181
181
182 class GitHandler:
182 class GitHandler:
183 """
183 """
184 Handler for Git operations like push/pull etc
184 Handler for Git operations like push/pull etc
185 """
185 """
186 def __init__(self, repo_location, repo_name, git_path, update_server_info,
186 def __init__(self, repo_location, repo_name, git_path, update_server_info,
187 extras):
187 extras):
188 if not os.path.isdir(repo_location):
188 if not os.path.isdir(repo_location):
189 raise OSError(repo_location)
189 raise OSError(repo_location)
190 self.content_path = repo_location
190 self.content_path = repo_location
191 self.repo_name = repo_name
191 self.repo_name = repo_name
192 self.repo_location = repo_location
192 self.repo_location = repo_location
193 self.extras = extras
193 self.extras = extras
194 self.git_path = git_path
194 self.git_path = git_path
195 self.update_server_info = update_server_info
195 self.update_server_info = update_server_info
196
196
197 def __call__(self, environ, start_response):
197 def __call__(self, environ, start_response):
198 app = webob.exc.HTTPNotFound()
198 app = webob.exc.HTTPNotFound()
199 candidate_paths = (
199 candidate_paths = (
200 self.content_path, os.path.join(self.content_path, '.git'))
200 self.content_path, os.path.join(self.content_path, '.git'))
201
201
202 for content_path in candidate_paths:
202 for content_path in candidate_paths:
203 try:
203 try:
204 app = pygrack.GitRepository(
204 app = pygrack.GitRepository(
205 self.repo_name, content_path, self.git_path,
205 self.repo_name, content_path, self.git_path,
206 self.update_server_info, self.extras)
206 self.update_server_info, self.extras)
207 break
207 break
208 except OSError:
208 except OSError:
209 continue
209 continue
210
210
211 return app(environ, start_response)
211 return app(environ, start_response)
212
212
213
213
214 def create_git_wsgi_app(repo_path, repo_name, config):
214 def create_git_wsgi_app(repo_path, repo_name, config):
215 """
215 """
216 Creates a WSGI application to handle Git requests.
216 Creates a WSGI application to handle Git requests.
217
217
218 :param config: is a dictionary holding the extras.
218 :param config: is a dictionary holding the extras.
219 """
219 """
220 git_path = settings.GIT_EXECUTABLE()
220 git_path = settings.GIT_EXECUTABLE()
221 update_server_info = config.pop('git_update_server_info')
221 update_server_info = config.pop('git_update_server_info')
222 app = GitHandler(
222 app = GitHandler(
223 repo_path, repo_name, git_path, update_server_info, config)
223 repo_path, repo_name, git_path, update_server_info, config)
224
224
225 return app
225 return app
226
226
227
227
228 class GitLFSHandler:
228 class GitLFSHandler:
229 """
229 """
230 Handler for Git LFS operations
230 Handler for Git LFS operations
231 """
231 """
232
232
233 def __init__(self, repo_location, repo_name, git_path, update_server_info,
233 def __init__(self, repo_location, repo_name, git_path, update_server_info,
234 extras):
234 extras):
235 if not os.path.isdir(repo_location):
235 if not os.path.isdir(repo_location):
236 raise OSError(repo_location)
236 raise OSError(repo_location)
237 self.content_path = repo_location
237 self.content_path = repo_location
238 self.repo_name = repo_name
238 self.repo_name = repo_name
239 self.repo_location = repo_location
239 self.repo_location = repo_location
240 self.extras = extras
240 self.extras = extras
241 self.git_path = git_path
241 self.git_path = git_path
242 self.update_server_info = update_server_info
242 self.update_server_info = update_server_info
243
243
244 def get_app(self, git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme):
244 def get_app(self, git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme):
245 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
245 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
246 return app
246 return app
247
247
248
248
249 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
249 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
250 git_path = settings.GIT_EXECUTABLE()
250 git_path = settings.GIT_EXECUTABLE()
251 update_server_info = config.pop('git_update_server_info')
251 update_server_info = config.pop('git_update_server_info')
252 git_lfs_enabled = config.pop('git_lfs_enabled')
252 git_lfs_enabled = config.pop('git_lfs_enabled')
253 git_lfs_store_path = config.pop('git_lfs_store_path')
253 git_lfs_store_path = config.pop('git_lfs_store_path')
254 git_lfs_http_scheme = config.pop('git_lfs_http_scheme', 'http')
254 git_lfs_http_scheme = config.pop('git_lfs_http_scheme', 'http')
255 app = GitLFSHandler(
255 app = GitLFSHandler(
256 repo_path, repo_name, git_path, update_server_info, config)
256 repo_path, repo_name, git_path, update_server_info, config)
257
257
258 return app.get_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
258 return app.get_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
General Comments 0
You need to be logged in to leave comments. Login now