Show More
@@ -91,7 +91,6 b' class SshWrapper(object):' | |||
|
91 | 91 | |
|
92 | 92 | def get_repo_details(self, mode): |
|
93 | 93 | vcs_type = mode if mode in ['svn', 'hg', 'git'] else None |
|
94 | mode = mode | |
|
95 | 94 | repo_name = None |
|
96 | 95 | |
|
97 | 96 | hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$' |
@@ -101,8 +100,7 b' class SshWrapper(object):' | |||
|
101 | 100 | repo_name = hg_match.group(1).strip('/') |
|
102 | 101 | return vcs_type, repo_name, mode |
|
103 | 102 | |
|
104 | git_pattern = ( | |
|
105 | r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$') | |
|
103 | git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$' | |
|
106 | 104 | git_match = re.match(git_pattern, self.command) |
|
107 | 105 | if git_match is not None: |
|
108 | 106 | vcs_type = 'git' |
@@ -115,7 +113,8 b' class SshWrapper(object):' | |||
|
115 | 113 | |
|
116 | 114 | if svn_match is not None: |
|
117 | 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 | 118 | return vcs_type, repo_name, mode |
|
120 | 119 | |
|
121 | 120 | return vcs_type, repo_name, mode |
@@ -188,8 +187,7 b' class SshWrapper(object):' | |||
|
188 | 187 | log.debug('SSH Connection info %s', self.get_connection_info()) |
|
189 | 188 | |
|
190 | 189 | if shell and self.command is None: |
|
191 | log.info( | |
|
192 | 'Dropping to shell, no command given and shell is allowed') | |
|
190 | log.info('Dropping to shell, no command given and shell is allowed') | |
|
193 | 191 | os.execl('/bin/bash', '-l') |
|
194 | 192 | exit_code = 1 |
|
195 | 193 | |
@@ -216,8 +214,7 b' class SshWrapper(object):' | |||
|
216 | 214 | exit_code = -1 |
|
217 | 215 | |
|
218 | 216 | else: |
|
219 | log.error( | |
|
220 | 'Unhandled Command: "%s" Aborting.', self.command) | |
|
217 | log.error('Unhandled Command: "%s" Aborting.', self.command) | |
|
221 | 218 | exit_code = -1 |
|
222 | 219 | |
|
223 | 220 | return exit_code |
@@ -66,9 +66,8 b' class VcsServer(object):' | |||
|
66 | 66 | |
|
67 | 67 | def _check_permissions(self, action): |
|
68 | 68 | permission = self.user_permissions.get(self.repo_name) |
|
69 | log.debug( | |
|
70 | 'permission for %s on %s are: %s', | |
|
71 | self.user, self.repo_name, permission) | |
|
69 | log.debug('permission for %s on %s are: %s', | |
|
70 | self.user, self.repo_name, permission) | |
|
72 | 71 | |
|
73 | 72 | if not permission: |
|
74 | 73 | log.error('user `%s` permissions to repo:%s are empty. Forbidding access.', |
@@ -68,8 +68,7 b' class GitServer(VcsServer):' | |||
|
68 | 68 | self.store = store |
|
69 | 69 | self.ini_path = ini_path |
|
70 | 70 | self.repo_name = repo_name |
|
71 | self._path = self.git_path = config.get( | |
|
72 | 'app:main', 'ssh.executable.git') | |
|
71 | self._path = self.git_path = config.get('app:main', 'ssh.executable.git') | |
|
73 | 72 | |
|
74 | 73 | self.repo_mode = repo_mode |
|
75 | 74 | self.tunnel = GitTunnelWrapper(server=self) |
@@ -95,9 +95,8 b' class SubversionTunnelWrapper(object):' | |||
|
95 | 95 | signal.alarm(self.timeout) |
|
96 | 96 | first_response = self._read_first_client_response() |
|
97 | 97 | signal.alarm(0) |
|
98 | return ( | |
|
99 | self._parse_first_client_response(first_response) | |
|
100 | if first_response else None) | |
|
98 | return (self._parse_first_client_response(first_response) | |
|
99 | if first_response else None) | |
|
101 | 100 | |
|
102 | 101 | def patch_first_client_response(self, response, **kwargs): |
|
103 | 102 | self.create_hooks_env() |
@@ -112,9 +111,8 b' class SubversionTunnelWrapper(object):' | |||
|
112 | 111 | self.process.stdin.write(buffer_) |
|
113 | 112 | |
|
114 | 113 | def fail(self, message): |
|
115 | print( | |
|
116 | "( failure ( ( 210005 {message} 0: 0 ) ) )".format( | |
|
117 | message=self._svn_string(message))) | |
|
114 | print("( failure ( ( 210005 {message} 0: 0 ) ) )".format( | |
|
115 | message=self._svn_string(message))) | |
|
118 | 116 | self.remove_configs() |
|
119 | 117 | self.process.kill() |
|
120 | 118 | return 1 |
@@ -139,6 +137,7 b' class SubversionTunnelWrapper(object):' | |||
|
139 | 137 | brackets_stack.pop() |
|
140 | 138 | elif next_byte == " " and not brackets_stack: |
|
141 | 139 | break |
|
140 | ||
|
142 | 141 | return buffer_ |
|
143 | 142 | |
|
144 | 143 | def _parse_first_client_response(self, buffer_): |
@@ -149,8 +148,7 b' class SubversionTunnelWrapper(object):' | |||
|
149 | 148 | ( version:number ( cap:word ... ) url:string ? ra-client:string |
|
150 | 149 | ( ? client:string ) ) |
|
151 | 150 | |
|
152 | Please check https://svn.apache.org/repos/asf/subversion/trunk/ | |
|
153 | subversion/libsvn_ra_svn/protocol | |
|
151 | Please check https://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol | |
|
154 | 152 | """ |
|
155 | 153 | version_re = r'(?P<version>\d+)' |
|
156 | 154 | capabilities_re = r'\(\s(?P<capabilities>[\w\d\-\ ]+)\s\)' |
@@ -163,8 +161,35 b' class SubversionTunnelWrapper(object):' | |||
|
163 | 161 | version=version_re, capabilities=capabilities_re, |
|
164 | 162 | url=url_re, ra_client=ra_client_re, client=client_re)) |
|
165 | 163 | matcher = regex.match(buffer_) |
|
164 | ||
|
166 | 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 | 193 | def run(self, extras): |
|
169 | 194 | action = 'pull' |
|
170 | 195 | self.create_svn_config() |
@@ -175,7 +200,8 b' class SubversionTunnelWrapper(object):' | |||
|
175 | 200 | return self.fail("Repository name cannot be extracted") |
|
176 | 201 | |
|
177 | 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 | 206 | exit_code = self.server._check_permissions(action) |
|
181 | 207 | if exit_code: |
@@ -200,10 +226,10 b' class SubversionServer(VcsServer):' | |||
|
200 | 226 | .__init__(user, user_permissions, config, env) |
|
201 | 227 | self.store = store |
|
202 | 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 | 231 | self.repo_name = repo_name |
|
205 | self._path = self.svn_path = config.get( | |
|
206 | 'app:main', 'ssh.executable.svn') | |
|
232 | self._path = self.svn_path = config.get('app:main', 'ssh.executable.svn') | |
|
207 | 233 | |
|
208 | 234 | self.tunnel = SubversionTunnelWrapper(server=self) |
|
209 | 235 |
@@ -89,6 +89,86 b' class TestSubversionServer(object):' | |||
|
89 | 89 | result = server._check_permissions(action) |
|
90 | 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 | 172 | def test_run_returns_executes_command(self, svn_server): |
|
93 | 173 | server = svn_server.create() |
|
94 | 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