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