##// END OF EJS Templates
updated with a latest changes.
updated with a latest changes.

File last commit:

r5326:cede61e3 default
r5536:2c8dbdc5 merge default
Show More
__init__.py
387 lines | 13.7 KiB | text/x-python | PythonLexer
copyrights: updated for 2023
r5088 # Copyright (C) 2016-2023 RhodeCode GmbH
ssh-support: enabled full handling of all backends via SSH....
r2187 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import os
import re
import logging
import datetime
ssh-wrapper: perf optimizations...
r4947 from sqlalchemy import Table
ssh-support: enabled full handling of all backends via SSH....
r2187
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 from rhodecode.lib.api_utils import call_service_api
ssh-wrapper: perf optimizations...
r4947 from rhodecode.lib.utils2 import AttributeDict
ssh-support: enabled full handling of all backends via SSH....
r2187
from .hg import MercurialServer
from .git import GitServer
from .svn import SubversionServer
log = logging.getLogger(__name__)
class SshWrapper(object):
ssh: use pre-compiled backends for faster matching of vcs detection.
r4702 hg_cmd_pat = re.compile(r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$')
git_cmd_pat = re.compile(r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
svn_cmd_pat = re.compile(r'^svnserve -t')
ssh-support: enabled full handling of all backends via SSH....
r2187
def __init__(self, command, connection_info, mode,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user, user_id, key_id: int, shell, ini_path: str, settings, env):
ssh-support: enabled full handling of all backends via SSH....
r2187 self.command = command
self.connection_info = connection_info
self.mode = mode
ssh-wrapper: perf optimizations...
r4947 self.username = user
ssh-support: enabled full handling of all backends via SSH....
r2187 self.user_id = user_id
self.key_id = key_id
self.shell = shell
self.ini_path = ini_path
self.env = env
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 self.settings = settings
ssh-support: enabled full handling of all backends via SSH....
r2187 self.server_impl = None
def update_key_access_time(self, key_id):
ssh-wrapper: perf optimizations...
r4947 from rhodecode.model.meta import raw_query_executor, Base
table = Table('user_ssh_keys', Base.metadata, autoload=False)
apps: various fixes and improvements for python3
r5072 atime = datetime.datetime.utcnow()
ssh-wrapper: perf optimizations...
r4947 stmt = (
table.update()
.where(table.c.ssh_key_id == key_id)
apps: various fixes and improvements for python3
r5072 .values(accessed_on=atime)
# no MySQL Support for .returning :((
#.returning(table.c.accessed_on, table.c.ssh_key_fingerprint)
ssh-wrapper: perf optimizations...
r4947 )
apps: various fixes and improvements for python3
r5072 res_count = None
ssh-wrapper: perf optimizations...
r4947 with raw_query_executor() as session:
result = session.execute(stmt)
if result.rowcount:
apps: various fixes and improvements for python3
r5072 res_count = result.rowcount
ssh-wrapper: perf optimizations...
r4947
apps: various fixes and improvements for python3
r5072 if res_count:
log.debug('Update key id:`%s` access time', key_id)
ssh-wrapper: perf optimizations...
r4947
def get_user(self, user_id):
user = AttributeDict()
# lazy load db imports
from rhodecode.model.db import User
dbuser = User.get(user_id)
if not dbuser:
return None
user.user_id = dbuser.user_id
user.username = dbuser.username
user.auth_user = dbuser.AuthUser()
return user
ssh-support: enabled full handling of all backends via SSH....
r2187
def get_connection_info(self):
"""
connection_info
Identifies the client and server ends of the connection.
The variable contains four space-separated values: client IP address,
client port number, server IP address, and server port number.
"""
conn = dict(
client_ip=None,
client_port=None,
server_ip=None,
server_port=None,
)
info = self.connection_info.split(' ')
if len(info) == 4:
conn['client_ip'] = info[0]
conn['client_port'] = info[1]
conn['server_ip'] = info[2]
conn['server_port'] = info[3]
return conn
ssh: allow clone by ID via SSH operations.
r4644 def maybe_translate_repo_uid(self, repo_name):
ssh: handle subrepos better
r4703 _org_name = repo_name
if _org_name.startswith('_'):
# remove format of _ID/subrepo
_org_name = _org_name.split('/', 1)[0]
ssh: allow clone by ID via SSH operations.
r4644 if repo_name.startswith('_'):
from rhodecode.model.repo import RepoModel
ssh: handle subrepos better
r4703 org_repo_name = repo_name
log.debug('translating UID repo %s', org_repo_name)
ssh: allow clone by ID via SSH operations.
r4644 by_id_match = RepoModel().get_repo_by_id(repo_name)
if by_id_match:
repo_name = by_id_match.repo_name
ssh: handle subrepos better
r4703 log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
return repo_name, _org_name
ssh: allow clone by ID via SSH operations.
r4644
ssh-support: enabled full handling of all backends via SSH....
r2187 def get_repo_details(self, mode):
vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
repo_name = None
ssh: use pre-compiled backends for faster matching of vcs detection.
r4702 hg_match = self.hg_cmd_pat.match(self.command)
ssh-support: enabled full handling of all backends via SSH....
r2187 if hg_match is not None:
vcs_type = 'hg'
ssh: handle subrepos better
r4703 repo_id = hg_match.group(1).strip('/')
repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
ssh-support: enabled full handling of all backends via SSH....
r2187 return vcs_type, repo_name, mode
ssh: use pre-compiled backends for faster matching of vcs detection.
r4702 git_match = self.git_cmd_pat.match(self.command)
ssh-support: enabled full handling of all backends via SSH....
r2187 if git_match is not None:
ssh: handle subrepos better
r4703 mode = git_match.group(1)
ssh-support: enabled full handling of all backends via SSH....
r2187 vcs_type = 'git'
ssh: handle subrepos better
r4703 repo_id = git_match.group(2).strip('/')
repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
ssh-support: enabled full handling of all backends via SSH....
r2187 return vcs_type, repo_name, mode
ssh: use pre-compiled backends for faster matching of vcs detection.
r4702 svn_match = self.svn_cmd_pat.match(self.command)
ssh-support: enabled full handling of all backends via SSH....
r2187 if svn_match is not None:
vcs_type = 'svn'
dan
svn: fixed case of wrong extracted repository name for SSH backend. In cases...
r4281 # Repo name should be extracted from the input stream, we're unable to
# extract it at this point in execution
ssh-support: enabled full handling of all backends via SSH....
r2187 return vcs_type, repo_name, mode
return vcs_type, repo_name, mode
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 # TODO: remove this once we have .ini defined access path...
from rhodecode.model.scm import ScmModel
ssh-support: enabled full handling of all backends via SSH....
r2187 store = ScmModel().repos_path
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 check_branch_perms = False
detect_force_push = False
if branch_permissions:
check_branch_perms = True
detect_force_push = True
ssh-support: enabled full handling of all backends via SSH....
r2187 log.debug(
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
vcs, mode, repo, check_branch_perms)
# detect if we have to check branch permissions
extras = {
'detect_force_push': detect_force_push,
'check_branch_perms': check_branch_perms,
feat(celery-hooks): added all needed changes to support new celery backend, removed DummyHooksCallbackDaemon, updated tests. Fixes: RCCE-55
r5298 'config': self.ini_path
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 }
ssh-support: enabled full handling of all backends via SSH....
r2187
if vcs == 'hg':
server = MercurialServer(
store=store, ini_path=self.ini_path,
repo_name=repo, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
ssh-support: enabled full handling of all backends via SSH....
r2187 self.server_impl = server
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 return server.run(tunnel_extras=extras)
ssh-support: enabled full handling of all backends via SSH....
r2187
elif vcs == 'git':
server = GitServer(
store=store, ini_path=self.ini_path,
repo_name=repo, repo_mode=mode, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
ssh-support: enabled full handling of all backends via SSH....
r2187 self.server_impl = server
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 return server.run(tunnel_extras=extras)
ssh-support: enabled full handling of all backends via SSH....
r2187
elif vcs == 'svn':
server = SubversionServer(
store=store, ini_path=self.ini_path,
repo_name=None, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
ssh-support: enabled full handling of all backends via SSH....
r2187 self.server_impl = server
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 return server.run(tunnel_extras=extras)
ssh-support: enabled full handling of all backends via SSH....
r2187
else:
modernize: updates for python3
r5095 raise Exception(f'Unrecognised VCS: {vcs}')
ssh-support: enabled full handling of all backends via SSH....
r2187
def wrap(self):
mode = self.mode
ssh-wrapper: perf optimizations...
r4947 username = self.username
ssh-support: enabled full handling of all backends via SSH....
r2187 user_id = self.user_id
key_id = self.key_id
shell = self.shell
scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
log.debug(
ssh-wrapper: perf optimizations...
r4947 'Mode: `%s` User: `name:%s : id:%s` Shell: `%s` SSH Command: `\"%s\"` '
ssh-support: enabled full handling of all backends via SSH....
r2187 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
ssh-wrapper: perf optimizations...
r4947 mode, username, user_id, shell, self.command,
ssh-support: enabled full handling of all backends via SSH....
r2187 scm_detected, scm_mode, scm_repo)
ssh-wrapper: perf optimizations...
r4947 log.debug('SSH Connection info %s', self.get_connection_info())
ssh-support: enabled full handling of all backends via SSH....
r2187 # update last access time for this key
ssh-wrapper: perf optimizations...
r4947 if key_id:
self.update_key_access_time(key_id)
ssh-support: enabled full handling of all backends via SSH....
r2187
if shell and self.command is None:
dan
svn: fixed case of wrong extracted repository name for SSH backend. In cases...
r4281 log.info('Dropping to shell, no command given and shell is allowed')
ssh-support: enabled full handling of all backends via SSH....
r2187 os.execl('/bin/bash', '-l')
exit_code = 1
elif scm_detected:
ssh-wrapper: perf optimizations...
r4947 user = self.get_user(user_id)
ssh: prevent exceptions when user associated to stored old key is not found.
r2206 if not user:
log.warning('User with id %s not found', user_id)
exit_code = -1
return exit_code
ssh-wrapper: perf optimizations...
r4947 auth_user = user.auth_user
ssh-support: enabled full handling of all backends via SSH....
r2187 permissions = auth_user.permissions['repositories']
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
ssh-support: enabled full handling of all backends via SSH....
r2187 try:
exit_code, is_updated = self.serve(
branch-permissions: enabled branch permissions checks for SSH backend.
r2982 scm_detected, scm_repo, scm_mode, user, permissions,
repo_branch_permissions)
ssh-support: enabled full handling of all backends via SSH....
r2187 except Exception:
log.exception('Error occurred during execution of SshWrapper')
exit_code = -1
elif self.command is None and shell is False:
log.error('No Command given.')
exit_code = -1
else:
dan
svn: fixed case of wrong extracted repository name for SSH backend. In cases...
r4281 log.error('Unhandled Command: "%s" Aborting.', self.command)
ssh-support: enabled full handling of all backends via SSH....
r2187 exit_code = -1
return exit_code
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314
class SshWrapperStandalone(SshWrapper):
"""
New version of SshWrapper designed to be depended only on service API
"""
repos_path = None
@staticmethod
def parse_user_related_data(user_data):
user = AttributeDict()
user.user_id = user_data['user_id']
user.username = user_data['username']
user.repo_permissions = user_data['repo_permissions']
user.branch_permissions = user_data['branch_permissions']
return user
def wrap(self):
mode = self.mode
username = self.username
user_id = self.user_id
shell = self.shell
scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
log.debug(
'Mode: `%s` User: `name:%s : id:%s` Shell: `%s` SSH Command: `\"%s\"` '
'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
mode, username, user_id, shell, self.command,
scm_detected, scm_mode, scm_repo)
log.debug('SSH Connection info %s', self.get_connection_info())
if shell and self.command is None:
log.info('Dropping to shell, no command given and shell is allowed')
os.execl('/bin/bash', '-l')
exit_code = 1
elif scm_detected:
refactor(ssh-wrapper): changed SSHVcsServer to SshVcsServer, updated call_service_api method.
r5326 data = call_service_api(self.settings, {
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314 "method": "service_get_data_for_ssh_wrapper",
"args": {"user_id": user_id, "repo_name": scm_repo, "key_id": self.key_id}
})
user = self.parse_user_related_data(data)
if not user:
log.warning('User with id %s not found', user_id)
exit_code = -1
return exit_code
self.repos_path = data['repos_path']
permissions = user.repo_permissions
repo_branch_permissions = user.branch_permissions
try:
exit_code, is_updated = self.serve(
scm_detected, scm_repo, scm_mode, user, permissions,
repo_branch_permissions)
except Exception:
log.exception('Error occurred during execution of SshWrapper')
exit_code = -1
elif self.command is None and shell is False:
log.error('No Command given.')
exit_code = -1
else:
log.error('Unhandled Command: "%s" Aborting.', self.command)
exit_code = -1
return exit_code
def maybe_translate_repo_uid(self, repo_name):
_org_name = repo_name
if _org_name.startswith('_'):
_org_name = _org_name.split('/', 1)[0]
if repo_name.startswith('_'):
org_repo_name = repo_name
log.debug('translating UID repo %s', org_repo_name)
refactor(ssh-wrapper): changed SSHVcsServer to SshVcsServer, updated call_service_api method.
r5326 by_id_match = call_service_api(self.settings, {
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314 'method': 'service_get_repo_name_by_id',
"args": {"repo_id": repo_name}
})
if by_id_match:
repo_name = by_id_match['repo_name']
log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
return repo_name, _org_name
def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
store = self.repos_path
check_branch_perms = False
detect_force_push = False
if branch_permissions:
check_branch_perms = True
detect_force_push = True
log.debug(
'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
vcs, mode, repo, check_branch_perms)
# detect if we have to check branch permissions
extras = {
'detect_force_push': detect_force_push,
'check_branch_perms': check_branch_perms,
'config': self.ini_path
}
match vcs:
case 'hg':
server = MercurialServer(
store=store, ini_path=self.ini_path,
repo_name=repo, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314 case 'git':
server = GitServer(
store=store, ini_path=self.ini_path,
repo_name=repo, repo_mode=mode, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314 case 'svn':
server = SubversionServer(
store=store, ini_path=self.ini_path,
repo_name=None, user=user,
feat(ssh-wrapper-speedup): major rewrite of code to address imports problem with ssh-wrapper-v2...
r5325 user_permissions=permissions, settings=self.settings, env=self.env)
fix(ssh): Added alternative SshWrapper and changes needed to support it + service api. Fixes: RCCE-6
r5314 case _:
raise Exception(f'Unrecognised VCS: {vcs}')
self.server_impl = server
return server.run(tunnel_extras=extras)