##// END OF EJS Templates
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
ilin.s -
r5314:585ee450 default
parent child Browse files
Show More
@@ -0,0 +1,55 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
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
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
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/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 import pytest
21
22 from rhodecode.api.tests.utils import (
23 build_data, api_call)
24
25
26 @pytest.mark.usefixtures("app")
27 class TestServiceApi:
28
29 def test_service_api_with_wrong_secret(self):
30 id, payload = build_data("wrong_api_key", 'service_get_repo_name_by_id')
31 response = api_call(self.app, payload)
32
33 assert 'Invalid API KEY' == response.json['error']
34
35 def test_service_api_with_legit_secret(self):
36 id, payload = build_data(self.app.app.config.get_settings()['app.service_api.token'],
37 'service_get_repo_name_by_id', repo_id='1')
38 response = api_call(self.app, payload)
39 assert not response.json['error']
40
41 def test_service_api_not_a_part_of_public_api_suggestions(self):
42 id, payload = build_data("secret", 'some_random_guess_method')
43 response = api_call(self.app, payload)
44 assert 'service_' not in response.json['error']
45
46 def test_service_get_data_for_ssh_wrapper_output(self):
47 id, payload = build_data(
48 self.app.app.config.get_settings()['app.service_api.token'],
49 'service_get_data_for_ssh_wrapper',
50 user_id=1,
51 repo_name='vcs_test_git')
52 response = api_call(self.app, payload)
53
54 assert ['branch_permissions', 'repo_permissions', 'repos_path', 'user_id', 'username']\
55 == list(response.json['result'].keys())
@@ -0,0 +1,125 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20 import datetime
21 from collections import defaultdict
22
23 from sqlalchemy import Table
24 from rhodecode.api import jsonrpc_method, SERVICE_API_IDENTIFIER
25
26
27 log = logging.getLogger(__name__)
28
29
30 @jsonrpc_method()
31 def service_get_data_for_ssh_wrapper(request, apiuser, user_id, repo_name, key_id=None):
32 from rhodecode.model.db import User
33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.meta import raw_query_executor, Base
35
36 if key_id:
37 table = Table('user_ssh_keys', Base.metadata, autoload=False)
38 atime = datetime.datetime.utcnow()
39 stmt = (
40 table.update()
41 .where(table.c.ssh_key_id == key_id)
42 .values(accessed_on=atime)
43 )
44
45 res_count = None
46 with raw_query_executor() as session:
47 result = session.execute(stmt)
48 if result.rowcount:
49 res_count = result.rowcount
50
51 if res_count:
52 log.debug(f'Update key id:{key_id} access time')
53 db_user = User.get(user_id)
54 if not db_user:
55 return None
56 auth_user = db_user.AuthUser()
57
58 return {
59 'user_id': db_user.user_id,
60 'username': db_user.username,
61 'repo_permissions': auth_user.permissions['repositories'],
62 "branch_permissions": auth_user.get_branch_permissions(repo_name),
63 "repos_path": ScmModel().repos_path
64 }
65
66
67 @jsonrpc_method()
68 def service_get_repo_name_by_id(request, apiuser, repo_id):
69 from rhodecode.model.repo import RepoModel
70 by_id_match = RepoModel().get_repo_by_id(repo_id)
71 if by_id_match:
72 repo_name = by_id_match.repo_name
73 return {
74 'repo_name': repo_name
75 }
76 return None
77
78
79 @jsonrpc_method()
80 def service_mark_for_invalidation(request, apiuser, repo_name):
81 from rhodecode.model.scm import ScmModel
82 ScmModel().mark_for_invalidation(repo_name)
83 return {'msg': "Applied"}
84
85
86 @jsonrpc_method()
87 def service_config_to_hgrc(request, apiuser, cli_flags, repo_name):
88 from rhodecode.model.db import RhodeCodeUi
89 from rhodecode.model.settings import VcsSettingsModel
90
91 ui_sections = defaultdict(list)
92 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
93
94 default_hooks = [
95 ('pretxnchangegroup.ssh_auth', 'python:vcsserver.hooks.pre_push_ssh_auth'),
96 ('pretxnchangegroup.ssh', 'python:vcsserver.hooks.pre_push_ssh'),
97 ('changegroup.ssh', 'python:vcsserver.hooks.post_push_ssh'),
98
99 ('preoutgoing.ssh', 'python:vcsserver.hooks.pre_pull_ssh'),
100 ('outgoing.ssh', 'python:vcsserver.hooks.post_pull_ssh'),
101 ]
102
103 for k, v in default_hooks:
104 ui_sections['hooks'].append((k, v))
105
106 for entry in ui:
107 if not entry.active:
108 continue
109 sec = entry.section
110 key = entry.key
111
112 if sec in cli_flags:
113 # we want only custom hooks, so we skip builtins
114 if sec == 'hooks' and key in RhodeCodeUi.HOOKS_BUILTIN:
115 continue
116
117 ui_sections[sec].append([key, entry.value])
118
119 flags = []
120 for _sec, key_val in ui_sections.items():
121 flags.append(' ')
122 flags.append(f'[{_sec}]')
123 for key, val in key_val:
124 flags.append(f'{key}= {val}')
125 return {'flags': flags}
@@ -0,0 +1,72 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import os
20 import sys
21 import time
22 import logging
23
24 import click
25
26 from pyramid.paster import setup_logging
27
28 from rhodecode.lib.statsd_client import StatsdClient
29 from .backends import SshWrapperStandalone
30 from .ssh_wrapper_v1 import setup_custom_logging
31
32 log = logging.getLogger(__name__)
33
34
35 @click.command()
36 @click.argument('ini_path', type=click.Path(exists=True))
37 @click.option(
38 '--mode', '-m', required=False, default='auto',
39 type=click.Choice(['auto', 'vcs', 'git', 'hg', 'svn', 'test']),
40 help='mode of operation')
41 @click.option('--user', help='Username for which the command will be executed')
42 @click.option('--user-id', help='User ID for which the command will be executed')
43 @click.option('--key-id', help='ID of the key from the database')
44 @click.option('--shell', '-s', is_flag=True, help='Allow Shell')
45 @click.option('--debug', is_flag=True, help='Enabled detailed output logging')
46 def main(ini_path, mode, user, user_id, key_id, shell, debug):
47 setup_custom_logging(ini_path, debug)
48
49 command = os.environ.get('SSH_ORIGINAL_COMMAND', '')
50 if not command and mode not in ['test']:
51 raise ValueError(
52 'Unable to fetch SSH_ORIGINAL_COMMAND from environment.'
53 'Please make sure this is set and available during execution '
54 'of this script.')
55 connection_info = os.environ.get('SSH_CONNECTION', '')
56 time_start = time.time()
57 env = {'RC_CMD_SSH_WRAPPER': '1'}
58 statsd = StatsdClient.statsd
59 try:
60 ssh_wrapper = SshWrapperStandalone(
61 command, connection_info, mode,
62 user, user_id, key_id, shell, ini_path, env)
63 except Exception:
64 log.exception('Failed to execute SshWrapper')
65 sys.exit(-5)
66 return_code = ssh_wrapper.wrap()
67 operation_took = time.time() - time_start
68 if statsd:
69 operation_took_ms = round(1000.0 * operation_took)
70 statsd.timing("rhodecode_ssh_wrapper_timing.histogram", operation_took_ms,
71 use_decimals=False)
72 sys.exit(return_code)
@@ -153,6 +153,12 b' startup.import_repos = false'
153 ; SSH calls. Set this for events to receive proper url for SSH calls.
153 ; SSH calls. Set this for events to receive proper url for SSH calls.
154 app.base_url = http://rhodecode.local
154 app.base_url = http://rhodecode.local
155
155
156 ; Host at which the Service API is running.
157 app.service_api.host = http://rhodecode.local:10020
158
159 ; Secret for Service API authentication.
160 app.service_api.token =
161
156 ; Unique application ID. Should be a random unique string for security.
162 ; Unique application ID. Should be a random unique string for security.
157 app_instance_uuid = rc-production
163 app_instance_uuid = rc-production
158
164
@@ -104,6 +104,12 b' startup.import_repos = false'
104 ; SSH calls. Set this for events to receive proper url for SSH calls.
104 ; SSH calls. Set this for events to receive proper url for SSH calls.
105 app.base_url = http://rhodecode.local
105 app.base_url = http://rhodecode.local
106
106
107 ; Host at which the Service API is running.
108 app.service_api.host= http://rhodecode.local:10020
109
110 ; Secret for Service API authentication.
111 app.service_api.token =
112
107 ; Unique application ID. Should be a random unique string for security.
113 ; Unique application ID. Should be a random unique string for security.
108 app_instance_uuid = rc-production
114 app_instance_uuid = rc-production
109
115
@@ -46,6 +46,7 b' log = logging.getLogger(__name__)'
46
46
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_URL = '/_admin/apiv2'
48 DEFAULT_URL = '/_admin/apiv2'
49 SERVICE_API_IDENTIFIER = 'service_'
49
50
50
51
51 def find_methods(jsonrpc_methods, pattern):
52 def find_methods(jsonrpc_methods, pattern):
@@ -54,7 +55,9 b' def find_methods(jsonrpc_methods, patter'
54 pattern = [pattern]
55 pattern = [pattern]
55
56
56 for single_pattern in pattern:
57 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
58 for method_name, method in filter(
59 lambda x: not x[0].startswith(SERVICE_API_IDENTIFIER), jsonrpc_methods.items()
60 ):
58 if fnmatch.fnmatch(method_name, single_pattern):
61 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
62 matches[method_name] = method
60 return matches
63 return matches
@@ -190,43 +193,48 b' def request_view(request):'
190 # check if we can find this session using api_key, get_by_auth_token
193 # check if we can find this session using api_key, get_by_auth_token
191 # search not expired tokens only
194 # search not expired tokens only
192 try:
195 try:
193 api_user = User.get_by_auth_token(request.rpc_api_key)
196 if not request.rpc_method.startswith(SERVICE_API_IDENTIFIER):
197 api_user = User.get_by_auth_token(request.rpc_api_key)
194
198
195 if api_user is None:
199 if api_user is None:
196 return jsonrpc_error(
200 return jsonrpc_error(
197 request, retid=request.rpc_id, message='Invalid API KEY')
201 request, retid=request.rpc_id, message='Invalid API KEY')
198
202
199 if not api_user.active:
203 if not api_user.active:
200 return jsonrpc_error(
204 return jsonrpc_error(
201 request, retid=request.rpc_id,
205 request, retid=request.rpc_id,
202 message='Request from this user not allowed')
206 message='Request from this user not allowed')
203
207
204 # check if we are allowed to use this IP
208 # check if we are allowed to use this IP
205 auth_u = AuthUser(
209 auth_u = AuthUser(
206 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
210 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
207 if not auth_u.ip_allowed:
211 if not auth_u.ip_allowed:
208 return jsonrpc_error(
212 return jsonrpc_error(
209 request, retid=request.rpc_id,
213 request, retid=request.rpc_id,
210 message='Request from IP:{} not allowed'.format(
214 message='Request from IP:{} not allowed'.format(
211 request.rpc_ip_addr))
215 request.rpc_ip_addr))
212 else:
216 else:
213 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
217 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
218
219 # register our auth-user
220 request.rpc_user = auth_u
221 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
214
222
215 # register our auth-user
223 # now check if token is valid for API
216 request.rpc_user = auth_u
224 auth_token = request.rpc_api_key
217 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
225 token_match = api_user.authenticate_by_token(
226 auth_token, roles=[UserApiKeys.ROLE_API])
227 invalid_token = not token_match
218
228
219 # now check if token is valid for API
229 log.debug('Checking if API KEY is valid with proper role')
220 auth_token = request.rpc_api_key
230 if invalid_token:
221 token_match = api_user.authenticate_by_token(
231 return jsonrpc_error(
222 auth_token, roles=[UserApiKeys.ROLE_API])
232 request, retid=request.rpc_id,
223 invalid_token = not token_match
233 message='API KEY invalid or, has bad role for an API call')
224
234 else:
225 log.debug('Checking if API KEY is valid with proper role')
235 auth_u = 'service'
226 if invalid_token:
236 if request.rpc_api_key != request.registry.settings['app.service_api.token']:
227 return jsonrpc_error(
237 raise Exception("Provided service secret is not recognized!")
228 request, retid=request.rpc_id,
229 message='API KEY invalid or, has bad role for an API call')
230
238
231 except Exception:
239 except Exception:
232 log.exception('Error on API AUTH')
240 log.exception('Error on API AUTH')
@@ -290,7 +298,8 b' def request_view(request):'
290 })
298 })
291
299
292 # register some common functions for usage
300 # register some common functions for usage
293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
301 rpc_user = request.rpc_user.user_id if hasattr(request, 'rpc_user') else None
302 attach_context_attributes(TemplateArgs(), request, rpc_user)
294
303
295 statsd = request.registry.statsd
304 statsd = request.registry.statsd
296
305
@@ -23,6 +23,7 b' import datetime'
23 import configparser
23 import configparser
24 from sqlalchemy import Table
24 from sqlalchemy import Table
25
25
26 from rhodecode.lib.utils import call_service_api
26 from rhodecode.lib.utils2 import AttributeDict
27 from rhodecode.lib.utils2 import AttributeDict
27 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
28
29
@@ -261,3 +262,131 b' class SshWrapper(object):'
261 exit_code = -1
262 exit_code = -1
262
263
263 return exit_code
264 return exit_code
265
266
267 class SshWrapperStandalone(SshWrapper):
268 """
269 New version of SshWrapper designed to be depended only on service API
270 """
271 repos_path = None
272
273 @staticmethod
274 def parse_user_related_data(user_data):
275 user = AttributeDict()
276 user.user_id = user_data['user_id']
277 user.username = user_data['username']
278 user.repo_permissions = user_data['repo_permissions']
279 user.branch_permissions = user_data['branch_permissions']
280 return user
281
282 def wrap(self):
283 mode = self.mode
284 username = self.username
285 user_id = self.user_id
286 shell = self.shell
287
288 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
289
290 log.debug(
291 'Mode: `%s` User: `name:%s : id:%s` Shell: `%s` SSH Command: `\"%s\"` '
292 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
293 mode, username, user_id, shell, self.command,
294 scm_detected, scm_mode, scm_repo)
295
296 log.debug('SSH Connection info %s', self.get_connection_info())
297
298 if shell and self.command is None:
299 log.info('Dropping to shell, no command given and shell is allowed')
300 os.execl('/bin/bash', '-l')
301 exit_code = 1
302
303 elif scm_detected:
304 data = call_service_api(self.ini_path, {
305 "method": "service_get_data_for_ssh_wrapper",
306 "args": {"user_id": user_id, "repo_name": scm_repo, "key_id": self.key_id}
307 })
308 user = self.parse_user_related_data(data)
309 if not user:
310 log.warning('User with id %s not found', user_id)
311 exit_code = -1
312 return exit_code
313 self.repos_path = data['repos_path']
314 permissions = user.repo_permissions
315 repo_branch_permissions = user.branch_permissions
316 try:
317 exit_code, is_updated = self.serve(
318 scm_detected, scm_repo, scm_mode, user, permissions,
319 repo_branch_permissions)
320 except Exception:
321 log.exception('Error occurred during execution of SshWrapper')
322 exit_code = -1
323
324 elif self.command is None and shell is False:
325 log.error('No Command given.')
326 exit_code = -1
327
328 else:
329 log.error('Unhandled Command: "%s" Aborting.', self.command)
330 exit_code = -1
331
332 return exit_code
333
334 def maybe_translate_repo_uid(self, repo_name):
335 _org_name = repo_name
336 if _org_name.startswith('_'):
337 _org_name = _org_name.split('/', 1)[0]
338
339 if repo_name.startswith('_'):
340 org_repo_name = repo_name
341 log.debug('translating UID repo %s', org_repo_name)
342 by_id_match = call_service_api(self.ini_path, {
343 'method': 'service_get_repo_name_by_id',
344 "args": {"repo_id": repo_name}
345 })
346 if by_id_match:
347 repo_name = by_id_match['repo_name']
348 log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
349
350 return repo_name, _org_name
351
352 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
353 store = self.repos_path
354
355 check_branch_perms = False
356 detect_force_push = False
357
358 if branch_permissions:
359 check_branch_perms = True
360 detect_force_push = True
361
362 log.debug(
363 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
364 vcs, mode, repo, check_branch_perms)
365
366 # detect if we have to check branch permissions
367 extras = {
368 'detect_force_push': detect_force_push,
369 'check_branch_perms': check_branch_perms,
370 'config': self.ini_path
371 }
372
373 match vcs:
374 case 'hg':
375 server = MercurialServer(
376 store=store, ini_path=self.ini_path,
377 repo_name=repo, user=user,
378 user_permissions=permissions, config=self.config, env=self.env)
379 case 'git':
380 server = GitServer(
381 store=store, ini_path=self.ini_path,
382 repo_name=repo, repo_mode=mode, user=user,
383 user_permissions=permissions, config=self.config, env=self.env)
384 case 'svn':
385 server = SubversionServer(
386 store=store, ini_path=self.ini_path,
387 repo_name=None, user=user,
388 user_permissions=permissions, config=self.config, env=self.env)
389 case _:
390 raise Exception(f'Unrecognised VCS: {vcs}')
391 self.server_impl = server
392 return server.run(tunnel_extras=extras)
@@ -23,6 +23,7 b' import logging'
23 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
23 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
24 from rhodecode.lib.ext_json import sjson as json
24 from rhodecode.lib.ext_json import sjson as json
25 from rhodecode.lib.vcs.conf import settings as vcs_settings
25 from rhodecode.lib.vcs.conf import settings as vcs_settings
26 from rhodecode.lib.utils import call_service_api
26 from rhodecode.model.scm import ScmModel
27 from rhodecode.model.scm import ScmModel
27
28
28 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
@@ -47,6 +48,7 b' class VcsServer(object):'
47 self.repo_mode = None
48 self.repo_mode = None
48 self.store = ''
49 self.store = ''
49 self.ini_path = ''
50 self.ini_path = ''
51 self.hooks_protocol = None
50
52
51 def _invalidate_cache(self, repo_name):
53 def _invalidate_cache(self, repo_name):
52 """
54 """
@@ -54,7 +56,15 b' class VcsServer(object):'
54
56
55 :param repo_name: full repo name, also a cache key
57 :param repo_name: full repo name, also a cache key
56 """
58 """
57 ScmModel().mark_for_invalidation(repo_name)
59 # Todo: Leave only "celery" case after transition.
60 match self.hooks_protocol:
61 case 'http':
62 ScmModel().mark_for_invalidation(repo_name)
63 case 'celery':
64 call_service_api(self.ini_path, {
65 "method": "service_mark_for_invalidation",
66 "args": {"repo_name": repo_name}
67 })
58
68
59 def has_write_perm(self):
69 def has_write_perm(self):
60 permission = self.user_permissions.get(self.repo_name)
70 permission = self.user_permissions.get(self.repo_name)
@@ -65,30 +75,31 b' class VcsServer(object):'
65
75
66 def _check_permissions(self, action):
76 def _check_permissions(self, action):
67 permission = self.user_permissions.get(self.repo_name)
77 permission = self.user_permissions.get(self.repo_name)
78 user_info = f'{self.user["user_id"]}:{self.user["username"]}'
68 log.debug('permission for %s on %s are: %s',
79 log.debug('permission for %s on %s are: %s',
69 self.user, self.repo_name, permission)
80 user_info, self.repo_name, permission)
70
81
71 if not permission:
82 if not permission:
72 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
83 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
73 self.user, self.repo_name)
84 user_info, self.repo_name)
74 return -2
85 return -2
75
86
76 if action == 'pull':
87 if action == 'pull':
77 if permission in self.read_perms:
88 if permission in self.read_perms:
78 log.info(
89 log.info(
79 'READ Permissions for User "%s" detected to repo "%s"!',
90 'READ Permissions for User "%s" detected to repo "%s"!',
80 self.user, self.repo_name)
91 user_info, self.repo_name)
81 return 0
92 return 0
82 else:
93 else:
83 if permission in self.write_perms:
94 if permission in self.write_perms:
84 log.info(
95 log.info(
85 'WRITE, or Higher Permissions for User "%s" detected to repo "%s"!',
96 'WRITE, or Higher Permissions for User "%s" detected to repo "%s"!',
86 self.user, self.repo_name)
97 user_info, self.repo_name)
87 return 0
98 return 0
88
99
89 log.error('Cannot properly fetch or verify user `%s` permissions. '
100 log.error('Cannot properly fetch or verify user `%s` permissions. '
90 'Permissions: %s, vcs action: %s',
101 'Permissions: %s, vcs action: %s',
91 self.user, permission, action)
102 user_info, permission, action)
92 return -2
103 return -2
93
104
94 def update_environment(self, action, extras=None):
105 def update_environment(self, action, extras=None):
@@ -134,9 +145,10 b' class VcsServer(object):'
134 if exit_code:
145 if exit_code:
135 return exit_code, False
146 return exit_code, False
136
147
137 req = self.env['request']
148 req = self.env.get('request')
138 server_url = req.host_url + req.script_name
149 if req:
139 extras['server_url'] = server_url
150 server_url = req.host_url + req.script_name
151 extras['server_url'] = server_url
140
152
141 log.debug('Using %s binaries from path %s', self.backend, self._path)
153 log.debug('Using %s binaries from path %s', self.backend, self._path)
142 exit_code = self.tunnel.run(extras)
154 exit_code = self.tunnel.run(extras)
@@ -144,12 +156,13 b' class VcsServer(object):'
144 return exit_code, action == "push"
156 return exit_code, action == "push"
145
157
146 def run(self, tunnel_extras=None):
158 def run(self, tunnel_extras=None):
159 self.hooks_protocol = self.config.get('app:main', 'vcs.hooks.protocol')
147 tunnel_extras = tunnel_extras or {}
160 tunnel_extras = tunnel_extras or {}
148 extras = {}
161 extras = {}
149 extras.update(tunnel_extras)
162 extras.update(tunnel_extras)
150
163
151 callback_daemon, extras = prepare_callback_daemon(
164 callback_daemon, extras = prepare_callback_daemon(
152 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
165 extras, protocol=self.hooks_protocol,
153 host=vcs_settings.HOOKS_HOST)
166 host=vcs_settings.HOOKS_HOST)
154
167
155 with callback_daemon:
168 with callback_daemon:
@@ -23,6 +23,7 b' import tempfile'
23 import textwrap
23 import textwrap
24 import collections
24 import collections
25 from .base import VcsServer
25 from .base import VcsServer
26 from rhodecode.lib.utils import call_service_api
26 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.settings import VcsSettingsModel
28 from rhodecode.model.settings import VcsSettingsModel
28
29
@@ -108,6 +109,14 b' class MercurialServer(VcsServer):'
108 self.tunnel = MercurialTunnelWrapper(server=self)
109 self.tunnel = MercurialTunnelWrapper(server=self)
109
110
110 def config_to_hgrc(self, repo_name):
111 def config_to_hgrc(self, repo_name):
112 # Todo: once transition is done only call to service api should exist
113 if self.hooks_protocol == 'celery':
114 data = call_service_api(self.ini_path, {
115 "method": "service_config_to_hgrc",
116 "args": {"cli_flags": self.cli_flags, "repo_name": repo_name}
117 })
118 return data['flags']
119
111 ui_sections = collections.defaultdict(list)
120 ui_sections = collections.defaultdict(list)
112 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
121 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
113
122
1 NO CONTENT: file renamed from rhodecode/apps/ssh_support/lib/ssh_wrapper.py to rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
NO CONTENT: file renamed from rhodecode/apps/ssh_support/lib/ssh_wrapper.py to rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
@@ -20,7 +20,7 b' import os'
20 import pytest
20 import pytest
21 import configparser
21 import configparser
22
22
23 from rhodecode.apps.ssh_support.lib.ssh_wrapper import SshWrapper
23 from rhodecode.apps.ssh_support.lib.ssh_wrapper_v1 import SshWrapper
24 from rhodecode.lib.utils2 import AttributeDict
24 from rhodecode.lib.utils2 import AttributeDict
25
25
26
26
@@ -34,6 +34,7 b' import tarfile'
34 import warnings
34 import warnings
35 from functools import wraps
35 from functools import wraps
36 from os.path import join as jn
36 from os.path import join as jn
37 from configparser import NoOptionError
37
38
38 import paste
39 import paste
39 import pkg_resources
40 import pkg_resources
@@ -52,6 +53,9 b' from rhodecode.model import meta'
52 from rhodecode.model.db import (
53 from rhodecode.model.db import (
53 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56 from rhodecode.lib.pyramid_utils import get_config
57 from rhodecode.lib.vcs import CurlSession
58 from rhodecode.lib.vcs.exceptions import ImproperlyConfiguredError
55
59
56
60
57 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
@@ -821,3 +825,27 b' def send_test_email(recipients, email_bo'
821 email_body = email_body_plaintext = email_body
825 email_body = email_body_plaintext = email_body
822 subject = f'SUBJECT FROM: {socket.gethostname()}'
826 subject = f'SUBJECT FROM: {socket.gethostname()}'
823 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
827 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
828
829
830 def call_service_api(ini_path, payload):
831 config = get_config(ini_path)
832 try:
833 host = config.get('app:main', 'app.service_api.host')
834 except NoOptionError:
835 raise ImproperlyConfiguredError(
836 "app.service_api.host is missing. "
837 "Please ensure that app.service_api.host and app.service_api.token are "
838 "defined inside of .ini configuration file."
839 )
840 api_url = config.get('app:main', 'rhodecode.api.url')
841 payload.update({
842 'id': 'service',
843 'auth_token': config.get('app:main', 'app.service_api.token')
844 })
845
846 response = CurlSession().post(f'{host}{api_url}', json.dumps(payload))
847
848 if response.status_code != 200:
849 raise Exception("Service API responded with error")
850
851 return json.loads(response.content)['result']
@@ -146,6 +146,10 b' class CommandError(VCSError):'
146 pass
146 pass
147
147
148
148
149 class ImproperlyConfiguredError(Exception):
150 pass
151
152
149 class UnhandledException(VCSError):
153 class UnhandledException(VCSError):
150 """
154 """
151 Signals that something unexpected went wrong.
155 Signals that something unexpected went wrong.
@@ -110,6 +110,7 b' def ini_config(request, tmpdir_factory, '
110 'vcs.scm_app_implementation': 'http',
110 'vcs.scm_app_implementation': 'http',
111 'vcs.hooks.protocol': 'http',
111 'vcs.hooks.protocol': 'http',
112 'vcs.hooks.host': '*',
112 'vcs.hooks.host': '*',
113 'app.service_api.token': 'service_secret_token',
113 }},
114 }},
114
115
115 {'handler_console': {
116 {'handler_console': {
@@ -196,7 +196,8 b' setup('
196 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main',
196 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main',
197 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',
197 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',
198 'rc-add-artifact=rhodecode.lib.rc_commands.add_artifact:main',
198 'rc-add-artifact=rhodecode.lib.rc_commands.add_artifact:main',
199 'rc-ssh-wrapper=rhodecode.apps.ssh_support.lib.ssh_wrapper:main',
199 'rc-ssh-wrapper=rhodecode.apps.ssh_support.lib.ssh_wrapper_v1:main',
200 'rc-ssh-wrapper-v2=rhodecode.apps.ssh_support.lib.ssh_wrapper_v2:main',
200 ],
201 ],
201 'beaker.backends': [
202 'beaker.backends': [
202 'memorylru_base=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase',
203 'memorylru_base=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase',
General Comments 0
You need to be logged in to leave comments. Login now