13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
4 |
"""Tests for the safe branch open code."""
|
|
5 |
||
6 |
||
7 |
__metaclass__ = type |
|
8 |
||
13811.1.1
by Jeroen Vermeulen
More lint. |
9 |
from bzrlib.branch import ( |
10 |
Branch, |
|
11 |
BranchReferenceFormat, |
|
12 |
BzrBranchFormat7, |
|
13 |
)
|
|
14 |
from bzrlib.bzrdir import ( |
|
15 |
BzrDir, |
|
16 |
BzrDirMetaFormat1, |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
17 |
BzrProber, |
13811.1.1
by Jeroen Vermeulen
More lint. |
18 |
)
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
19 |
from bzrlib.controldir import ControlDirFormat |
20 |
from bzrlib.errors import NotBranchError |
|
7675.1225.1
by Jelmer Vernooij
Merge stable. |
21 |
from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack1 |
13811.1.1
by Jeroen Vermeulen
More lint. |
22 |
from bzrlib.tests import TestCaseWithTransport |
23 |
from bzrlib.transport import chroot |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
24 |
from lazr.uri import URI |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
25 |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
26 |
from lp.codehosting.safe_open import ( |
27 |
BadUrl, |
|
28 |
BlacklistPolicy, |
|
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
29 |
BranchLoopError, |
30 |
BranchReferenceForbidden, |
|
13811.1.1
by Jeroen Vermeulen
More lint. |
31 |
safe_open, |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
32 |
SafeBranchOpener, |
13604.1.7
by Jelmer Vernooij
Fix some imports. |
33 |
WhitelistPolicy, |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
34 |
)
|
35 |
from lp.testing import TestCase |
|
36 |
||
37 |
||
38 |
class TestSafeBranchOpenerCheckAndFollowBranchReference(TestCase): |
|
39 |
"""Unit tests for `SafeBranchOpener.checkAndFollowBranchReference`."""
|
|
40 |
||
13677.5.5
by Jelmer Vernooij
more docstrings, tests. |
41 |
def setUp(self): |
42 |
super(TestSafeBranchOpenerCheckAndFollowBranchReference, self).setUp() |
|
43 |
SafeBranchOpener.install_hook() |
|
44 |
||
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
45 |
class StubbedSafeBranchOpener(SafeBranchOpener): |
46 |
"""SafeBranchOpener that provides canned answers.
|
|
47 |
||
48 |
We implement the methods we need to to be able to control all the
|
|
49 |
inputs to the `BranchMirrorer.checkSource` method, which is what is
|
|
50 |
being tested in this class.
|
|
51 |
"""
|
|
52 |
||
53 |
def __init__(self, references, policy): |
|
54 |
parent_cls = TestSafeBranchOpenerCheckAndFollowBranchReference |
|
55 |
super(parent_cls.StubbedSafeBranchOpener, self).__init__(policy) |
|
56 |
self._reference_values = {} |
|
57 |
for i in range(len(references) - 1): |
|
13811.1.1
by Jeroen Vermeulen
More lint. |
58 |
self._reference_values[references[i]] = references[i + 1] |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
59 |
self.follow_reference_calls = [] |
60 |
||
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
61 |
def followReference(self, url): |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
62 |
self.follow_reference_calls.append(url) |
63 |
return self._reference_values[url] |
|
64 |
||
65 |
def makeBranchOpener(self, should_follow_references, references, |
|
66 |
unsafe_urls=None): |
|
67 |
policy = BlacklistPolicy(should_follow_references, unsafe_urls) |
|
68 |
opener = self.StubbedSafeBranchOpener(references, policy) |
|
69 |
return opener |
|
70 |
||
71 |
def testCheckInitialURL(self): |
|
72 |
# checkSource rejects all URLs that are not allowed.
|
|
73 |
opener = self.makeBranchOpener(None, [], set(['a'])) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
74 |
self.assertRaises( |
75 |
BadUrl, opener.checkAndFollowBranchReference, 'a') |
|
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
76 |
|
77 |
def testNotReference(self): |
|
78 |
# When branch references are forbidden, checkAndFollowBranchReference
|
|
79 |
# does not raise on non-references.
|
|
80 |
opener = self.makeBranchOpener(False, ['a', None]) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
81 |
self.assertEquals( |
82 |
'a', opener.checkAndFollowBranchReference('a')) |
|
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
83 |
self.assertEquals(['a'], opener.follow_reference_calls) |
84 |
||
85 |
def testBranchReferenceForbidden(self): |
|
86 |
# checkAndFollowBranchReference raises BranchReferenceForbidden if
|
|
87 |
# branch references are forbidden and the source URL points to a
|
|
88 |
# branch reference.
|
|
89 |
opener = self.makeBranchOpener(False, ['a', 'b']) |
|
90 |
self.assertRaises( |
|
91 |
BranchReferenceForbidden, |
|
14482.1.6
by Jelmer Vernooij
Fix tests. |
92 |
opener.checkAndFollowBranchReference, 'a') |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
93 |
self.assertEquals(['a'], opener.follow_reference_calls) |
94 |
||
95 |
def testAllowedReference(self): |
|
96 |
# checkAndFollowBranchReference does not raise if following references
|
|
97 |
# is allowed and the source URL points to a branch reference to a
|
|
98 |
# permitted location.
|
|
99 |
opener = self.makeBranchOpener(True, ['a', 'b', None]) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
100 |
self.assertEquals( |
101 |
'b', opener.checkAndFollowBranchReference('a')) |
|
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
102 |
self.assertEquals(['a', 'b'], opener.follow_reference_calls) |
103 |
||
104 |
def testCheckReferencedURLs(self): |
|
105 |
# checkAndFollowBranchReference checks if the URL a reference points
|
|
106 |
# to is safe.
|
|
107 |
opener = self.makeBranchOpener( |
|
108 |
True, ['a', 'b', None], unsafe_urls=set('b')) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
109 |
self.assertRaises( |
110 |
BadUrl, opener.checkAndFollowBranchReference, 'a') |
|
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
111 |
self.assertEquals(['a'], opener.follow_reference_calls) |
112 |
||
113 |
def testSelfReferencingBranch(self): |
|
114 |
# checkAndFollowBranchReference raises BranchReferenceLoopError if
|
|
115 |
# following references is allowed and the source url points to a
|
|
116 |
# self-referencing branch reference.
|
|
117 |
opener = self.makeBranchOpener(True, ['a', 'a']) |
|
118 |
self.assertRaises( |
|
14482.1.8
by Jelmer Vernooij
Add more tests in test_safe_open. |
119 |
BranchLoopError, opener.checkAndFollowBranchReference, 'a') |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
120 |
self.assertEquals(['a'], opener.follow_reference_calls) |
121 |
||
122 |
def testBranchReferenceLoop(self): |
|
123 |
# checkAndFollowBranchReference raises BranchReferenceLoopError if
|
|
124 |
# following references is allowed and the source url points to a loop
|
|
125 |
# of branch references.
|
|
126 |
references = ['a', 'b', 'a'] |
|
127 |
opener = self.makeBranchOpener(True, references) |
|
128 |
self.assertRaises( |
|
14482.1.8
by Jelmer Vernooij
Add more tests in test_safe_open. |
129 |
BranchLoopError, opener.checkAndFollowBranchReference, 'a') |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
130 |
self.assertEquals(['a', 'b'], opener.follow_reference_calls) |
131 |
||
132 |
||
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
133 |
class TrackingProber(BzrProber): |
134 |
"""Subclass of BzrProber which tracks URLs it has been asked to open."""
|
|
135 |
||
136 |
seen_urls = [] |
|
137 |
||
138 |
@classmethod
|
|
139 |
def probe_transport(klass, transport): |
|
140 |
klass.seen_urls.append(transport.base) |
|
141 |
return BzrProber.probe_transport(transport) |
|
142 |
||
143 |
||
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
144 |
class TestSafeBranchOpenerStacking(TestCaseWithTransport): |
145 |
||
13677.5.4
by Jelmer Vernooij
Install hook globally. |
146 |
def setUp(self): |
147 |
super(TestSafeBranchOpenerStacking, self).setUp() |
|
148 |
SafeBranchOpener.install_hook() |
|
149 |
||
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
150 |
def makeBranchOpener(self, allowed_urls, probers=None): |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
151 |
policy = WhitelistPolicy(True, allowed_urls, True) |
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
152 |
return SafeBranchOpener(policy, probers) |
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
153 |
|
154 |
def makeBranch(self, path, branch_format, repository_format): |
|
155 |
"""Make a Bazaar branch at 'path' with the given formats."""
|
|
156 |
bzrdir_format = BzrDirMetaFormat1() |
|
157 |
bzrdir_format.set_branch_format(branch_format) |
|
158 |
bzrdir = self.make_bzrdir(path, format=bzrdir_format) |
|
159 |
repository_format.initialize(bzrdir) |
|
160 |
return bzrdir.create_branch() |
|
161 |
||
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
162 |
def testProbers(self): |
163 |
# Only the specified probers should be used
|
|
164 |
b = self.make_branch('branch') |
|
165 |
opener = self.makeBranchOpener([b.base], probers=[]) |
|
166 |
self.assertRaises(NotBranchError, opener.open, b.base) |
|
167 |
opener = self.makeBranchOpener([b.base], probers=[BzrProber]) |
|
168 |
self.assertEquals(b.base, opener.open(b.base).base) |
|
169 |
||
170 |
def testDefaultProbers(self): |
|
171 |
# If no probers are specified to the constructor
|
|
172 |
# of SafeBranchOpener, then a safe set will be used,
|
|
173 |
# rather than all probers registered in bzr.
|
|
174 |
self.addCleanup(ControlDirFormat.unregister_prober, TrackingProber) |
|
175 |
ControlDirFormat.register_prober(TrackingProber) |
|
176 |
# Open a location without any branches, so that all probers are
|
|
177 |
# tried.
|
|
178 |
# First, check that the TrackingProber tracks correctly.
|
|
179 |
TrackingProber.seen_urls = [] |
|
180 |
opener = self.makeBranchOpener(["."], probers=[TrackingProber]) |
|
181 |
self.assertRaises(NotBranchError, opener.open, ".") |
|
182 |
self.assertEquals(1, len(TrackingProber.seen_urls)) |
|
183 |
TrackingProber.seen_urls = [] |
|
184 |
# And make sure it's registered in such a way that BzrDir.open would
|
|
185 |
# use it.
|
|
186 |
self.assertRaises(NotBranchError, BzrDir.open, ".") |
|
187 |
self.assertEquals(1, len(TrackingProber.seen_urls)) |
|
188 |
TrackingProber.seen_urls = [] |
|
189 |
# Make sure that SafeBranchOpener doesn't use it if no
|
|
190 |
# probers were specified
|
|
191 |
opener = self.makeBranchOpener(["."]) |
|
192 |
self.assertRaises(NotBranchError, opener.open, ".") |
|
193 |
self.assertEquals(0, len(TrackingProber.seen_urls)) |
|
194 |
||
13604.1.6
by Jelmer Vernooij
Move SafeBranchOpener to its own file. |
195 |
def testAllowedURL(self): |
196 |
# checkSource does not raise an exception for branches stacked on
|
|
197 |
# branches with allowed URLs.
|
|
198 |
stacked_on_branch = self.make_branch('base-branch', format='1.6') |
|
199 |
stacked_branch = self.make_branch('stacked-branch', format='1.6') |
|
200 |
stacked_branch.set_stacked_on_url(stacked_on_branch.base) |
|
201 |
opener = self.makeBranchOpener( |
|
202 |
[stacked_branch.base, stacked_on_branch.base]) |
|
203 |
# This doesn't raise an exception.
|
|
204 |
opener.open(stacked_branch.base) |
|
205 |
||
206 |
def testUnstackableRepository(self): |
|
207 |
# checkSource treats branches with UnstackableRepositoryFormats as
|
|
208 |
# being not stacked.
|
|
209 |
branch = self.makeBranch( |
|
210 |
'unstacked', BzrBranchFormat7(), RepositoryFormatKnitPack1()) |
|
211 |
opener = self.makeBranchOpener([branch.base]) |
|
212 |
# This doesn't raise an exception.
|
|
213 |
opener.open(branch.base) |
|
214 |
||
215 |
def testAllowedRelativeURL(self): |
|
216 |
# checkSource passes on absolute urls to checkOneURL, even if the
|
|
217 |
# value of stacked_on_location in the config is set to a relative URL.
|
|
218 |
stacked_on_branch = self.make_branch('base-branch', format='1.6') |
|
219 |
stacked_branch = self.make_branch('stacked-branch', format='1.6') |
|
220 |
stacked_branch.set_stacked_on_url('../base-branch') |
|
221 |
opener = self.makeBranchOpener( |
|
222 |
[stacked_branch.base, stacked_on_branch.base]) |
|
223 |
# Note that stacked_on_branch.base is not '../base-branch', it's an
|
|
224 |
# absolute URL.
|
|
225 |
self.assertNotEqual('../base-branch', stacked_on_branch.base) |
|
226 |
# This doesn't raise an exception.
|
|
227 |
opener.open(stacked_branch.base) |
|
228 |
||
229 |
def testAllowedRelativeNested(self): |
|
230 |
# Relative URLs are resolved relative to the stacked branch.
|
|
231 |
self.get_transport().mkdir('subdir') |
|
232 |
a = self.make_branch('subdir/a', format='1.6') |
|
233 |
b = self.make_branch('b', format='1.6') |
|
234 |
b.set_stacked_on_url('../subdir/a') |
|
235 |
c = self.make_branch('subdir/c', format='1.6') |
|
236 |
c.set_stacked_on_url('../../b') |
|
237 |
opener = self.makeBranchOpener([c.base, b.base, a.base]) |
|
238 |
# This doesn't raise an exception.
|
|
239 |
opener.open(c.base) |
|
240 |
||
241 |
def testForbiddenURL(self): |
|
242 |
# checkSource raises a BadUrl exception if a branch is stacked on a
|
|
243 |
# branch with a forbidden URL.
|
|
244 |
stacked_on_branch = self.make_branch('base-branch', format='1.6') |
|
245 |
stacked_branch = self.make_branch('stacked-branch', format='1.6') |
|
246 |
stacked_branch.set_stacked_on_url(stacked_on_branch.base) |
|
247 |
opener = self.makeBranchOpener([stacked_branch.base]) |
|
248 |
self.assertRaises(BadUrl, opener.open, stacked_branch.base) |
|
249 |
||
250 |
def testForbiddenURLNested(self): |
|
251 |
# checkSource raises a BadUrl exception if a branch is stacked on a
|
|
252 |
# branch that is in turn stacked on a branch with a forbidden URL.
|
|
253 |
a = self.make_branch('a', format='1.6') |
|
254 |
b = self.make_branch('b', format='1.6') |
|
255 |
b.set_stacked_on_url(a.base) |
|
256 |
c = self.make_branch('c', format='1.6') |
|
257 |
c.set_stacked_on_url(b.base) |
|
258 |
opener = self.makeBranchOpener([c.base, b.base]) |
|
259 |
self.assertRaises(BadUrl, opener.open, c.base) |
|
260 |
||
261 |
def testSelfStackedBranch(self): |
|
262 |
# checkSource raises StackingLoopError if a branch is stacked on
|
|
263 |
# itself. This avoids infinite recursion errors.
|
|
264 |
a = self.make_branch('a', format='1.6') |
|
265 |
# Bazaar 1.17 and up make it harder to create branches like this.
|
|
266 |
# It's still worth testing that we don't blow up in the face of them,
|
|
267 |
# so we grovel around a bit to create one anyway.
|
|
268 |
a.get_config().set_user_option('stacked_on_location', a.base) |
|
269 |
opener = self.makeBranchOpener([a.base]) |
|
270 |
self.assertRaises(BranchLoopError, opener.open, a.base) |
|
271 |
||
272 |
def testLoopStackedBranch(self): |
|
273 |
# checkSource raises StackingLoopError if a branch is stacked in such
|
|
274 |
# a way so that it is ultimately stacked on itself. e.g. a stacked on
|
|
275 |
# b stacked on a.
|
|
276 |
a = self.make_branch('a', format='1.6') |
|
277 |
b = self.make_branch('b', format='1.6') |
|
278 |
a.set_stacked_on_url(b.base) |
|
279 |
b.set_stacked_on_url(a.base) |
|
280 |
opener = self.makeBranchOpener([a.base, b.base]) |
|
281 |
self.assertRaises(BranchLoopError, opener.open, a.base) |
|
282 |
self.assertRaises(BranchLoopError, opener.open, b.base) |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
283 |
|
13756.3.14
by Jelmer Vernooij
More tests. |
284 |
def testCustomOpener(self): |
285 |
# A custom function for opening a control dir can be specified.
|
|
286 |
a = self.make_branch('a', format='2a') |
|
287 |
b = self.make_branch('b', format='2a') |
|
288 |
b.set_stacked_on_url(a.base) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
289 |
|
290 |
TrackingProber.seen_urls = [] |
|
291 |
opener = self.makeBranchOpener( |
|
292 |
[a.base, b.base], probers=[TrackingProber]) |
|
293 |
opener.open(b.base) |
|
294 |
self.assertEquals( |
|
295 |
set(TrackingProber.seen_urls), set([b.base, a.base])) |
|
13756.3.14
by Jelmer Vernooij
More tests. |
296 |
|
297 |
def testCustomOpenerWithBranchReference(self): |
|
298 |
# A custom function for opening a control dir can be specified.
|
|
299 |
a = self.make_branch('a', format='2a') |
|
300 |
b_dir = self.make_bzrdir('b') |
|
301 |
b = BranchReferenceFormat().initialize(b_dir, target_branch=a) |
|
14517.1.3
by Jelmer Vernooij
Re-merge always loading of foreign plugins. |
302 |
TrackingProber.seen_urls = [] |
303 |
opener = self.makeBranchOpener( |
|
304 |
[a.base, b.base], probers=[TrackingProber]) |
|
305 |
opener.open(b.base) |
|
306 |
self.assertEquals( |
|
307 |
set(TrackingProber.seen_urls), set([b.base, a.base])) |
|
13756.3.14
by Jelmer Vernooij
More tests. |
308 |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
309 |
|
310 |
class TestSafeOpen(TestCaseWithTransport): |
|
311 |
"""Tests for `safe_open`."""
|
|
312 |
||
13677.5.4
by Jelmer Vernooij
Install hook globally. |
313 |
def setUp(self): |
314 |
super(TestSafeOpen, self).setUp() |
|
315 |
SafeBranchOpener.install_hook() |
|
316 |
||
13677.5.5
by Jelmer Vernooij
more docstrings, tests. |
317 |
def test_hook_does_not_interfere(self): |
318 |
# The transform_fallback_location hook does not interfere with regular
|
|
319 |
# stacked branch access outside of safe_open.
|
|
320 |
self.make_branch('stacked') |
|
321 |
self.make_branch('stacked-on') |
|
322 |
Branch.open('stacked').set_stacked_on_url('../stacked-on') |
|
323 |
Branch.open('stacked') |
|
324 |
||
13637.3.5
by Jelmer Vernooij
Readd tests. |
325 |
def get_chrooted_scheme(self, relpath): |
326 |
"""Create a server that is chrooted to `relpath`.
|
|
327 |
||
328 |
:return: ``(scheme, get_url)`` where ``scheme`` is the scheme of the
|
|
329 |
chroot server and ``get_url`` returns URLs on said server.
|
|
330 |
"""
|
|
331 |
transport = self.get_transport(relpath) |
|
332 |
chroot_server = chroot.ChrootServer(transport) |
|
333 |
chroot_server.start_server() |
|
334 |
self.addCleanup(chroot_server.stop_server) |
|
13811.1.1
by Jeroen Vermeulen
More lint. |
335 |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
336 |
def get_url(relpath): |
337 |
return chroot_server.get_url() + relpath |
|
13811.1.1
by Jeroen Vermeulen
More lint. |
338 |
|
13637.3.5
by Jelmer Vernooij
Readd tests. |
339 |
return URI(chroot_server.get_url()).scheme, get_url |
340 |
||
341 |
def test_stacked_within_scheme(self): |
|
342 |
# A branch that is stacked on a URL of the same scheme is safe to
|
|
343 |
# open.
|
|
344 |
self.get_transport().mkdir('inside') |
|
345 |
self.make_branch('inside/stacked') |
|
346 |
self.make_branch('inside/stacked-on') |
|
347 |
scheme, get_chrooted_url = self.get_chrooted_scheme('inside') |
|
348 |
Branch.open(get_chrooted_url('stacked')).set_stacked_on_url( |
|
349 |
get_chrooted_url('stacked-on')) |
|
350 |
safe_open(scheme, get_chrooted_url('stacked')) |
|
351 |
||
352 |
def test_stacked_outside_scheme(self): |
|
353 |
# A branch that is stacked on a URL that is not of the same scheme is
|
|
354 |
# not safe to open.
|
|
355 |
self.get_transport().mkdir('inside') |
|
356 |
self.get_transport().mkdir('outside') |
|
357 |
self.make_branch('inside/stacked') |
|
358 |
self.make_branch('outside/stacked-on') |
|
359 |
scheme, get_chrooted_url = self.get_chrooted_scheme('inside') |
|
360 |
Branch.open(get_chrooted_url('stacked')).set_stacked_on_url( |
|
361 |
self.get_url('outside/stacked-on')) |
|
362 |
self.assertRaises( |
|
363 |
BadUrl, safe_open, scheme, get_chrooted_url('stacked')) |