##// END OF EJS Templates
Mercurial: fix ssh-server support for UI objects...
marcink -
r3626:30cddb61 default
parent child Browse files
Show More
@@ -1,123 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 import shutil
24 23 import logging
25 24 import tempfile
26 25 import textwrap
27
26 import collections
28 27 from .base import VcsServer
28 from rhodecode.model.settings import VcsSettingsModel
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class MercurialTunnelWrapper(object):
34 34 process = None
35 35
36 36 def __init__(self, server):
37 37 self.server = server
38 38 self.stdin = sys.stdin
39 39 self.stdout = sys.stdout
40 self.svn_conf_fd, self.svn_conf_path = tempfile.mkstemp()
41 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp()
40 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp(prefix='hgrc_rhodecode_')
42 41
43 42 def create_hooks_env(self):
43 repo_name = self.server.repo_name
44 hg_flags = self.config_to_hgrc(repo_name)
44 45
45 46 content = textwrap.dedent(
46 47 '''
47 # SSH hooks version=1.0.0
48 # SSH hooks version=2.0.0
48 49 [hooks]
49 50 pretxnchangegroup.ssh_auth=python:vcsserver.hooks.pre_push_ssh_auth
50 51 pretxnchangegroup.ssh=python:vcsserver.hooks.pre_push_ssh
51 52 changegroup.ssh=python:vcsserver.hooks.post_push_ssh
52 53
53 54 preoutgoing.ssh=python:vcsserver.hooks.pre_pull_ssh
54 55 outgoing.ssh=python:vcsserver.hooks.post_pull_ssh
55 56
57 # Custom Config version=2.0.0
58 {custom}
56 59 '''
57 )
60 ).format(custom='\n'.join(hg_flags))
61
62 root = self.server.get_root_store()
63 hgrc_custom = os.path.join(root, repo_name, '.hg', 'hgrc_rhodecode')
64 hgrc_main = os.path.join(root, repo_name, '.hg', 'hgrc')
58 65
66 # cleanup custom hgrc file
67 if os.path.isfile(hgrc_custom):
68 with open(hgrc_custom, 'wb') as f:
69 f.write('')
70 log.debug('Cleanup custom hgrc file under %s', hgrc_custom)
71
72 # write temp
59 73 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
60 74 hooks_env_file.write(content)
61 root = self.server.get_root_store()
62 75
63 hgrc_custom = os.path.join(
64 root, self.server.repo_name, '.hg', 'hgrc_rhodecode')
65 log.debug('Wrote custom hgrc file under %s', hgrc_custom)
66 shutil.move(
67 self.hooks_env_path, hgrc_custom)
68
69 hgrc_main = os.path.join(
70 root, self.server.repo_name, '.hg', 'hgrc')
71 include_marker = '%include hgrc_rhodecode'
76 return self.hooks_env_path
72 77
73 if not os.path.isfile(hgrc_main):
74 os.mknod(hgrc_main)
75
76 with open(hgrc_main, 'rb') as f:
77 data = f.read()
78 has_marker = include_marker in data
78 def remove_configs(self):
79 os.remove(self.hooks_env_path)
79 80
80 if not has_marker:
81 log.debug('Adding include marker for hooks')
82 with open(hgrc_main, 'wa') as f:
83 f.write(textwrap.dedent('''
84 # added by RhodeCode
85 {}
86 '''.format(include_marker)))
87
88 def command(self):
81 def command(self, hgrc_path):
89 82 root = self.server.get_root_store()
90 83
91 84 command = (
92 "cd {root}; {hg_path} -R {root}{repo_name} "
85 "cd {root}; HGRCPATH={hgrc} {hg_path} -R {root}{repo_name} "
93 86 "serve --stdio".format(
94 87 root=root, hg_path=self.server.hg_path,
95 repo_name=self.server.repo_name))
88 repo_name=self.server.repo_name, hgrc=hgrc_path))
96 89 log.debug("Final CMD: %s", command)
97 90 return command
98 91
99 92 def run(self, extras):
100 93 # at this point we cannot tell, we do further ACL checks
101 94 # inside the hooks
102 95 action = '?'
103 96 # permissions are check via `pre_push_ssh_auth` hook
104 97 self.server.update_environment(action=action, extras=extras)
105 self.create_hooks_env()
106 return os.system(self.command())
98 custom_hgrc_file = self.create_hooks_env()
99
100 try:
101 return os.system(self.command(custom_hgrc_file))
102 finally:
103 self.remove_configs()
107 104
108 105
109 106 class MercurialServer(VcsServer):
110 107 backend = 'hg'
108 cli_flags = ['phases', 'largefiles', 'extensions', 'experimental']
111 109
112 def __init__(self, store, ini_path, repo_name,
113 user, user_permissions, config, env):
114 super(MercurialServer, self).\
115 __init__(user, user_permissions, config, env)
110 def __init__(self, store, ini_path, repo_name, user, user_permissions, config, env):
111 super(MercurialServer, self).__init__(user, user_permissions, config, env)
116 112
117 113 self.store = store
118 114 self.ini_path = ini_path
119 115 self.repo_name = repo_name
120 self._path = self.hg_path = config.get(
121 'app:main', 'ssh.executable.hg')
116 self._path = self.hg_path = config.get('app:main', 'ssh.executable.hg')
117 self.tunnel = MercurialTunnelWrapper(server=self)
118
119 def config_to_hgrc(self, repo_name):
120 ui_sections = collections.defaultdict(list)
121 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
122 122
123 self.tunnel = MercurialTunnelWrapper(server=self)
123 for entry in ui:
124 if not entry.active:
125 continue
126 sec = entry.section
127
128 if sec in self.cli_flags:
129 ui_sections[sec].append([entry.key, entry.value])
130
131 flags = []
132 for _sec, key_val in ui_sections.items():
133 flags.append(' ')
134 flags.append('[{}]'.format(_sec))
135 for key, val in key_val:
136 flags.append('{}= {}'.format(key, val))
137 return flags
@@ -1,116 +1,119 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import os
21 22 import mock
22 23 import pytest
23 24
24 25 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialServer
25 26 from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dummy_user
26 27
27 28
28 29 class MercurialServerCreator(object):
29 30 root = '/tmp/repo/path/'
30 31 hg_path = '/usr/local/bin/hg'
31 32
32 33 config_data = {
33 34 'app:main': {
34 35 'ssh.executable.hg': hg_path,
35 36 'vcs.hooks.protocol': 'http',
36 37 }
37 38 }
38 39 repo_name = 'test_hg'
39 40 user = plain_dummy_user()
40 41
41 42 def __init__(self):
42 43 def config_get(part, key):
43 44 return self.config_data.get(part, {}).get(key)
44 45 self.config_mock = mock.Mock()
45 46 self.config_mock.get = mock.Mock(side_effect=config_get)
46 47
47 48 def create(self, **kwargs):
48 49 parameters = {
49 50 'store': self.root,
50 51 'ini_path': '',
51 52 'user': self.user,
52 53 'repo_name': self.repo_name,
53 54 'user_permissions': {
54 55 'test_hg': 'repository.admin'
55 56 },
56 57 'config': self.config_mock,
57 58 'env': plain_dummy_env()
58 59 }
59 60 parameters.update(kwargs)
60 61 server = MercurialServer(**parameters)
61 62 return server
62 63
63 64
64 65 @pytest.fixture
65 66 def hg_server(app):
66 67 return MercurialServerCreator()
67 68
68 69
69 70 class TestMercurialServer(object):
70 71
71 def test_command(self, hg_server):
72 def test_command(self, hg_server, tmpdir):
72 73 server = hg_server.create()
74 custom_hgrc = os.path.join(str(tmpdir), 'hgrc')
73 75 expected_command = (
74 'cd {root}; {hg_path} -R {root}{repo_name} serve --stdio'.format(
75 root=hg_server.root, hg_path=hg_server.hg_path,
76 'cd {root}; HGRCPATH={custom_hgrc} {hg_path} -R {root}{repo_name} serve --stdio'.format(
77 root=hg_server.root, custom_hgrc=custom_hgrc, hg_path=hg_server.hg_path,
76 78 repo_name=hg_server.repo_name)
77 79 )
78 assert expected_command == server.tunnel.command()
80 server_command = server.tunnel.command(custom_hgrc)
81 assert expected_command == server_command
79 82
80 83 @pytest.mark.parametrize('permissions, action, code', [
81 84 ({}, 'pull', -2),
82 85 ({'test_hg': 'repository.read'}, 'pull', 0),
83 86 ({'test_hg': 'repository.read'}, 'push', -2),
84 87 ({'test_hg': 'repository.write'}, 'push', 0),
85 88 ({'test_hg': 'repository.admin'}, 'push', 0),
86 89
87 90 ])
88 91 def test_permission_checks(self, hg_server, permissions, action, code):
89 92 server = hg_server.create(user_permissions=permissions)
90 93 result = server._check_permissions(action)
91 94 assert result is code
92 95
93 96 @pytest.mark.parametrize('permissions, value', [
94 97 ({}, False),
95 98 ({'test_hg': 'repository.read'}, False),
96 99 ({'test_hg': 'repository.write'}, True),
97 100 ({'test_hg': 'repository.admin'}, True),
98 101
99 102 ])
100 103 def test_has_write_permissions(self, hg_server, permissions, value):
101 104 server = hg_server.create(user_permissions=permissions)
102 105 result = server.has_write_perm()
103 106 assert result is value
104 107
105 108 def test_run_returns_executes_command(self, hg_server):
106 109 server = hg_server.create()
107 110 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialTunnelWrapper
108 111 with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch:
109 112 _patch.return_value = 0
110 113 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'):
111 114 exit_code = server.run()
112 115
113 116 assert exit_code == (0, False)
114 117
115 118
116 119
@@ -1,886 +1,888 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import hashlib
23 23 import logging
24 24 from collections import namedtuple
25 25 from functools import wraps
26 26 import bleach
27 27
28 28 from rhodecode.lib import rc_cache
29 29 from rhodecode.lib.utils2 import (
30 30 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
31 31 from rhodecode.lib.vcs.backends import base
32 32 from rhodecode.model import BaseModel
33 33 from rhodecode.model.db import (
34 34 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
35 35 from rhodecode.model.meta import Session
36 36
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 UiSetting = namedtuple(
42 42 'UiSetting', ['section', 'key', 'value', 'active'])
43 43
44 44 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
45 45
46 46
47 47 class SettingNotFound(Exception):
48 48 def __init__(self, setting_id):
49 49 msg = 'Setting `{}` is not found'.format(setting_id)
50 50 super(SettingNotFound, self).__init__(msg)
51 51
52 52
53 53 class SettingsModel(BaseModel):
54 54 BUILTIN_HOOKS = (
55 55 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
56 56 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
57 57 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
58 58 RhodeCodeUi.HOOK_PUSH_KEY,)
59 59 HOOKS_SECTION = 'hooks'
60 60
61 61 def __init__(self, sa=None, repo=None):
62 62 self.repo = repo
63 63 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
64 64 self.SettingsDbModel = (
65 65 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
66 66 super(SettingsModel, self).__init__(sa)
67 67
68 68 def get_ui_by_key(self, key):
69 69 q = self.UiDbModel.query()
70 70 q = q.filter(self.UiDbModel.ui_key == key)
71 71 q = self._filter_by_repo(RepoRhodeCodeUi, q)
72 72 return q.scalar()
73 73
74 74 def get_ui_by_section(self, section):
75 75 q = self.UiDbModel.query()
76 76 q = q.filter(self.UiDbModel.ui_section == section)
77 77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 78 return q.all()
79 79
80 80 def get_ui_by_section_and_key(self, section, key):
81 81 q = self.UiDbModel.query()
82 82 q = q.filter(self.UiDbModel.ui_section == section)
83 83 q = q.filter(self.UiDbModel.ui_key == key)
84 84 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 85 return q.scalar()
86 86
87 87 def get_ui(self, section=None, key=None):
88 88 q = self.UiDbModel.query()
89 89 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90 90
91 91 if section:
92 92 q = q.filter(self.UiDbModel.ui_section == section)
93 93 if key:
94 94 q = q.filter(self.UiDbModel.ui_key == key)
95 95
96 96 # TODO: mikhail: add caching
97 97 result = [
98 98 UiSetting(
99 99 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
100 100 value=safe_str(r.ui_value), active=r.ui_active
101 101 )
102 102 for r in q.all()
103 103 ]
104 104 return result
105 105
106 106 def get_builtin_hooks(self):
107 107 q = self.UiDbModel.query()
108 108 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 109 return self._get_hooks(q)
110 110
111 111 def get_custom_hooks(self):
112 112 q = self.UiDbModel.query()
113 113 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 114 return self._get_hooks(q)
115 115
116 116 def create_ui_section_value(self, section, val, key=None, active=True):
117 117 new_ui = self.UiDbModel()
118 118 new_ui.ui_section = section
119 119 new_ui.ui_value = val
120 120 new_ui.ui_active = active
121 121
122 122 repository_id = ''
123 123 if self.repo:
124 124 repo = self._get_repo(self.repo)
125 125 repository_id = repo.repo_id
126 126 new_ui.repository_id = repository_id
127 127
128 128 if not key:
129 129 # keys are unique so they need appended info
130 130 if self.repo:
131 131 key = hashlib.sha1(
132 132 '{}{}{}'.format(section, val, repository_id)).hexdigest()
133 133 else:
134 134 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
135 135
136 136 new_ui.ui_key = key
137 137
138 138 Session().add(new_ui)
139 139 return new_ui
140 140
141 141 def create_or_update_hook(self, key, value):
142 142 ui = (
143 143 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
144 144 self.UiDbModel())
145 145 ui.ui_section = self.HOOKS_SECTION
146 146 ui.ui_active = True
147 147 ui.ui_key = key
148 148 ui.ui_value = value
149 149
150 150 if self.repo:
151 151 repo = self._get_repo(self.repo)
152 152 repository_id = repo.repo_id
153 153 ui.repository_id = repository_id
154 154
155 155 Session().add(ui)
156 156 return ui
157 157
158 158 def delete_ui(self, id_):
159 159 ui = self.UiDbModel.get(id_)
160 160 if not ui:
161 161 raise SettingNotFound(id_)
162 162 Session().delete(ui)
163 163
164 164 def get_setting_by_name(self, name):
165 165 q = self._get_settings_query()
166 166 q = q.filter(self.SettingsDbModel.app_settings_name == name)
167 167 return q.scalar()
168 168
169 169 def create_or_update_setting(
170 170 self, name, val=Optional(''), type_=Optional('unicode')):
171 171 """
172 172 Creates or updates RhodeCode setting. If updates is triggered it will
173 173 only update parameters that are explicityl set Optional instance will
174 174 be skipped
175 175
176 176 :param name:
177 177 :param val:
178 178 :param type_:
179 179 :return:
180 180 """
181 181
182 182 res = self.get_setting_by_name(name)
183 183 repo = self._get_repo(self.repo) if self.repo else None
184 184
185 185 if not res:
186 186 val = Optional.extract(val)
187 187 type_ = Optional.extract(type_)
188 188
189 189 args = (
190 190 (repo.repo_id, name, val, type_)
191 191 if repo else (name, val, type_))
192 192 res = self.SettingsDbModel(*args)
193 193
194 194 else:
195 195 if self.repo:
196 196 res.repository_id = repo.repo_id
197 197
198 198 res.app_settings_name = name
199 199 if not isinstance(type_, Optional):
200 200 # update if set
201 201 res.app_settings_type = type_
202 202 if not isinstance(val, Optional):
203 203 # update if set
204 204 res.app_settings_value = val
205 205
206 206 Session().add(res)
207 207 return res
208 208
209 209 def invalidate_settings_cache(self):
210 210 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
211 211 CacheKey.set_invalidate(invalidation_namespace)
212 212
213 213 def get_all_settings(self, cache=False):
214 214 region = rc_cache.get_or_create_region('sql_cache_short')
215 215 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
216 216
217 217 @region.conditional_cache_on_arguments(condition=cache)
218 218 def _get_all_settings(name, key):
219 219 q = self._get_settings_query()
220 220 if not q:
221 221 raise Exception('Could not get application settings !')
222 222
223 223 settings = {
224 224 'rhodecode_' + result.app_settings_name: result.app_settings_value
225 225 for result in q
226 226 }
227 227 return settings
228 228
229 229 repo = self._get_repo(self.repo) if self.repo else None
230 230 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
231 231
232 232 inv_context_manager = rc_cache.InvalidationContext(
233 233 uid='cache_settings', invalidation_namespace=invalidation_namespace)
234 234 with inv_context_manager as invalidation_context:
235 235 # check for stored invalidation signal, and maybe purge the cache
236 236 # before computing it again
237 237 if invalidation_context.should_invalidate():
238 238 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
239 239 # reads different settings etc. It's little too much but those caches
240 240 # are anyway very short lived and it's a safest way.
241 241 region = rc_cache.get_or_create_region('sql_cache_short')
242 242 region.invalidate()
243 243
244 244 result = _get_all_settings('rhodecode_settings', key)
245 245 log.debug('Fetching app settings for key: %s took: %.3fs', key,
246 246 inv_context_manager.compute_time)
247 247
248 248 return result
249 249
250 250 def get_auth_settings(self):
251 251 q = self._get_settings_query()
252 252 q = q.filter(
253 253 self.SettingsDbModel.app_settings_name.startswith('auth_'))
254 254 rows = q.all()
255 255 auth_settings = {
256 256 row.app_settings_name: row.app_settings_value for row in rows}
257 257 return auth_settings
258 258
259 259 def get_auth_plugins(self):
260 260 auth_plugins = self.get_setting_by_name("auth_plugins")
261 261 return auth_plugins.app_settings_value
262 262
263 263 def get_default_repo_settings(self, strip_prefix=False):
264 264 q = self._get_settings_query()
265 265 q = q.filter(
266 266 self.SettingsDbModel.app_settings_name.startswith('default_'))
267 267 rows = q.all()
268 268
269 269 result = {}
270 270 for row in rows:
271 271 key = row.app_settings_name
272 272 if strip_prefix:
273 273 key = remove_prefix(key, prefix='default_')
274 274 result.update({key: row.app_settings_value})
275 275 return result
276 276
277 277 def get_repo(self):
278 278 repo = self._get_repo(self.repo)
279 279 if not repo:
280 280 raise Exception(
281 281 'Repository `{}` cannot be found inside the database'.format(
282 282 self.repo))
283 283 return repo
284 284
285 285 def _filter_by_repo(self, model, query):
286 286 if self.repo:
287 287 repo = self.get_repo()
288 288 query = query.filter(model.repository_id == repo.repo_id)
289 289 return query
290 290
291 291 def _get_hooks(self, query):
292 292 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
293 293 query = self._filter_by_repo(RepoRhodeCodeUi, query)
294 294 return query.all()
295 295
296 296 def _get_settings_query(self):
297 297 q = self.SettingsDbModel.query()
298 298 return self._filter_by_repo(RepoRhodeCodeSetting, q)
299 299
300 300 def list_enabled_social_plugins(self, settings):
301 301 enabled = []
302 302 for plug in SOCIAL_PLUGINS_LIST:
303 303 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
304 304 )):
305 305 enabled.append(plug)
306 306 return enabled
307 307
308 308
309 309 def assert_repo_settings(func):
310 310 @wraps(func)
311 311 def _wrapper(self, *args, **kwargs):
312 312 if not self.repo_settings:
313 313 raise Exception('Repository is not specified')
314 314 return func(self, *args, **kwargs)
315 315 return _wrapper
316 316
317 317
318 318 class IssueTrackerSettingsModel(object):
319 319 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
320 320 SETTINGS_PREFIX = 'issuetracker_'
321 321
322 322 def __init__(self, sa=None, repo=None):
323 323 self.global_settings = SettingsModel(sa=sa)
324 324 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
325 325
326 326 @property
327 327 def inherit_global_settings(self):
328 328 if not self.repo_settings:
329 329 return True
330 330 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
331 331 return setting.app_settings_value if setting else True
332 332
333 333 @inherit_global_settings.setter
334 334 def inherit_global_settings(self, value):
335 335 if self.repo_settings:
336 336 settings = self.repo_settings.create_or_update_setting(
337 337 self.INHERIT_SETTINGS, value, type_='bool')
338 338 Session().add(settings)
339 339
340 340 def _get_keyname(self, key, uid, prefix=''):
341 341 return '{0}{1}{2}_{3}'.format(
342 342 prefix, self.SETTINGS_PREFIX, key, uid)
343 343
344 344 def _make_dict_for_settings(self, qs):
345 345 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
346 346
347 347 issuetracker_entries = {}
348 348 # create keys
349 349 for k, v in qs.items():
350 350 if k.startswith(prefix_match):
351 351 uid = k[len(prefix_match):]
352 352 issuetracker_entries[uid] = None
353 353
354 354 def url_cleaner(input_str):
355 355 input_str = input_str.replace('"', '').replace("'", '')
356 356 input_str = bleach.clean(input_str, strip=True)
357 357 return input_str
358 358
359 359 # populate
360 360 for uid in issuetracker_entries:
361 361 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
362 362
363 363 issuetracker_entries[uid] = AttributeDict({
364 364 'pat': qs.get(
365 365 self._get_keyname('pat', uid, 'rhodecode_')),
366 366 'url': url_cleaner(
367 367 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
368 368 'pref': bleach.clean(
369 369 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
370 370 'desc': qs.get(
371 371 self._get_keyname('desc', uid, 'rhodecode_')),
372 372 })
373 373
374 374 return issuetracker_entries
375 375
376 376 def get_global_settings(self, cache=False):
377 377 """
378 378 Returns list of global issue tracker settings
379 379 """
380 380 defaults = self.global_settings.get_all_settings(cache=cache)
381 381 settings = self._make_dict_for_settings(defaults)
382 382 return settings
383 383
384 384 def get_repo_settings(self, cache=False):
385 385 """
386 386 Returns list of issue tracker settings per repository
387 387 """
388 388 if not self.repo_settings:
389 389 raise Exception('Repository is not specified')
390 390 all_settings = self.repo_settings.get_all_settings(cache=cache)
391 391 settings = self._make_dict_for_settings(all_settings)
392 392 return settings
393 393
394 394 def get_settings(self, cache=False):
395 395 if self.inherit_global_settings:
396 396 return self.get_global_settings(cache=cache)
397 397 else:
398 398 return self.get_repo_settings(cache=cache)
399 399
400 400 def delete_entries(self, uid):
401 401 if self.repo_settings:
402 402 all_patterns = self.get_repo_settings()
403 403 settings_model = self.repo_settings
404 404 else:
405 405 all_patterns = self.get_global_settings()
406 406 settings_model = self.global_settings
407 407 entries = all_patterns.get(uid, [])
408 408
409 409 for del_key in entries:
410 410 setting_name = self._get_keyname(del_key, uid)
411 411 entry = settings_model.get_setting_by_name(setting_name)
412 412 if entry:
413 413 Session().delete(entry)
414 414
415 415 Session().commit()
416 416
417 417 def create_or_update_setting(
418 418 self, name, val=Optional(''), type_=Optional('unicode')):
419 419 if self.repo_settings:
420 420 setting = self.repo_settings.create_or_update_setting(
421 421 name, val, type_)
422 422 else:
423 423 setting = self.global_settings.create_or_update_setting(
424 424 name, val, type_)
425 425 return setting
426 426
427 427
428 428 class VcsSettingsModel(object):
429 429
430 430 INHERIT_SETTINGS = 'inherit_vcs_settings'
431 431 GENERAL_SETTINGS = (
432 432 'use_outdated_comments',
433 433 'pr_merge_enabled',
434 434 'hg_use_rebase_for_merging',
435 435 'hg_close_branch_before_merging',
436 436 'git_use_rebase_for_merging',
437 437 'git_close_branch_before_merging',
438 438 'diff_cache',
439 439 )
440 440
441 441 HOOKS_SETTINGS = (
442 442 ('hooks', 'changegroup.repo_size'),
443 443 ('hooks', 'changegroup.push_logger'),
444 444 ('hooks', 'outgoing.pull_logger'),
445 445 )
446 446 HG_SETTINGS = (
447 447 ('extensions', 'largefiles'),
448 448 ('phases', 'publish'),
449 449 ('extensions', 'evolve'),
450 450 ('extensions', 'topic'),
451 451 ('experimental', 'evolution'),
452 ('experimental', 'evolution.exchange'),
452 453 )
453 454 GIT_SETTINGS = (
454 455 ('vcs_git_lfs', 'enabled'),
455 456 )
456 457 GLOBAL_HG_SETTINGS = (
457 458 ('extensions', 'largefiles'),
458 459 ('largefiles', 'usercache'),
459 460 ('phases', 'publish'),
460 461 ('extensions', 'hgsubversion'),
461 462 ('extensions', 'evolve'),
462 463 ('extensions', 'topic'),
463 464 ('experimental', 'evolution'),
465 ('experimental', 'evolution.exchange'),
464 466 )
465 467
466 468 GLOBAL_GIT_SETTINGS = (
467 469 ('vcs_git_lfs', 'enabled'),
468 470 ('vcs_git_lfs', 'store_location')
469 471 )
470 472
471 473 GLOBAL_SVN_SETTINGS = (
472 474 ('vcs_svn_proxy', 'http_requests_enabled'),
473 475 ('vcs_svn_proxy', 'http_server_url')
474 476 )
475 477
476 478 SVN_BRANCH_SECTION = 'vcs_svn_branch'
477 479 SVN_TAG_SECTION = 'vcs_svn_tag'
478 480 SSL_SETTING = ('web', 'push_ssl')
479 481 PATH_SETTING = ('paths', '/')
480 482
481 483 def __init__(self, sa=None, repo=None):
482 484 self.global_settings = SettingsModel(sa=sa)
483 485 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
484 486 self._ui_settings = (
485 487 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
486 488 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
487 489
488 490 @property
489 491 @assert_repo_settings
490 492 def inherit_global_settings(self):
491 493 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
492 494 return setting.app_settings_value if setting else True
493 495
494 496 @inherit_global_settings.setter
495 497 @assert_repo_settings
496 498 def inherit_global_settings(self, value):
497 499 self.repo_settings.create_or_update_setting(
498 500 self.INHERIT_SETTINGS, value, type_='bool')
499 501
500 502 def get_global_svn_branch_patterns(self):
501 503 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
502 504
503 505 @assert_repo_settings
504 506 def get_repo_svn_branch_patterns(self):
505 507 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
506 508
507 509 def get_global_svn_tag_patterns(self):
508 510 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
509 511
510 512 @assert_repo_settings
511 513 def get_repo_svn_tag_patterns(self):
512 514 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
513 515
514 516 def get_global_settings(self):
515 517 return self._collect_all_settings(global_=True)
516 518
517 519 @assert_repo_settings
518 520 def get_repo_settings(self):
519 521 return self._collect_all_settings(global_=False)
520 522
521 523 @assert_repo_settings
522 524 def create_or_update_repo_settings(
523 525 self, data, inherit_global_settings=False):
524 526 from rhodecode.model.scm import ScmModel
525 527
526 528 self.inherit_global_settings = inherit_global_settings
527 529
528 530 repo = self.repo_settings.get_repo()
529 531 if not inherit_global_settings:
530 532 if repo.repo_type == 'svn':
531 533 self.create_repo_svn_settings(data)
532 534 else:
533 535 self.create_or_update_repo_hook_settings(data)
534 536 self.create_or_update_repo_pr_settings(data)
535 537
536 538 if repo.repo_type == 'hg':
537 539 self.create_or_update_repo_hg_settings(data)
538 540
539 541 if repo.repo_type == 'git':
540 542 self.create_or_update_repo_git_settings(data)
541 543
542 544 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
543 545
544 546 @assert_repo_settings
545 547 def create_or_update_repo_hook_settings(self, data):
546 548 for section, key in self.HOOKS_SETTINGS:
547 549 data_key = self._get_form_ui_key(section, key)
548 550 if data_key not in data:
549 551 raise ValueError(
550 552 'The given data does not contain {} key'.format(data_key))
551 553
552 554 active = data.get(data_key)
553 555 repo_setting = self.repo_settings.get_ui_by_section_and_key(
554 556 section, key)
555 557 if not repo_setting:
556 558 global_setting = self.global_settings.\
557 559 get_ui_by_section_and_key(section, key)
558 560 self.repo_settings.create_ui_section_value(
559 561 section, global_setting.ui_value, key=key, active=active)
560 562 else:
561 563 repo_setting.ui_active = active
562 564 Session().add(repo_setting)
563 565
564 566 def update_global_hook_settings(self, data):
565 567 for section, key in self.HOOKS_SETTINGS:
566 568 data_key = self._get_form_ui_key(section, key)
567 569 if data_key not in data:
568 570 raise ValueError(
569 571 'The given data does not contain {} key'.format(data_key))
570 572 active = data.get(data_key)
571 573 repo_setting = self.global_settings.get_ui_by_section_and_key(
572 574 section, key)
573 575 repo_setting.ui_active = active
574 576 Session().add(repo_setting)
575 577
576 578 @assert_repo_settings
577 579 def create_or_update_repo_pr_settings(self, data):
578 580 return self._create_or_update_general_settings(
579 581 self.repo_settings, data)
580 582
581 583 def create_or_update_global_pr_settings(self, data):
582 584 return self._create_or_update_general_settings(
583 585 self.global_settings, data)
584 586
585 587 @assert_repo_settings
586 588 def create_repo_svn_settings(self, data):
587 589 return self._create_svn_settings(self.repo_settings, data)
588 590
589 591 def _set_evolution(self, settings, is_enabled):
590 592 if is_enabled:
591 593 # if evolve is active set evolution=all
592 594
593 595 self._create_or_update_ui(
594 596 settings, *('experimental', 'evolution'), value='all',
595 597 active=True)
596 598 self._create_or_update_ui(
597 599 settings, *('experimental', 'evolution.exchange'), value='yes',
598 600 active=True)
599 601 # if evolve is active set topics server support
600 602 self._create_or_update_ui(
601 603 settings, *('extensions', 'topic'), value='',
602 604 active=True)
603 605
604 606 else:
605 607 self._create_or_update_ui(
606 608 settings, *('experimental', 'evolution'), value='',
607 609 active=False)
608 610 self._create_or_update_ui(
609 611 settings, *('experimental', 'evolution.exchange'), value='no',
610 612 active=False)
611 613 self._create_or_update_ui(
612 614 settings, *('extensions', 'topic'), value='',
613 615 active=False)
614 616
615 617 @assert_repo_settings
616 618 def create_or_update_repo_hg_settings(self, data):
617 619 largefiles, phases, evolve = \
618 620 self.HG_SETTINGS[:3]
619 621 largefiles_key, phases_key, evolve_key = \
620 622 self._get_settings_keys(self.HG_SETTINGS[:3], data)
621 623
622 624 self._create_or_update_ui(
623 625 self.repo_settings, *largefiles, value='',
624 626 active=data[largefiles_key])
625 627 self._create_or_update_ui(
626 628 self.repo_settings, *evolve, value='',
627 629 active=data[evolve_key])
628 630 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
629 631
630 632 self._create_or_update_ui(
631 633 self.repo_settings, *phases, value=safe_str(data[phases_key]))
632 634
633 635 def create_or_update_global_hg_settings(self, data):
634 636 largefiles, largefiles_store, phases, hgsubversion, evolve \
635 637 = self.GLOBAL_HG_SETTINGS[:5]
636 638 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
637 639 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
638 640
639 641 self._create_or_update_ui(
640 642 self.global_settings, *largefiles, value='',
641 643 active=data[largefiles_key])
642 644 self._create_or_update_ui(
643 645 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
644 646 self._create_or_update_ui(
645 647 self.global_settings, *phases, value=safe_str(data[phases_key]))
646 648 self._create_or_update_ui(
647 649 self.global_settings, *hgsubversion, active=data[subversion_key])
648 650 self._create_or_update_ui(
649 651 self.global_settings, *evolve, value='',
650 652 active=data[evolve_key])
651 653 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
652 654
653 655 def create_or_update_repo_git_settings(self, data):
654 656 # NOTE(marcink): # comma makes unpack work properly
655 657 lfs_enabled, \
656 658 = self.GIT_SETTINGS
657 659
658 660 lfs_enabled_key, \
659 661 = self._get_settings_keys(self.GIT_SETTINGS, data)
660 662
661 663 self._create_or_update_ui(
662 664 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
663 665 active=data[lfs_enabled_key])
664 666
665 667 def create_or_update_global_git_settings(self, data):
666 668 lfs_enabled, lfs_store_location \
667 669 = self.GLOBAL_GIT_SETTINGS
668 670 lfs_enabled_key, lfs_store_location_key \
669 671 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
670 672
671 673 self._create_or_update_ui(
672 674 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
673 675 active=data[lfs_enabled_key])
674 676 self._create_or_update_ui(
675 677 self.global_settings, *lfs_store_location,
676 678 value=data[lfs_store_location_key])
677 679
678 680 def create_or_update_global_svn_settings(self, data):
679 681 # branch/tags patterns
680 682 self._create_svn_settings(self.global_settings, data)
681 683
682 684 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
683 685 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
684 686 self.GLOBAL_SVN_SETTINGS, data)
685 687
686 688 self._create_or_update_ui(
687 689 self.global_settings, *http_requests_enabled,
688 690 value=safe_str(data[http_requests_enabled_key]))
689 691 self._create_or_update_ui(
690 692 self.global_settings, *http_server_url,
691 693 value=data[http_server_url_key])
692 694
693 695 def update_global_ssl_setting(self, value):
694 696 self._create_or_update_ui(
695 697 self.global_settings, *self.SSL_SETTING, value=value)
696 698
697 699 def update_global_path_setting(self, value):
698 700 self._create_or_update_ui(
699 701 self.global_settings, *self.PATH_SETTING, value=value)
700 702
701 703 @assert_repo_settings
702 704 def delete_repo_svn_pattern(self, id_):
703 705 ui = self.repo_settings.UiDbModel.get(id_)
704 706 if ui and ui.repository.repo_name == self.repo_settings.repo:
705 707 # only delete if it's the same repo as initialized settings
706 708 self.repo_settings.delete_ui(id_)
707 709 else:
708 710 # raise error as if we wouldn't find this option
709 711 self.repo_settings.delete_ui(-1)
710 712
711 713 def delete_global_svn_pattern(self, id_):
712 714 self.global_settings.delete_ui(id_)
713 715
714 716 @assert_repo_settings
715 717 def get_repo_ui_settings(self, section=None, key=None):
716 718 global_uis = self.global_settings.get_ui(section, key)
717 719 repo_uis = self.repo_settings.get_ui(section, key)
718 720
719 721 filtered_repo_uis = self._filter_ui_settings(repo_uis)
720 722 filtered_repo_uis_keys = [
721 723 (s.section, s.key) for s in filtered_repo_uis]
722 724
723 725 def _is_global_ui_filtered(ui):
724 726 return (
725 727 (ui.section, ui.key) in filtered_repo_uis_keys
726 728 or ui.section in self._svn_sections)
727 729
728 730 filtered_global_uis = [
729 731 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
730 732
731 733 return filtered_global_uis + filtered_repo_uis
732 734
733 735 def get_global_ui_settings(self, section=None, key=None):
734 736 return self.global_settings.get_ui(section, key)
735 737
736 738 def get_ui_settings_as_config_obj(self, section=None, key=None):
737 739 config = base.Config()
738 740
739 741 ui_settings = self.get_ui_settings(section=section, key=key)
740 742
741 743 for entry in ui_settings:
742 744 config.set(entry.section, entry.key, entry.value)
743 745
744 746 return config
745 747
746 748 def get_ui_settings(self, section=None, key=None):
747 749 if not self.repo_settings or self.inherit_global_settings:
748 750 return self.get_global_ui_settings(section, key)
749 751 else:
750 752 return self.get_repo_ui_settings(section, key)
751 753
752 754 def get_svn_patterns(self, section=None):
753 755 if not self.repo_settings:
754 756 return self.get_global_ui_settings(section)
755 757 else:
756 758 return self.get_repo_ui_settings(section)
757 759
758 760 @assert_repo_settings
759 761 def get_repo_general_settings(self):
760 762 global_settings = self.global_settings.get_all_settings()
761 763 repo_settings = self.repo_settings.get_all_settings()
762 764 filtered_repo_settings = self._filter_general_settings(repo_settings)
763 765 global_settings.update(filtered_repo_settings)
764 766 return global_settings
765 767
766 768 def get_global_general_settings(self):
767 769 return self.global_settings.get_all_settings()
768 770
769 771 def get_general_settings(self):
770 772 if not self.repo_settings or self.inherit_global_settings:
771 773 return self.get_global_general_settings()
772 774 else:
773 775 return self.get_repo_general_settings()
774 776
775 777 def get_repos_location(self):
776 778 return self.global_settings.get_ui_by_key('/').ui_value
777 779
778 780 def _filter_ui_settings(self, settings):
779 781 filtered_settings = [
780 782 s for s in settings if self._should_keep_setting(s)]
781 783 return filtered_settings
782 784
783 785 def _should_keep_setting(self, setting):
784 786 keep = (
785 787 (setting.section, setting.key) in self._ui_settings or
786 788 setting.section in self._svn_sections)
787 789 return keep
788 790
789 791 def _filter_general_settings(self, settings):
790 792 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
791 793 return {
792 794 k: settings[k]
793 795 for k in settings if k in keys}
794 796
795 797 def _collect_all_settings(self, global_=False):
796 798 settings = self.global_settings if global_ else self.repo_settings
797 799 result = {}
798 800
799 801 for section, key in self._ui_settings:
800 802 ui = settings.get_ui_by_section_and_key(section, key)
801 803 result_key = self._get_form_ui_key(section, key)
802 804
803 805 if ui:
804 806 if section in ('hooks', 'extensions'):
805 807 result[result_key] = ui.ui_active
806 808 elif result_key in ['vcs_git_lfs_enabled']:
807 809 result[result_key] = ui.ui_active
808 810 else:
809 811 result[result_key] = ui.ui_value
810 812
811 813 for name in self.GENERAL_SETTINGS:
812 814 setting = settings.get_setting_by_name(name)
813 815 if setting:
814 816 result_key = 'rhodecode_{}'.format(name)
815 817 result[result_key] = setting.app_settings_value
816 818
817 819 return result
818 820
819 821 def _get_form_ui_key(self, section, key):
820 822 return '{section}_{key}'.format(
821 823 section=section, key=key.replace('.', '_'))
822 824
823 825 def _create_or_update_ui(
824 826 self, settings, section, key, value=None, active=None):
825 827 ui = settings.get_ui_by_section_and_key(section, key)
826 828 if not ui:
827 829 active = True if active is None else active
828 830 settings.create_ui_section_value(
829 831 section, value, key=key, active=active)
830 832 else:
831 833 if active is not None:
832 834 ui.ui_active = active
833 835 if value is not None:
834 836 ui.ui_value = value
835 837 Session().add(ui)
836 838
837 839 def _create_svn_settings(self, settings, data):
838 840 svn_settings = {
839 841 'new_svn_branch': self.SVN_BRANCH_SECTION,
840 842 'new_svn_tag': self.SVN_TAG_SECTION
841 843 }
842 844 for key in svn_settings:
843 845 if data.get(key):
844 846 settings.create_ui_section_value(svn_settings[key], data[key])
845 847
846 848 def _create_or_update_general_settings(self, settings, data):
847 849 for name in self.GENERAL_SETTINGS:
848 850 data_key = 'rhodecode_{}'.format(name)
849 851 if data_key not in data:
850 852 raise ValueError(
851 853 'The given data does not contain {} key'.format(data_key))
852 854 setting = settings.create_or_update_setting(
853 855 name, data[data_key], 'bool')
854 856 Session().add(setting)
855 857
856 858 def _get_settings_keys(self, settings, data):
857 859 data_keys = [self._get_form_ui_key(*s) for s in settings]
858 860 for data_key in data_keys:
859 861 if data_key not in data:
860 862 raise ValueError(
861 863 'The given data does not contain {} key'.format(data_key))
862 864 return data_keys
863 865
864 866 def create_largeobjects_dirs_if_needed(self, repo_store_path):
865 867 """
866 868 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
867 869 does a repository scan if enabled in the settings.
868 870 """
869 871
870 872 from rhodecode.lib.vcs.backends.hg import largefiles_store
871 873 from rhodecode.lib.vcs.backends.git import lfs_store
872 874
873 875 paths = [
874 876 largefiles_store(repo_store_path),
875 877 lfs_store(repo_store_path)]
876 878
877 879 for path in paths:
878 880 if os.path.isdir(path):
879 881 continue
880 882 if os.path.isfile(path):
881 883 continue
882 884 # not a file nor dir, we try to create it
883 885 try:
884 886 os.makedirs(path)
885 887 except Exception:
886 888 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now