##// 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 153 ; SSH calls. Set this for events to receive proper url for SSH calls.
154 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 162 ; Unique application ID. Should be a random unique string for security.
157 163 app_instance_uuid = rc-production
158 164
@@ -104,6 +104,12 b' startup.import_repos = false'
104 104 ; SSH calls. Set this for events to receive proper url for SSH calls.
105 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 113 ; Unique application ID. Should be a random unique string for security.
108 114 app_instance_uuid = rc-production
109 115
@@ -46,6 +46,7 b' log = logging.getLogger(__name__)'
46 46
47 47 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 48 DEFAULT_URL = '/_admin/apiv2'
49 SERVICE_API_IDENTIFIER = 'service_'
49 50
50 51
51 52 def find_methods(jsonrpc_methods, pattern):
@@ -54,7 +55,9 b' def find_methods(jsonrpc_methods, patter'
54 55 pattern = [pattern]
55 56
56 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 61 if fnmatch.fnmatch(method_name, single_pattern):
59 62 matches[method_name] = method
60 63 return matches
@@ -190,43 +193,48 b' def request_view(request):'
190 193 # check if we can find this session using api_key, get_by_auth_token
191 194 # search not expired tokens only
192 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:
196 return jsonrpc_error(
197 request, retid=request.rpc_id, message='Invalid API KEY')
199 if api_user is None:
200 return jsonrpc_error(
201 request, retid=request.rpc_id, message='Invalid API KEY')
198 202
199 if not api_user.active:
200 return jsonrpc_error(
201 request, retid=request.rpc_id,
202 message='Request from this user not allowed')
203 if not api_user.active:
204 return jsonrpc_error(
205 request, retid=request.rpc_id,
206 message='Request from this user not allowed')
203 207
204 # check if we are allowed to use this IP
205 auth_u = AuthUser(
206 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
207 if not auth_u.ip_allowed:
208 return jsonrpc_error(
209 request, retid=request.rpc_id,
210 message='Request from IP:{} not allowed'.format(
211 request.rpc_ip_addr))
212 else:
213 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
208 # check if we are allowed to use this IP
209 auth_u = AuthUser(
210 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
211 if not auth_u.ip_allowed:
212 return jsonrpc_error(
213 request, retid=request.rpc_id,
214 message='Request from IP:{} not allowed'.format(
215 request.rpc_ip_addr))
216 else:
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
216 request.rpc_user = auth_u
217 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
223 # now check if token is valid for API
224 auth_token = request.rpc_api_key
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
220 auth_token = request.rpc_api_key
221 token_match = api_user.authenticate_by_token(
222 auth_token, roles=[UserApiKeys.ROLE_API])
223 invalid_token = not token_match
224
225 log.debug('Checking if API KEY is valid with proper role')
226 if invalid_token:
227 return jsonrpc_error(
228 request, retid=request.rpc_id,
229 message='API KEY invalid or, has bad role for an API call')
229 log.debug('Checking if API KEY is valid with proper role')
230 if invalid_token:
231 return jsonrpc_error(
232 request, retid=request.rpc_id,
233 message='API KEY invalid or, has bad role for an API call')
234 else:
235 auth_u = 'service'
236 if request.rpc_api_key != request.registry.settings['app.service_api.token']:
237 raise Exception("Provided service secret is not recognized!")
230 238
231 239 except Exception:
232 240 log.exception('Error on API AUTH')
@@ -290,7 +298,8 b' def request_view(request):'
290 298 })
291 299
292 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 304 statsd = request.registry.statsd
296 305
@@ -23,6 +23,7 b' import datetime'
23 23 import configparser
24 24 from sqlalchemy import Table
25 25
26 from rhodecode.lib.utils import call_service_api
26 27 from rhodecode.lib.utils2 import AttributeDict
27 28 from rhodecode.model.scm import ScmModel
28 29
@@ -261,3 +262,131 b' class SshWrapper(object):'
261 262 exit_code = -1
262 263
263 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 23 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
24 24 from rhodecode.lib.ext_json import sjson as json
25 25 from rhodecode.lib.vcs.conf import settings as vcs_settings
26 from rhodecode.lib.utils import call_service_api
26 27 from rhodecode.model.scm import ScmModel
27 28
28 29 log = logging.getLogger(__name__)
@@ -47,6 +48,7 b' class VcsServer(object):'
47 48 self.repo_mode = None
48 49 self.store = ''
49 50 self.ini_path = ''
51 self.hooks_protocol = None
50 52
51 53 def _invalidate_cache(self, repo_name):
52 54 """
@@ -54,7 +56,15 b' class VcsServer(object):'
54 56
55 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 69 def has_write_perm(self):
60 70 permission = self.user_permissions.get(self.repo_name)
@@ -65,30 +75,31 b' class VcsServer(object):'
65 75
66 76 def _check_permissions(self, action):
67 77 permission = self.user_permissions.get(self.repo_name)
78 user_info = f'{self.user["user_id"]}:{self.user["username"]}'
68 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 82 if not permission:
72 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 85 return -2
75 86
76 87 if action == 'pull':
77 88 if permission in self.read_perms:
78 89 log.info(
79 90 'READ Permissions for User "%s" detected to repo "%s"!',
80 self.user, self.repo_name)
91 user_info, self.repo_name)
81 92 return 0
82 93 else:
83 94 if permission in self.write_perms:
84 95 log.info(
85 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 98 return 0
88 99
89 100 log.error('Cannot properly fetch or verify user `%s` permissions. '
90 101 'Permissions: %s, vcs action: %s',
91 self.user, permission, action)
102 user_info, permission, action)
92 103 return -2
93 104
94 105 def update_environment(self, action, extras=None):
@@ -134,9 +145,10 b' class VcsServer(object):'
134 145 if exit_code:
135 146 return exit_code, False
136 147
137 req = self.env['request']
138 server_url = req.host_url + req.script_name
139 extras['server_url'] = server_url
148 req = self.env.get('request')
149 if req:
150 server_url = req.host_url + req.script_name
151 extras['server_url'] = server_url
140 152
141 153 log.debug('Using %s binaries from path %s', self.backend, self._path)
142 154 exit_code = self.tunnel.run(extras)
@@ -144,12 +156,13 b' class VcsServer(object):'
144 156 return exit_code, action == "push"
145 157
146 158 def run(self, tunnel_extras=None):
159 self.hooks_protocol = self.config.get('app:main', 'vcs.hooks.protocol')
147 160 tunnel_extras = tunnel_extras or {}
148 161 extras = {}
149 162 extras.update(tunnel_extras)
150 163
151 164 callback_daemon, extras = prepare_callback_daemon(
152 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
165 extras, protocol=self.hooks_protocol,
153 166 host=vcs_settings.HOOKS_HOST)
154 167
155 168 with callback_daemon:
@@ -23,6 +23,7 b' import tempfile'
23 23 import textwrap
24 24 import collections
25 25 from .base import VcsServer
26 from rhodecode.lib.utils import call_service_api
26 27 from rhodecode.model.db import RhodeCodeUi
27 28 from rhodecode.model.settings import VcsSettingsModel
28 29
@@ -108,6 +109,14 b' class MercurialServer(VcsServer):'
108 109 self.tunnel = MercurialTunnelWrapper(server=self)
109 110
110 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 120 ui_sections = collections.defaultdict(list)
112 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
@@ -20,7 +20,7 b' import os'
20 20 import pytest
21 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 24 from rhodecode.lib.utils2 import AttributeDict
25 25
26 26
@@ -34,6 +34,7 b' import tarfile'
34 34 import warnings
35 35 from functools import wraps
36 36 from os.path import join as jn
37 from configparser import NoOptionError
37 38
38 39 import paste
39 40 import pkg_resources
@@ -52,6 +53,9 b' from rhodecode.model import meta'
52 53 from rhodecode.model.db import (
53 54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 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 61 log = logging.getLogger(__name__)
@@ -821,3 +825,27 b' def send_test_email(recipients, email_bo'
821 825 email_body = email_body_plaintext = email_body
822 826 subject = f'SUBJECT FROM: {socket.gethostname()}'
823 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 146 pass
147 147
148 148
149 class ImproperlyConfiguredError(Exception):
150 pass
151
152
149 153 class UnhandledException(VCSError):
150 154 """
151 155 Signals that something unexpected went wrong.
@@ -110,6 +110,7 b' def ini_config(request, tmpdir_factory, '
110 110 'vcs.scm_app_implementation': 'http',
111 111 'vcs.hooks.protocol': 'http',
112 112 'vcs.hooks.host': '*',
113 'app.service_api.token': 'service_secret_token',
113 114 }},
114 115
115 116 {'handler_console': {
@@ -196,7 +196,8 b' setup('
196 196 'rc-upgrade-db=rhodecode.lib.rc_commands.upgrade_db:main',
197 197 'rc-ishell=rhodecode.lib.rc_commands.ishell:main',
198 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 202 'beaker.backends': [
202 203 'memorylru_base=rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase',
General Comments 0
You need to be logged in to leave comments. Login now