From 2f5a27316ff27788810d23027a1f99f060271098 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 10 Aug 2007 18:21:22 -0700 Subject: [PATCH] webish: localfile=/localdir= are now disabled by default, a special switch is required to enable them --- src/allmydata/client.py | 8 +++- src/allmydata/test/test_web.py | 69 +++++++++++++++++++++++++++++++--- src/allmydata/webish.py | 29 +++++++++++++- 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/allmydata/client.py b/src/allmydata/client.py index 8f31a9bb..949e6dc7 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -26,6 +26,7 @@ class Client(node.Node, Referenceable): STOREDIR = 'storage' NODETYPE = "client" WEBPORTFILE = "webport" + WEB_ALLOW_LOCAL_ACCESS_FILE = "webport_allow_localfile" INTRODUCER_FURL_FILE = "introducer.furl" MY_FURL_FILE = "myself.furl" SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline" @@ -49,7 +50,12 @@ class Client(node.Node, Referenceable): f = open(WEBPORTFILE, "r") webport = f.read().strip() # strports string f.close() - self.add_service(WebishServer(webport)) + ws = WebishServer(webport) + local_access_file = os.path.join(self.basedir, + self.WEB_ALLOW_LOCAL_ACCESS_FILE) + if os.path.exists(local_access_file): + ws.allow_local_access(True) + self.add_service(ws) INTRODUCER_FURL_FILE = os.path.join(self.basedir, self.INTRODUCER_FURL_FILE) diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index 3e58cf3e..add61e56 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -158,7 +158,8 @@ class WebMixin(object): def setUp(self): self.s = MyClient() self.s.startService() - s = webish.WebishServer("0") + self.ws = s = webish.WebishServer("0") + s.allow_local_access(True) s.setServiceParent(self.s) port = s.listener._port.getHost().port self.webish_url = "http://localhost:%d" % port @@ -461,11 +462,15 @@ class Web(WebMixin, unittest.TestCase): d.addBoth(self.should404, "test_GET_FILEURL_json_missing") return d + def disable_local_access(self, res=None): + self.ws.allow_local_access(False) + return res + def test_GET_FILEURL_localfile(self): localfile = os.path.abspath("web/GET_FILEURL_localfile") + url = "/vdrive/global/foo/bar.txt?t=download&localfile=%s" % localfile fileutil.make_dirs("web") - d = self.GET("/vdrive/global/foo/bar.txt?t=download&localfile=%s" - % localfile) + d = self.GET(url) def _done(res): self.failUnless(os.path.exists(localfile)) data = open(localfile, "rb").read() @@ -473,6 +478,17 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(_done) return d + def test_GET_FILEURL_localfile_disabled(self): + localfile = os.path.abspath("web/GET_FILEURL_localfile_disabled") + url = "/vdrive/global/foo/bar.txt?t=download&localfile=%s" % localfile + fileutil.make_dirs("web") + self.disable_local_access() + d = self.GET(url) + d.addBoth(self.shouldFail, error.Error, "localfile disabled", + "403 Forbidden", + "local file access is disabled") + return d + def test_GET_FILEURL_localfile_nonlocal(self): # TODO: somehow pretend that we aren't local, and verify that the # server refuses to write to local files, probably by changing the @@ -510,12 +526,12 @@ class Web(WebMixin, unittest.TestCase): def test_PUT_NEWFILEURL_localfile(self): localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile") + url = "/vdrive/global/foo/new.txt?t=upload&localfile=%s" % localfile fileutil.make_dirs("web") f = open(localfile, "wb") f.write(self.NEWFILE_CONTENTS) f.close() - d = self.PUT("/vdrive/global/foo/new.txt?t=upload&localfile=%s" % - localfile, "") + d = self.PUT(url, "") def _check(res): self.failUnless("new.txt" in self._foo_node.children) new_uri = self._foo_node.children["new.txt"] @@ -525,6 +541,20 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(_check) return d + def test_PUT_NEWFILEURL_localfile_disabled(self): + localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile_disabled") + url = "/vdrive/global/foo/new.txt?t=upload&localfile=%s" % localfile + fileutil.make_dirs("web") + f = open(localfile, "wb") + f.write(self.NEWFILE_CONTENTS) + f.close() + self.disable_local_access() + d = self.PUT(url, "") + d.addBoth(self.shouldFail, error.Error, "put localfile disabled", + "403 Forbidden", + "local file access is disabled") + return d + def test_PUT_NEWFILEURL_localfile_mkdirs(self): localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile_mkdirs") fileutil.make_dirs("web") @@ -715,6 +745,16 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(_check) return d + def test_GET_DIRURL_localdir_disabled(self): + localdir = os.path.abspath("web/GET_DIRURL_localdir_disabled") + fileutil.make_dirs("web") + self.disable_local_access() + d = self.GET("/vdrive/global/foo?t=download&localdir=%s" % localdir) + d.addBoth(self.shouldFail, error.Error, "localfile disabled", + "403 Forbidden", + "local file access is disabled") + return d + def test_GET_DIRURL_localdir_nonabsolute(self): localdir = "web/nonabsolute/dirpath" fileutil.make_dirs("web/nonabsolute") @@ -778,6 +818,25 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(_check) return d + def test_PUT_NEWDIRURL_localdir_disabled(self): + localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_disabled") + # create some files there + fileutil.make_dirs(os.path.join(localdir, "one")) + fileutil.make_dirs(os.path.join(localdir, "one/sub")) + fileutil.make_dirs(os.path.join(localdir, "two")) + fileutil.make_dirs(os.path.join(localdir, "three")) + self.touch(localdir, "three/foo.txt") + self.touch(localdir, "three/bar.txt") + self.touch(localdir, "zap.zip") + + self.disable_local_access() + d = self.PUT("/vdrive/global/newdir?t=upload&localdir=%s" + % localdir, "") + d.addBoth(self.shouldFail, error.Error, "localfile disabled", + "403 Forbidden", + "local file access is disabled") + return d + def test_PUT_NEWDIRURL_localdir_mkdirs(self): localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_mkdirs") # create some files there diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index 62a973b5..c4aafc55 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -19,6 +19,9 @@ def getxmlfile(name): class IClient(Interface): pass +class ILocalAccess(Interface): + def allow_local_access(): + pass # we must override twisted.web.http.Request.requestReceived with a version @@ -347,7 +350,15 @@ class NeedAbsolutePathError: req.setHeader("content-type", "text/plain") return "localfile= or localdir= requires an absolute path" - +class LocalAccessDisabledError: + implements(inevow.IResource) + + def renderHTTP(self, ctx): + req = inevow.IRequest(ctx) + req.setResponseCode(http.FORBIDDEN) + req.setHeader("content-type", "text/plain") + return "local file access is disabled" + class LocalFileDownloader(resource.Resource): def __init__(self, filenode, local_filename): @@ -832,6 +843,8 @@ class VDrive(rend.Page): if localdir != os.path.abspath(localdir): return NeedAbsolutePathError(), () if localfile or localdir: + if not ILocalAccess(ctx).allow_local_access(): + return LocalAccessDisabledError(), () if req.getHost().host != LOCALHOST: return NeedLocalhostError(), () # TODO: think about clobbering/revealing config files and node secrets @@ -1032,18 +1045,30 @@ class Root(rend.Page): return T.div[form] +class LocalAccess: + implements(ILocalAccess) + def __init__(self): + self.local_access = False + def allow_local_access(self): + return self.local_access + class WebishServer(service.MultiService): name = "webish" - def __init__(self, webport): + def __init__(self, webport, local_access=False): service.MultiService.__init__(self) self.root = Root() self.site = site = appserver.NevowSite(self.root) self.site.requestFactory = MyRequest + self.allow_local = LocalAccess() + self.site.remember(self.allow_local, ILocalAccess) s = strports.service(webport, site) s.setServiceParent(self) self.listener = s # stash it so the tests can query for the portnum + def allow_local_access(self, enable=True): + self.allow_local.local_access = enable + def startService(self): service.MultiService.startService(self) # to make various services available to render_* methods, we stash a -- 2.45.2