##// END OF EJS Templates
svn: fixed case of wrong extracted repository name for SSH backend. In cases...
dan -
r4281:5da17e74 default
parent child Browse files
Show More
@@ -91,7 +91,6 b' class SshWrapper(object):'
91
91
92 def get_repo_details(self, mode):
92 def get_repo_details(self, mode):
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
94 mode = mode
95 repo_name = None
94 repo_name = None
96
95
97 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
96 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
@@ -101,8 +100,7 b' class SshWrapper(object):'
101 repo_name = hg_match.group(1).strip('/')
100 repo_name = hg_match.group(1).strip('/')
102 return vcs_type, repo_name, mode
101 return vcs_type, repo_name, mode
103
102
104 git_pattern = (
103 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
105 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
106 git_match = re.match(git_pattern, self.command)
104 git_match = re.match(git_pattern, self.command)
107 if git_match is not None:
105 if git_match is not None:
108 vcs_type = 'git'
106 vcs_type = 'git'
@@ -115,7 +113,8 b' class SshWrapper(object):'
115
113
116 if svn_match is not None:
114 if svn_match is not None:
117 vcs_type = 'svn'
115 vcs_type = 'svn'
118 # Repo name should be extracted from the input stream
116 # Repo name should be extracted from the input stream, we're unable to
117 # extract it at this point in execution
119 return vcs_type, repo_name, mode
118 return vcs_type, repo_name, mode
120
119
121 return vcs_type, repo_name, mode
120 return vcs_type, repo_name, mode
@@ -188,8 +187,7 b' class SshWrapper(object):'
188 log.debug('SSH Connection info %s', self.get_connection_info())
187 log.debug('SSH Connection info %s', self.get_connection_info())
189
188
190 if shell and self.command is None:
189 if shell and self.command is None:
191 log.info(
190 log.info('Dropping to shell, no command given and shell is allowed')
192 'Dropping to shell, no command given and shell is allowed')
193 os.execl('/bin/bash', '-l')
191 os.execl('/bin/bash', '-l')
194 exit_code = 1
192 exit_code = 1
195
193
@@ -216,8 +214,7 b' class SshWrapper(object):'
216 exit_code = -1
214 exit_code = -1
217
215
218 else:
216 else:
219 log.error(
217 log.error('Unhandled Command: "%s" Aborting.', self.command)
220 'Unhandled Command: "%s" Aborting.', self.command)
221 exit_code = -1
218 exit_code = -1
222
219
223 return exit_code
220 return exit_code
@@ -66,9 +66,8 b' class VcsServer(object):'
66
66
67 def _check_permissions(self, action):
67 def _check_permissions(self, action):
68 permission = self.user_permissions.get(self.repo_name)
68 permission = self.user_permissions.get(self.repo_name)
69 log.debug(
69 log.debug('permission for %s on %s are: %s',
70 'permission for %s on %s are: %s',
70 self.user, self.repo_name, permission)
71 self.user, self.repo_name, permission)
72
71
73 if not permission:
72 if not permission:
74 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
73 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
@@ -68,8 +68,7 b' class GitServer(VcsServer):'
68 self.store = store
68 self.store = store
69 self.ini_path = ini_path
69 self.ini_path = ini_path
70 self.repo_name = repo_name
70 self.repo_name = repo_name
71 self._path = self.git_path = config.get(
71 self._path = self.git_path = config.get('app:main', 'ssh.executable.git')
72 'app:main', 'ssh.executable.git')
73
72
74 self.repo_mode = repo_mode
73 self.repo_mode = repo_mode
75 self.tunnel = GitTunnelWrapper(server=self)
74 self.tunnel = GitTunnelWrapper(server=self)
@@ -95,9 +95,8 b' class SubversionTunnelWrapper(object):'
95 signal.alarm(self.timeout)
95 signal.alarm(self.timeout)
96 first_response = self._read_first_client_response()
96 first_response = self._read_first_client_response()
97 signal.alarm(0)
97 signal.alarm(0)
98 return (
98 return (self._parse_first_client_response(first_response)
99 self._parse_first_client_response(first_response)
99 if first_response else None)
100 if first_response else None)
101
100
102 def patch_first_client_response(self, response, **kwargs):
101 def patch_first_client_response(self, response, **kwargs):
103 self.create_hooks_env()
102 self.create_hooks_env()
@@ -112,9 +111,8 b' class SubversionTunnelWrapper(object):'
112 self.process.stdin.write(buffer_)
111 self.process.stdin.write(buffer_)
113
112
114 def fail(self, message):
113 def fail(self, message):
115 print(
114 print("( failure ( ( 210005 {message} 0: 0 ) ) )".format(
116 "( failure ( ( 210005 {message} 0: 0 ) ) )".format(
115 message=self._svn_string(message)))
117 message=self._svn_string(message)))
118 self.remove_configs()
116 self.remove_configs()
119 self.process.kill()
117 self.process.kill()
120 return 1
118 return 1
@@ -139,6 +137,7 b' class SubversionTunnelWrapper(object):'
139 brackets_stack.pop()
137 brackets_stack.pop()
140 elif next_byte == " " and not brackets_stack:
138 elif next_byte == " " and not brackets_stack:
141 break
139 break
140
142 return buffer_
141 return buffer_
143
142
144 def _parse_first_client_response(self, buffer_):
143 def _parse_first_client_response(self, buffer_):
@@ -149,8 +148,7 b' class SubversionTunnelWrapper(object):'
149 ( version:number ( cap:word ... ) url:string ? ra-client:string
148 ( version:number ( cap:word ... ) url:string ? ra-client:string
150 ( ? client:string ) )
149 ( ? client:string ) )
151
150
152 Please check https://svn.apache.org/repos/asf/subversion/trunk/
151 Please check https://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol
153 subversion/libsvn_ra_svn/protocol
154 """
152 """
155 version_re = r'(?P<version>\d+)'
153 version_re = r'(?P<version>\d+)'
156 capabilities_re = r'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)'
154 capabilities_re = r'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)'
@@ -163,8 +161,35 b' class SubversionTunnelWrapper(object):'
163 version=version_re, capabilities=capabilities_re,
161 version=version_re, capabilities=capabilities_re,
164 url=url_re, ra_client=ra_client_re, client=client_re))
162 url=url_re, ra_client=ra_client_re, client=client_re))
165 matcher = regex.match(buffer_)
163 matcher = regex.match(buffer_)
164
166 return matcher.groupdict() if matcher else None
165 return matcher.groupdict() if matcher else None
167
166
167 def _match_repo_name(self, url):
168 """
169 Given an server url, try to match it against ALL known repository names.
170 This handles a tricky SVN case for SSH and subdir commits.
171 E.g if our repo name is my-svn-repo, a svn commit on file in a subdir would
172 result in the url with this subdir added.
173 """
174 # case 1 direct match, we don't do any "heavy" lookups
175 if url in self.server.user_permissions:
176 return url
177
178 log.debug('Extracting repository name from subdir path %s', url)
179 # case 2 we check all permissions, and match closes possible case...
180 # NOTE(dan): In this case we only know that url has a subdir parts, it's safe
181 # to assume that it will have the repo name as prefix, we ensure the prefix
182 # for similar repositories isn't matched by adding a /
183 # e.g subgroup/repo-name/ and subgroup/repo-name-1/ would work correct.
184 for repo_name in self.server.user_permissions:
185 repo_name_prefix = repo_name + '/'
186 if url.startswith(repo_name_prefix):
187 log.debug('Found prefix %s match, returning proper repository name',
188 repo_name_prefix)
189 return repo_name
190
191 return
192
168 def run(self, extras):
193 def run(self, extras):
169 action = 'pull'
194 action = 'pull'
170 self.create_svn_config()
195 self.create_svn_config()
@@ -175,7 +200,8 b' class SubversionTunnelWrapper(object):'
175 return self.fail("Repository name cannot be extracted")
200 return self.fail("Repository name cannot be extracted")
176
201
177 url_parts = urlparse.urlparse(first_response['url'])
202 url_parts = urlparse.urlparse(first_response['url'])
178 self.server.repo_name = url_parts.path.strip('/')
203
204 self.server.repo_name = self._match_repo_name(url_parts.path.strip('/'))
179
205
180 exit_code = self.server._check_permissions(action)
206 exit_code = self.server._check_permissions(action)
181 if exit_code:
207 if exit_code:
@@ -200,10 +226,10 b' class SubversionServer(VcsServer):'
200 .__init__(user, user_permissions, config, env)
226 .__init__(user, user_permissions, config, env)
201 self.store = store
227 self.store = store
202 self.ini_path = ini_path
228 self.ini_path = ini_path
203 # this is set in .run() from input stream
229 # NOTE(dan): repo_name at this point is empty,
230 # this is set later in .run() based from parsed input stream
204 self.repo_name = repo_name
231 self.repo_name = repo_name
205 self._path = self.svn_path = config.get(
232 self._path = self.svn_path = config.get('app:main', 'ssh.executable.svn')
206 'app:main', 'ssh.executable.svn')
207
233
208 self.tunnel = SubversionTunnelWrapper(server=self)
234 self.tunnel = SubversionTunnelWrapper(server=self)
209
235
@@ -89,6 +89,86 b' class TestSubversionServer(object):'
89 result = server._check_permissions(action)
89 result = server._check_permissions(action)
90 assert result is code
90 assert result is code
91
91
92 @pytest.mark.parametrize('permissions, access_paths, expected_match', [
93 # not matched repository name
94 ({
95 'test-svn': ''
96 }, ['test-svn-1', 'test-svn-1/subpath'],
97 None),
98
99 # exact match
100 ({
101 'test-svn': ''
102 },
103 ['test-svn'],
104 'test-svn'),
105
106 # subdir commits
107 ({
108 'test-svn': ''
109 },
110 ['test-svn/foo',
111 'test-svn/foo/test-svn',
112 'test-svn/trunk/development.txt',
113 ],
114 'test-svn'),
115
116 # subgroups + similar patterns
117 ({
118 'test-svn': '',
119 'test-svn-1': '',
120 'test-svn-subgroup/test-svn': '',
121
122 },
123 ['test-svn-1',
124 'test-svn-1/foo/test-svn',
125 'test-svn-1/test-svn',
126 ],
127 'test-svn-1'),
128
129 # subgroups + similar patterns
130 ({
131 'test-svn-1': '',
132 'test-svn-10': '',
133 'test-svn-100': '',
134 },
135 ['test-svn-10',
136 'test-svn-10/foo/test-svn',
137 'test-svn-10/test-svn',
138 ],
139 'test-svn-10'),
140
141 # subgroups + similar patterns
142 ({
143 'name': '',
144 'nameContains': '',
145 'nameContainsThis': '',
146 },
147 ['nameContains',
148 'nameContains/This',
149 'nameContains/This/test-svn',
150 ],
151 'nameContains'),
152
153 # subgroups + similar patterns
154 ({
155 'test-svn': '',
156 'test-svn-1': '',
157 'test-svn-subgroup/test-svn': '',
158
159 },
160 ['test-svn-subgroup/test-svn',
161 'test-svn-subgroup/test-svn/foo/test-svn',
162 'test-svn-subgroup/test-svn/trunk/example.txt',
163 ],
164 'test-svn-subgroup/test-svn'),
165 ])
166 def test_repo_extraction_on_subdir(self, svn_server, permissions, access_paths, expected_match):
167 server = svn_server.create(user_permissions=permissions)
168 for path in access_paths:
169 repo_name = server.tunnel._match_repo_name(path)
170 assert repo_name == expected_match
171
92 def test_run_returns_executes_command(self, svn_server):
172 def test_run_returns_executes_command(self, svn_server):
93 server = svn_server.create()
173 server = svn_server.create()
94 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
174 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
General Comments 0
You need to be logged in to leave comments. Login now