launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04622
[Merge] lp:~jelmer/launchpad/safe-opener-threading into lp:launchpad
Jelmer Vernooij has proposed merging lp:~jelmer/launchpad/safe-opener-threading into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #826082 in Launchpad itself: "BadUrl from safe_open opening a branch"
https://bugs.launchpad.net/launchpad/+bug/826082
Bug #826136 in loggerhead: "dictionary changed size during iteration"
https://bugs.launchpad.net/loggerhead/+bug/826136
For more details, see:
https://code.launchpad.net/~jelmer/launchpad/safe-opener-threading/+merge/71670
Make SafeBranchOpener threadsafe.
This fixes some fallout from a change to reconcile the various safe branch openers in launchpad, which was breaking loggerhead.
--
https://code.launchpad.net/~jelmer/launchpad/safe-opener-threading/+merge/71670
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jelmer/launchpad/safe-opener-threading into lp:launchpad.
=== modified file 'lib/lp/codehosting/bzrutils.py'
--- lib/lp/codehosting/bzrutils.py 2011-08-09 14:53:07 +0000
+++ lib/lp/codehosting/bzrutils.py 2011-08-16 12:04:26 +0000
@@ -47,6 +47,7 @@
unregister_transport,
)
from bzrlib.transport.local import LocalTransport
+import lp.codehosting.safe_open
from lazr.uri import URI
from canonical.launchpad.webapp.errorlog import (
=== modified file 'lib/lp/codehosting/safe_open.py'
--- lib/lp/codehosting/safe_open.py 2011-08-09 14:59:08 +0000
+++ lib/lp/codehosting/safe_open.py 2011-08-16 12:04:26 +0000
@@ -11,6 +11,8 @@
from lazr.uri import URI
+import threading
+
__all__ = [
'AcceptAnythingPolicy',
'BadUrl',
@@ -150,19 +152,59 @@
return urlutils.join(branch.base, url), self.check
+class SingleSchemePolicy(BranchOpenPolicy):
+ """Branch open policy that rejects URLs not on the given scheme."""
+
+ def __init__(self, allowed_scheme):
+ self.allowed_scheme = allowed_scheme
+
+ def shouldFollowReferences(self):
+ return True
+
+ def transformFallbackLocation(self, branch, url):
+ return urlutils.join(branch.base, url), True
+
+ def checkOneURL(self, url):
+ """Check that `url` is safe to open."""
+ if URI(url).scheme != self.allowed_scheme:
+ raise BadUrl(url)
+
+
class SafeBranchOpener(object):
"""Safe branch opener.
+ All locations that are opened (stacked-on branches, references) are checked
+ against a policy object.
+
The policy object is expected to have the following methods:
* checkOneURL
* shouldFollowReferences
* transformFallbackLocation
"""
+ _threading_data = threading.local()
+
def __init__(self, policy):
self.policy = policy
self._seen_urls = set()
+ @classmethod
+ def install_hook(cls):
+ """Install the ``transformFallbackLocation`` hook.
+
+ This is done at module import time, but transformFallbackLocationHook
+ doesn't do anything unless the `_active_openers` threading.Local object
+ has a 'opener' attribute in this thread.
+
+ This is in a module-level function rather than performed at module level
+ so that it can be called in setUp for testing `SafeBranchOpener` as
+ bzrlib.tests.TestCase.setUp clears hooks.
+ """
+ Branch.hooks.install_named_hook(
+ 'transform_fallback_location',
+ cls.transformFallbackLocationHook,
+ 'SafeBranchOpener.transformFallbackLocationHook')
+
def checkAndFollowBranchReference(self, url):
"""Check URL (and possibly the referenced URL) for safety.
@@ -187,32 +229,33 @@
if not self.policy.shouldFollowReferences():
raise BranchReferenceForbidden(url)
- def transformFallbackLocationHook(self, branch, url):
+ @classmethod
+ def transformFallbackLocationHook(cls, branch, url):
"""Installed as the 'transform_fallback_location' Branch hook.
This method calls `transformFallbackLocation` on the policy object and
either returns the url it provides or passes it back to
checkAndFollowBranchReference.
"""
- new_url, check = self.policy.transformFallbackLocation(branch, url)
+ try:
+ opener = getattr(cls._threading_data, "opener")
+ except AttributeError:
+ return url
+ new_url, check = opener.policy.transformFallbackLocation(branch, url)
if check:
- return self.checkAndFollowBranchReference(new_url)
+ return opener.checkAndFollowBranchReference(new_url)
else:
return new_url
def runWithTransformFallbackLocationHookInstalled(
self, callable, *args, **kw):
- Branch.hooks.install_named_hook(
- 'transform_fallback_location', self.transformFallbackLocationHook,
- 'SafeBranchOpener.transformFallbackLocationHook')
+ assert (self.transformFallbackLocationHook in
+ Branch.hooks['transform_fallback_location'])
+ self._threading_data.opener = self
try:
return callable(*args, **kw)
finally:
- # XXX 2008-11-24 MichaelHudson, bug=301472: This is the hacky way
- # to remove a hook. The linked bug report asks for an API to do
- # it.
- Branch.hooks['transform_fallback_location'].remove(
- self.transformFallbackLocationHook)
+ del self._threading_data.opener
# We reset _seen_urls here to avoid multiple calls to open giving
# spurious loop exceptions.
self._seen_urls = set()
@@ -236,28 +279,13 @@
Branch.open, url)
-class URLChecker(BranchOpenPolicy):
- """Branch open policy that rejects URLs not on the given scheme."""
-
- def __init__(self, allowed_scheme):
- self.allowed_scheme = allowed_scheme
-
- def shouldFollowReferences(self):
- return True
-
- def transformFallbackLocation(self, branch, url):
- return urlutils.join(branch.base, url), True
-
- def checkOneURL(self, url):
- """Check that `url` is safe to open."""
- if URI(url).scheme != self.allowed_scheme:
- raise BadUrl(url)
-
-
def safe_open(allowed_scheme, url):
"""Open the branch at `url`, only accessing URLs on `allowed_scheme`.
:raises BadUrl: An attempt was made to open a URL that was not on
`allowed_scheme`.
"""
- return SafeBranchOpener(URLChecker(allowed_scheme)).open(url)
+ return SafeBranchOpener(SingleSchemePolicy(allowed_scheme)).open(url)
+
+
+SafeBranchOpener.install_hook()
=== modified file 'lib/lp/codehosting/tests/test_safe_open.py'
--- lib/lp/codehosting/tests/test_safe_open.py 2011-08-09 15:05:18 +0000
+++ lib/lp/codehosting/tests/test_safe_open.py 2011-08-16 12:04:26 +0000
@@ -37,6 +37,10 @@
class TestSafeBranchOpenerCheckAndFollowBranchReference(TestCase):
"""Unit tests for `SafeBranchOpener.checkAndFollowBranchReference`."""
+ def setUp(self):
+ super(TestSafeBranchOpenerCheckAndFollowBranchReference, self).setUp()
+ SafeBranchOpener.install_hook()
+
class StubbedSafeBranchOpener(SafeBranchOpener):
"""SafeBranchOpener that provides canned answers.
@@ -123,6 +127,10 @@
class TestSafeBranchOpenerStacking(TestCaseWithTransport):
+ def setUp(self):
+ super(TestSafeBranchOpenerStacking, self).setUp()
+ SafeBranchOpener.install_hook()
+
def makeBranchOpener(self, allowed_urls):
policy = WhitelistPolicy(True, allowed_urls, True)
return SafeBranchOpener(policy)
@@ -228,6 +236,18 @@
class TestSafeOpen(TestCaseWithTransport):
"""Tests for `safe_open`."""
+ def setUp(self):
+ super(TestSafeOpen, self).setUp()
+ SafeBranchOpener.install_hook()
+
+ def test_hook_does_not_interfere(self):
+ # The transform_fallback_location hook does not interfere with regular
+ # stacked branch access outside of safe_open.
+ self.make_branch('stacked')
+ self.make_branch('stacked-on')
+ Branch.open('stacked').set_stacked_on_url('../stacked-on')
+ Branch.open('stacked')
+
def get_chrooted_scheme(self, relpath):
"""Create a server that is chrooted to `relpath`.