mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-08 04:10:25 +00:00
web: remove /vdrive/private, replace with a start.html file that points at the /uri/PRIVATE_URI, to prevent XSRF attacks
This commit is contained in:
parent
8717905e35
commit
4de5767c98
2
setup.py
2
setup.py
@ -82,7 +82,7 @@ majority of the nodes are no longer available.""",
|
|||||||
"allmydata.scripts",],
|
"allmydata.scripts",],
|
||||||
package_dir={ "allmydata": "src/allmydata",},
|
package_dir={ "allmydata": "src/allmydata",},
|
||||||
scripts = ["bin/allmydata-tahoe"],
|
scripts = ["bin/allmydata-tahoe"],
|
||||||
package_data={ 'allmydata': ['web/*.xhtml', 'web/*.css'] },
|
package_data={ 'allmydata': ['web/*.xhtml', 'web/*.html', 'web/*.css'] },
|
||||||
classifiers=trove_classifiers,
|
classifiers=trove_classifiers,
|
||||||
test_suite="allmydata.test",
|
test_suite="allmydata.test",
|
||||||
)
|
)
|
||||||
|
@ -50,10 +50,7 @@ class Client(node.Node, Referenceable):
|
|||||||
except EnvironmentError:
|
except EnvironmentError:
|
||||||
pass # absent or unreadable webport file
|
pass # absent or unreadable webport file
|
||||||
else:
|
else:
|
||||||
ws = WebishServer(webport)
|
self.init_web(webport)
|
||||||
ws.allow_local_access(os.path.exists(os.path.join(self.basedir,
|
|
||||||
self.WEB_ALLOW_LOCAL_ACCESS_FILE)))
|
|
||||||
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)
|
||||||
@ -100,6 +97,18 @@ class Client(node.Node, Referenceable):
|
|||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
self.push_to_ourselves = True
|
self.push_to_ourselves = True
|
||||||
|
|
||||||
|
def init_web(self, webport):
|
||||||
|
# this must be called after the VirtualDrive is attached
|
||||||
|
ws = WebishServer(webport)
|
||||||
|
ws.allow_local_access(os.path.exists(os.path.join(self.basedir,
|
||||||
|
self.WEB_ALLOW_LOCAL_ACCESS_FILE)))
|
||||||
|
self.add_service(ws)
|
||||||
|
vd = self.getServiceNamed("vdrive")
|
||||||
|
startfile = os.path.join(self.basedir, "start.html")
|
||||||
|
d = vd.when_private_root_available()
|
||||||
|
d.addCallback(ws.create_start_html, startfile)
|
||||||
|
|
||||||
|
|
||||||
def _check_hotline(self, hotline_file):
|
def _check_hotline(self, hotline_file):
|
||||||
if os.path.exists(hotline_file):
|
if os.path.exists(hotline_file):
|
||||||
mtime = os.stat(hotline_file)[stat.ST_MTIME]
|
mtime = os.stat(hotline_file)[stat.ST_MTIME]
|
||||||
|
@ -285,6 +285,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
|
|||||||
d.addCallback(self._check_publish_private)
|
d.addCallback(self._check_publish_private)
|
||||||
d.addCallback(self.log, "did _check_publish_private")
|
d.addCallback(self.log, "did _check_publish_private")
|
||||||
d.addCallback(self._test_web)
|
d.addCallback(self._test_web)
|
||||||
|
d.addCallback(self._test_web_start)
|
||||||
d.addCallback(self._test_runner)
|
d.addCallback(self._test_runner)
|
||||||
return d
|
return d
|
||||||
test_vdrive.timeout = 1100
|
test_vdrive.timeout = 1100
|
||||||
@ -582,6 +583,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
|
|||||||
d.addCallback(lambda res: self.GET("vdrive/global/subdir3/new.txt"))
|
d.addCallback(lambda res: self.GET("vdrive/global/subdir3/new.txt"))
|
||||||
d.addCallback(self.failUnlessEqual, "NEWER contents")
|
d.addCallback(self.failUnlessEqual, "NEWER contents")
|
||||||
|
|
||||||
|
|
||||||
# TODO: mangle the second segment of a file, to test errors that
|
# TODO: mangle the second segment of a file, to test errors that
|
||||||
# occur after we've already sent some good data, which uses a
|
# occur after we've already sent some good data, which uses a
|
||||||
# different error path.
|
# different error path.
|
||||||
@ -594,6 +596,16 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
|
|||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def _test_web_start(self, res):
|
||||||
|
basedir = self.clients[0].basedir
|
||||||
|
startfile = os.path.join(basedir, "start.html")
|
||||||
|
self.failUnless(os.path.exists(startfile))
|
||||||
|
start_html = open(startfile, "r").read()
|
||||||
|
self.failUnless(self.webish_url in start_html)
|
||||||
|
private_uri = self.clients[0].getServiceNamed("vdrive")._private_uri
|
||||||
|
private_url = self.webish_url + "uri/" + private_uri.replace("/","!")
|
||||||
|
self.failUnless(private_url in start_html)
|
||||||
|
|
||||||
def _test_runner(self, res):
|
def _test_runner(self, res):
|
||||||
# exercise some of the diagnostic tools in runner.py
|
# exercise some of the diagnostic tools in runner.py
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import itertools
|
|||||||
|
|
||||||
class MyClient(service.MultiService):
|
class MyClient(service.MultiService):
|
||||||
nodeid = "fake_nodeid"
|
nodeid = "fake_nodeid"
|
||||||
|
basedir = "fake_basedir"
|
||||||
def get_versions(self):
|
def get_versions(self):
|
||||||
return {'allmydata': "fake",
|
return {'allmydata': "fake",
|
||||||
'foolscap': "fake",
|
'foolscap': "fake",
|
||||||
@ -378,10 +379,33 @@ class Web(WebMixin, unittest.TestCase):
|
|||||||
self.failUnless('Welcome To AllMyData' in res)
|
self.failUnless('Welcome To AllMyData' in res)
|
||||||
self.failUnless('Tahoe' in res)
|
self.failUnless('Tahoe' in res)
|
||||||
self.failUnless('To view the global shared filestore' in res)
|
self.failUnless('To view the global shared filestore' in res)
|
||||||
self.failUnless('To view your personal private non-shared' in res)
|
self.failUnless('personal vdrive not available.' in res)
|
||||||
|
|
||||||
|
self.s.basedir = 'web/test_welcome'
|
||||||
|
fileutil.make_dirs("web/test_welcome")
|
||||||
|
self.ws.create_start_html("private_uri",
|
||||||
|
"web/test_welcome/start.html")
|
||||||
|
return self.GET("/")
|
||||||
d.addCallback(_check)
|
d.addCallback(_check)
|
||||||
|
def _check2(res):
|
||||||
|
self.failUnless('To view your personal private non-shared' in res)
|
||||||
|
self.failUnless('from your local filesystem:' in res)
|
||||||
|
self.failUnless(os.path.abspath('web/test_welcome/start.html')
|
||||||
|
in res)
|
||||||
|
d.addCallback(_check2)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def test_start_html(self):
|
||||||
|
fileutil.make_dirs("web")
|
||||||
|
startfile = "web/start.html"
|
||||||
|
self.ws.create_start_html("private_uri", startfile)
|
||||||
|
|
||||||
|
self.failUnless(os.path.exists(startfile))
|
||||||
|
start_html = open(startfile, "r").read()
|
||||||
|
self.failUnless(self.webish_url in start_html)
|
||||||
|
private_url = self.webish_url + "/uri/private_uri"
|
||||||
|
self.failUnless(private_url in start_html)
|
||||||
|
|
||||||
def test_GET_FILEURL(self):
|
def test_GET_FILEURL(self):
|
||||||
d = self.GET("/vdrive/global/foo/bar.txt")
|
d = self.GET("/vdrive/global/foo/bar.txt")
|
||||||
d.addCallback(self.failUnlessIsBarDotTxt)
|
d.addCallback(self.failUnlessIsBarDotTxt)
|
||||||
@ -646,13 +670,6 @@ class Web(WebMixin, unittest.TestCase):
|
|||||||
'</td>\s+<td>DIR-RO</td>', res))
|
'</td>\s+<td>DIR-RO</td>', res))
|
||||||
d.addCallback(_check3)
|
d.addCallback(_check3)
|
||||||
|
|
||||||
# and take a quick peek at the private vdrive
|
|
||||||
d.addCallback(lambda res:
|
|
||||||
self.GET("/vdrive/private", followRedirect=True))
|
|
||||||
def _check4(res):
|
|
||||||
pass
|
|
||||||
d.addCallback(_check4)
|
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_GET_DIRURL_json(self):
|
def test_GET_DIRURL_json(self):
|
||||||
|
@ -3,6 +3,7 @@ import os
|
|||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI
|
from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI
|
||||||
|
from allmydata.util import observer
|
||||||
from allmydata import dirnode
|
from allmydata import dirnode
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ class VirtualDrive(service.MultiService):
|
|||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
self._global_uri = None
|
self._global_uri = None
|
||||||
self._private_uri = None
|
self._private_uri = None
|
||||||
|
self._private_root_observer = observer.OneShotObserverList()
|
||||||
|
|
||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
self.parent.log(msg)
|
self.parent.log(msg)
|
||||||
@ -46,6 +48,7 @@ class VirtualDrive(service.MultiService):
|
|||||||
self._private_uri = f.read().strip()
|
self._private_uri = f.read().strip()
|
||||||
f.close()
|
f.close()
|
||||||
self.log("using private vdrive uri %s" % self._private_uri)
|
self.log("using private vdrive uri %s" % self._private_uri)
|
||||||
|
self._private_root_observer.fire(self._private_uri)
|
||||||
|
|
||||||
furl_file = os.path.join(basedir, self.GLOBAL_VDRIVE_FURL_FILE)
|
furl_file = os.path.join(basedir, self.GLOBAL_VDRIVE_FURL_FILE)
|
||||||
if os.path.exists(furl_file):
|
if os.path.exists(furl_file):
|
||||||
@ -94,6 +97,7 @@ class VirtualDrive(service.MultiService):
|
|||||||
f = open(private_uri_file, "w")
|
f = open(private_uri_file, "w")
|
||||||
f.write(self._private_uri + "\n")
|
f.write(self._private_uri + "\n")
|
||||||
f.close()
|
f.close()
|
||||||
|
self._private_root_observer.fire(self._private_uri)
|
||||||
d.addCallback(_got_directory)
|
d.addCallback(_got_directory)
|
||||||
|
|
||||||
|
|
||||||
@ -104,6 +108,15 @@ class VirtualDrive(service.MultiService):
|
|||||||
return defer.fail(NoGlobalVirtualDriveError())
|
return defer.fail(NoGlobalVirtualDriveError())
|
||||||
return self.get_node(self._global_uri)
|
return self.get_node(self._global_uri)
|
||||||
|
|
||||||
|
def when_private_root_available(self):
|
||||||
|
"""Return a Deferred that will fire with the URI of the private
|
||||||
|
vdrive root, when it is available.
|
||||||
|
|
||||||
|
This might be right away if the private vdrive was already present.
|
||||||
|
The first time the node is started, this will take a bit longer.
|
||||||
|
"""
|
||||||
|
return self._private_root_observer.when_fired()
|
||||||
|
|
||||||
def have_private_root(self):
|
def have_private_root(self):
|
||||||
return bool(self._private_uri)
|
return bool(self._private_uri)
|
||||||
def get_private_root(self):
|
def get_private_root(self):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
from base64 import b32encode
|
from base64 import b32encode
|
||||||
import os.path
|
import os.path
|
||||||
from twisted.application import service, strports
|
from twisted.application import service, strports, internet
|
||||||
from twisted.web import static, resource, server, html, http
|
from twisted.web import static, resource, server, html, http
|
||||||
from twisted.python import util, log
|
from twisted.python import util, log
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
@ -1021,9 +1021,6 @@ class Root(rend.Page):
|
|||||||
if segments[1] == "global":
|
if segments[1] == "global":
|
||||||
d = vdrive.get_public_root()
|
d = vdrive.get_public_root()
|
||||||
name = "public vdrive"
|
name = "public vdrive"
|
||||||
elif segments[1] == "private":
|
|
||||||
d = vdrive.get_private_root()
|
|
||||||
name = "private vdrive"
|
|
||||||
else:
|
else:
|
||||||
return rend.NotFound
|
return rend.NotFound
|
||||||
d.addCallback(lambda dirnode: VDrive(dirnode, name))
|
d.addCallback(lambda dirnode: VDrive(dirnode, name))
|
||||||
@ -1099,9 +1096,13 @@ class Root(rend.Page):
|
|||||||
"responding), no vdrive available."]
|
"responding), no vdrive available."]
|
||||||
|
|
||||||
def render_private_vdrive(self, ctx, data):
|
def render_private_vdrive(self, ctx, data):
|
||||||
if IClient(ctx).getServiceNamed("vdrive").have_private_root():
|
basedir = IClient(ctx).basedir
|
||||||
|
start_html = os.path.abspath(os.path.join(basedir, "start.html"))
|
||||||
|
if os.path.exists(start_html):
|
||||||
return T.p["To view your personal private non-shared filestore, ",
|
return T.p["To view your personal private non-shared filestore, ",
|
||||||
T.a(href="vdrive/private")["Click Here!"],
|
"use this browser to open the following file from ",
|
||||||
|
"your local filesystem:",
|
||||||
|
T.pre[start_html],
|
||||||
]
|
]
|
||||||
return T.p["personal vdrive not available."]
|
return T.p["personal vdrive not available."]
|
||||||
|
|
||||||
@ -1131,8 +1132,9 @@ class LocalAccess:
|
|||||||
class WebishServer(service.MultiService):
|
class WebishServer(service.MultiService):
|
||||||
name = "webish"
|
name = "webish"
|
||||||
|
|
||||||
def __init__(self, webport, local_access=False):
|
def __init__(self, webport):
|
||||||
service.MultiService.__init__(self)
|
service.MultiService.__init__(self)
|
||||||
|
self.webport = webport
|
||||||
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
|
||||||
@ -1155,3 +1157,23 @@ class WebishServer(service.MultiService):
|
|||||||
# I thought you could do the same with an existing interface, but
|
# I thought you could do the same with an existing interface, but
|
||||||
# apparently 'ISite' does not exist
|
# apparently 'ISite' does not exist
|
||||||
#self.site._client = self.parent
|
#self.site._client = self.parent
|
||||||
|
|
||||||
|
def create_start_html(self, private_uri, startfile):
|
||||||
|
f = open(startfile, "w")
|
||||||
|
os.chmod(startfile, 0600)
|
||||||
|
template = open(util.sibpath(__file__, "web/start.html"), "r").read()
|
||||||
|
# what is our webport?
|
||||||
|
s = self.listener
|
||||||
|
if isinstance(s, internet.TCPServer):
|
||||||
|
base_url = "http://localhost:%d" % s._port.getHost().port
|
||||||
|
elif isinstance(s, internet.SSLServer):
|
||||||
|
base_url = "https://localhost:%d" % s._port.getHost().port
|
||||||
|
else:
|
||||||
|
base_url = "UNKNOWN" # this will break the href
|
||||||
|
# TODO: emit a start.html that explains that we don't know
|
||||||
|
# how to create a suitable URL
|
||||||
|
fields = {"private_uri": private_uri.replace("/","!"),
|
||||||
|
"base_url": base_url,
|
||||||
|
}
|
||||||
|
f.write(template % fields)
|
||||||
|
f.close()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user