webish: localfile=/localdir= are now disabled by default, a special switch is required to enable them
This commit is contained in:
parent
b4a0effe30
commit
2f5a27316f
|
@ -26,6 +26,7 @@ class Client(node.Node, Referenceable):
|
||||||
STOREDIR = 'storage'
|
STOREDIR = 'storage'
|
||||||
NODETYPE = "client"
|
NODETYPE = "client"
|
||||||
WEBPORTFILE = "webport"
|
WEBPORTFILE = "webport"
|
||||||
|
WEB_ALLOW_LOCAL_ACCESS_FILE = "webport_allow_localfile"
|
||||||
INTRODUCER_FURL_FILE = "introducer.furl"
|
INTRODUCER_FURL_FILE = "introducer.furl"
|
||||||
MY_FURL_FILE = "myself.furl"
|
MY_FURL_FILE = "myself.furl"
|
||||||
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
|
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
|
||||||
|
@ -49,7 +50,12 @@ class Client(node.Node, Referenceable):
|
||||||
f = open(WEBPORTFILE, "r")
|
f = open(WEBPORTFILE, "r")
|
||||||
webport = f.read().strip() # strports string
|
webport = f.read().strip() # strports string
|
||||||
f.close()
|
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,
|
INTRODUCER_FURL_FILE = os.path.join(self.basedir,
|
||||||
self.INTRODUCER_FURL_FILE)
|
self.INTRODUCER_FURL_FILE)
|
||||||
|
|
|
@ -158,7 +158,8 @@ class WebMixin(object):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.s = MyClient()
|
self.s = MyClient()
|
||||||
self.s.startService()
|
self.s.startService()
|
||||||
s = webish.WebishServer("0")
|
self.ws = s = webish.WebishServer("0")
|
||||||
|
s.allow_local_access(True)
|
||||||
s.setServiceParent(self.s)
|
s.setServiceParent(self.s)
|
||||||
port = s.listener._port.getHost().port
|
port = s.listener._port.getHost().port
|
||||||
self.webish_url = "http://localhost:%d" % 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")
|
d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def disable_local_access(self, res=None):
|
||||||
|
self.ws.allow_local_access(False)
|
||||||
|
return res
|
||||||
|
|
||||||
def test_GET_FILEURL_localfile(self):
|
def test_GET_FILEURL_localfile(self):
|
||||||
localfile = os.path.abspath("web/GET_FILEURL_localfile")
|
localfile = os.path.abspath("web/GET_FILEURL_localfile")
|
||||||
|
url = "/vdrive/global/foo/bar.txt?t=download&localfile=%s" % localfile
|
||||||
fileutil.make_dirs("web")
|
fileutil.make_dirs("web")
|
||||||
d = self.GET("/vdrive/global/foo/bar.txt?t=download&localfile=%s"
|
d = self.GET(url)
|
||||||
% localfile)
|
|
||||||
def _done(res):
|
def _done(res):
|
||||||
self.failUnless(os.path.exists(localfile))
|
self.failUnless(os.path.exists(localfile))
|
||||||
data = open(localfile, "rb").read()
|
data = open(localfile, "rb").read()
|
||||||
|
@ -473,6 +478,17 @@ class Web(WebMixin, unittest.TestCase):
|
||||||
d.addCallback(_done)
|
d.addCallback(_done)
|
||||||
return d
|
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):
|
def test_GET_FILEURL_localfile_nonlocal(self):
|
||||||
# TODO: somehow pretend that we aren't local, and verify that the
|
# TODO: somehow pretend that we aren't local, and verify that the
|
||||||
# server refuses to write to local files, probably by changing 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):
|
def test_PUT_NEWFILEURL_localfile(self):
|
||||||
localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile")
|
localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile")
|
||||||
|
url = "/vdrive/global/foo/new.txt?t=upload&localfile=%s" % localfile
|
||||||
fileutil.make_dirs("web")
|
fileutil.make_dirs("web")
|
||||||
f = open(localfile, "wb")
|
f = open(localfile, "wb")
|
||||||
f.write(self.NEWFILE_CONTENTS)
|
f.write(self.NEWFILE_CONTENTS)
|
||||||
f.close()
|
f.close()
|
||||||
d = self.PUT("/vdrive/global/foo/new.txt?t=upload&localfile=%s" %
|
d = self.PUT(url, "")
|
||||||
localfile, "")
|
|
||||||
def _check(res):
|
def _check(res):
|
||||||
self.failUnless("new.txt" in self._foo_node.children)
|
self.failUnless("new.txt" in self._foo_node.children)
|
||||||
new_uri = self._foo_node.children["new.txt"]
|
new_uri = self._foo_node.children["new.txt"]
|
||||||
|
@ -525,6 +541,20 @@ class Web(WebMixin, unittest.TestCase):
|
||||||
d.addCallback(_check)
|
d.addCallback(_check)
|
||||||
return d
|
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):
|
def test_PUT_NEWFILEURL_localfile_mkdirs(self):
|
||||||
localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile_mkdirs")
|
localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile_mkdirs")
|
||||||
fileutil.make_dirs("web")
|
fileutil.make_dirs("web")
|
||||||
|
@ -715,6 +745,16 @@ class Web(WebMixin, unittest.TestCase):
|
||||||
d.addCallback(_check)
|
d.addCallback(_check)
|
||||||
return d
|
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):
|
def test_GET_DIRURL_localdir_nonabsolute(self):
|
||||||
localdir = "web/nonabsolute/dirpath"
|
localdir = "web/nonabsolute/dirpath"
|
||||||
fileutil.make_dirs("web/nonabsolute")
|
fileutil.make_dirs("web/nonabsolute")
|
||||||
|
@ -778,6 +818,25 @@ class Web(WebMixin, unittest.TestCase):
|
||||||
d.addCallback(_check)
|
d.addCallback(_check)
|
||||||
return d
|
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):
|
def test_PUT_NEWDIRURL_localdir_mkdirs(self):
|
||||||
localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_mkdirs")
|
localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_mkdirs")
|
||||||
# create some files there
|
# create some files there
|
||||||
|
|
|
@ -19,6 +19,9 @@ def getxmlfile(name):
|
||||||
|
|
||||||
class IClient(Interface):
|
class IClient(Interface):
|
||||||
pass
|
pass
|
||||||
|
class ILocalAccess(Interface):
|
||||||
|
def allow_local_access():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# we must override twisted.web.http.Request.requestReceived with a version
|
# we must override twisted.web.http.Request.requestReceived with a version
|
||||||
|
@ -347,6 +350,14 @@ class NeedAbsolutePathError:
|
||||||
req.setHeader("content-type", "text/plain")
|
req.setHeader("content-type", "text/plain")
|
||||||
return "localfile= or localdir= requires an absolute path"
|
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):
|
class LocalFileDownloader(resource.Resource):
|
||||||
|
@ -832,6 +843,8 @@ class VDrive(rend.Page):
|
||||||
if localdir != os.path.abspath(localdir):
|
if localdir != os.path.abspath(localdir):
|
||||||
return NeedAbsolutePathError(), ()
|
return NeedAbsolutePathError(), ()
|
||||||
if localfile or localdir:
|
if localfile or localdir:
|
||||||
|
if not ILocalAccess(ctx).allow_local_access():
|
||||||
|
return LocalAccessDisabledError(), ()
|
||||||
if req.getHost().host != LOCALHOST:
|
if req.getHost().host != LOCALHOST:
|
||||||
return NeedLocalhostError(), ()
|
return NeedLocalhostError(), ()
|
||||||
# TODO: think about clobbering/revealing config files and node secrets
|
# TODO: think about clobbering/revealing config files and node secrets
|
||||||
|
@ -1032,18 +1045,30 @@ class Root(rend.Page):
|
||||||
return T.div[form]
|
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):
|
class WebishServer(service.MultiService):
|
||||||
name = "webish"
|
name = "webish"
|
||||||
|
|
||||||
def __init__(self, webport):
|
def __init__(self, webport, local_access=False):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
self.root = Root()
|
self.root = Root()
|
||||||
self.site = site = appserver.NevowSite(self.root)
|
self.site = site = appserver.NevowSite(self.root)
|
||||||
self.site.requestFactory = MyRequest
|
self.site.requestFactory = MyRequest
|
||||||
|
self.allow_local = LocalAccess()
|
||||||
|
self.site.remember(self.allow_local, ILocalAccess)
|
||||||
s = strports.service(webport, site)
|
s = strports.service(webport, site)
|
||||||
s.setServiceParent(self)
|
s.setServiceParent(self)
|
||||||
self.listener = s # stash it so the tests can query for the portnum
|
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):
|
def startService(self):
|
||||||
service.MultiService.startService(self)
|
service.MultiService.startService(self)
|
||||||
# to make various services available to render_* methods, we stash a
|
# to make various services available to render_* methods, we stash a
|
||||||
|
|
Loading…
Reference in New Issue