##// END OF EJS Templates
ssh: handle subrepos better
super-admin -
r4703:3248f2d8 stable
parent child Browse files
Show More
@@ -1,227 +1,238 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 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 re
23 23 import logging
24 24 import datetime
25 25 from pyramid.compat import configparser
26 26
27 27 from rhodecode.model.db import Session, User, UserSshKeys
28 28 from rhodecode.model.scm import ScmModel
29 29
30 30 from .hg import MercurialServer
31 31 from .git import GitServer
32 32 from .svn import SubversionServer
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class SshWrapper(object):
37 37 hg_cmd_pat = re.compile(r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$')
38 38 git_cmd_pat = re.compile(r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
39 39 svn_cmd_pat = re.compile(r'^svnserve -t')
40 40
41 41 def __init__(self, command, connection_info, mode,
42 42 user, user_id, key_id, shell, ini_path, env):
43 43 self.command = command
44 44 self.connection_info = connection_info
45 45 self.mode = mode
46 46 self.user = user
47 47 self.user_id = user_id
48 48 self.key_id = key_id
49 49 self.shell = shell
50 50 self.ini_path = ini_path
51 51 self.env = env
52 52
53 53 self.config = self.parse_config(ini_path)
54 54 self.server_impl = None
55 55
56 56 def parse_config(self, config_path):
57 57 parser = configparser.ConfigParser()
58 58 parser.read(config_path)
59 59 return parser
60 60
61 61 def update_key_access_time(self, key_id):
62 62 key = UserSshKeys().query().filter(
63 63 UserSshKeys.ssh_key_id == key_id).scalar()
64 64 if key:
65 65 key.accessed_on = datetime.datetime.utcnow()
66 66 Session().add(key)
67 67 Session().commit()
68 68 log.debug('Update key id:`%s` fingerprint:`%s` access time',
69 69 key_id, key.ssh_key_fingerprint)
70 70
71 71 def get_connection_info(self):
72 72 """
73 73 connection_info
74 74
75 75 Identifies the client and server ends of the connection.
76 76 The variable contains four space-separated values: client IP address,
77 77 client port number, server IP address, and server port number.
78 78 """
79 79 conn = dict(
80 80 client_ip=None,
81 81 client_port=None,
82 82 server_ip=None,
83 83 server_port=None,
84 84 )
85 85
86 86 info = self.connection_info.split(' ')
87 87 if len(info) == 4:
88 88 conn['client_ip'] = info[0]
89 89 conn['client_port'] = info[1]
90 90 conn['server_ip'] = info[2]
91 91 conn['server_port'] = info[3]
92 92
93 93 return conn
94 94
95 95 def maybe_translate_repo_uid(self, repo_name):
96 _org_name = repo_name
97 if _org_name.startswith('_'):
98 # remove format of _ID/subrepo
99 _org_name = _org_name.split('/', 1)[0]
100
96 101 if repo_name.startswith('_'):
97 102 from rhodecode.model.repo import RepoModel
103 org_repo_name = repo_name
104 log.debug('translating UID repo %s', org_repo_name)
98 105 by_id_match = RepoModel().get_repo_by_id(repo_name)
99 106 if by_id_match:
100 107 repo_name = by_id_match.repo_name
101 return repo_name
108 log.debug('translation of UID repo %s got `%s`', org_repo_name, repo_name)
109
110 return repo_name, _org_name
102 111
103 112 def get_repo_details(self, mode):
104 113 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
105 114 repo_name = None
106 115
107 116 hg_match = self.hg_cmd_pat.match(self.command)
108 117 if hg_match is not None:
109 118 vcs_type = 'hg'
110 repo_name = self.maybe_translate_repo_uid(hg_match.group(1).strip('/'))
119 repo_id = hg_match.group(1).strip('/')
120 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
111 121 return vcs_type, repo_name, mode
112 122
113 123 git_match = self.git_cmd_pat.match(self.command)
114 124 if git_match is not None:
125 mode = git_match.group(1)
115 126 vcs_type = 'git'
116 repo_name = self.maybe_translate_repo_uid(git_match.group(2).strip('/'))
117 mode = git_match.group(1)
127 repo_id = git_match.group(2).strip('/')
128 repo_name, org_name = self.maybe_translate_repo_uid(repo_id)
118 129 return vcs_type, repo_name, mode
119 130
120 131 svn_match = self.svn_cmd_pat.match(self.command)
121 132 if svn_match is not None:
122 133 vcs_type = 'svn'
123 134 # Repo name should be extracted from the input stream, we're unable to
124 135 # extract it at this point in execution
125 136 return vcs_type, repo_name, mode
126 137
127 138 return vcs_type, repo_name, mode
128 139
129 140 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
130 141 store = ScmModel().repos_path
131 142
132 143 check_branch_perms = False
133 144 detect_force_push = False
134 145
135 146 if branch_permissions:
136 147 check_branch_perms = True
137 148 detect_force_push = True
138 149
139 150 log.debug(
140 151 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
141 152 vcs, mode, repo, check_branch_perms)
142 153
143 154 # detect if we have to check branch permissions
144 155 extras = {
145 156 'detect_force_push': detect_force_push,
146 157 'check_branch_perms': check_branch_perms,
147 158 }
148 159
149 160 if vcs == 'hg':
150 161 server = MercurialServer(
151 162 store=store, ini_path=self.ini_path,
152 163 repo_name=repo, user=user,
153 164 user_permissions=permissions, config=self.config, env=self.env)
154 165 self.server_impl = server
155 166 return server.run(tunnel_extras=extras)
156 167
157 168 elif vcs == 'git':
158 169 server = GitServer(
159 170 store=store, ini_path=self.ini_path,
160 171 repo_name=repo, repo_mode=mode, user=user,
161 172 user_permissions=permissions, config=self.config, env=self.env)
162 173 self.server_impl = server
163 174 return server.run(tunnel_extras=extras)
164 175
165 176 elif vcs == 'svn':
166 177 server = SubversionServer(
167 178 store=store, ini_path=self.ini_path,
168 179 repo_name=None, user=user,
169 180 user_permissions=permissions, config=self.config, env=self.env)
170 181 self.server_impl = server
171 182 return server.run(tunnel_extras=extras)
172 183
173 184 else:
174 185 raise Exception('Unrecognised VCS: {}'.format(vcs))
175 186
176 187 def wrap(self):
177 188 mode = self.mode
178 189 user = self.user
179 190 user_id = self.user_id
180 191 key_id = self.key_id
181 192 shell = self.shell
182 193
183 194 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
184 195
185 196 log.debug(
186 197 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
187 198 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
188 199 mode, user, user_id, shell, self.command,
189 200 scm_detected, scm_mode, scm_repo)
190 201
191 202 # update last access time for this key
192 203 self.update_key_access_time(key_id)
193 204
194 205 log.debug('SSH Connection info %s', self.get_connection_info())
195 206
196 207 if shell and self.command is None:
197 208 log.info('Dropping to shell, no command given and shell is allowed')
198 209 os.execl('/bin/bash', '-l')
199 210 exit_code = 1
200 211
201 212 elif scm_detected:
202 213 user = User.get(user_id)
203 214 if not user:
204 215 log.warning('User with id %s not found', user_id)
205 216 exit_code = -1
206 217 return exit_code
207 218
208 219 auth_user = user.AuthUser()
209 220 permissions = auth_user.permissions['repositories']
210 221 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
211 222 try:
212 223 exit_code, is_updated = self.serve(
213 224 scm_detected, scm_repo, scm_mode, user, permissions,
214 225 repo_branch_permissions)
215 226 except Exception:
216 227 log.exception('Error occurred during execution of SshWrapper')
217 228 exit_code = -1
218 229
219 230 elif self.command is None and shell is False:
220 231 log.error('No Command given.')
221 232 exit_code = -1
222 233
223 234 else:
224 235 log.error('Unhandled Command: "%s" Aborting.', self.command)
225 236 exit_code = -1
226 237
227 238 return exit_code
General Comments 0
You need to be logged in to leave comments. Login now