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:
Brian Warner 2007-08-22 14:54:34 -07:00
parent 8717905e35
commit 4de5767c98
6 changed files with 93 additions and 20 deletions

View File

@ -82,7 +82,7 @@ majority of the nodes are no longer available.""",
"allmydata.scripts",],
package_dir={ "allmydata": "src/allmydata",},
scripts = ["bin/allmydata-tahoe"],
package_data={ 'allmydata': ['web/*.xhtml', 'web/*.css'] },
package_data={ 'allmydata': ['web/*.xhtml', 'web/*.html', 'web/*.css'] },
classifiers=trove_classifiers,
test_suite="allmydata.test",
)

View File

@ -50,10 +50,7 @@ class Client(node.Node, Referenceable):
except EnvironmentError:
pass # absent or unreadable webport file
else:
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)
self.init_web(webport)
INTRODUCER_FURL_FILE = os.path.join(self.basedir,
self.INTRODUCER_FURL_FILE)
@ -100,6 +97,18 @@ class Client(node.Node, Referenceable):
if os.path.exists(filename):
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):
if os.path.exists(hotline_file):
mtime = os.stat(hotline_file)[stat.ST_MTIME]

View File

@ -285,6 +285,7 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
d.addCallback(self._check_publish_private)
d.addCallback(self.log, "did _check_publish_private")
d.addCallback(self._test_web)
d.addCallback(self._test_web_start)
d.addCallback(self._test_runner)
return d
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(self.failUnlessEqual, "NEWER contents")
# TODO: mangle the second segment of a file, to test errors that
# occur after we've already sent some good data, which uses a
# different error path.
@ -594,6 +596,16 @@ class SystemTest(testutil.SignalMixin, unittest.TestCase):
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):
# exercise some of the diagnostic tools in runner.py

View File

@ -16,6 +16,7 @@ import itertools
class MyClient(service.MultiService):
nodeid = "fake_nodeid"
basedir = "fake_basedir"
def get_versions(self):
return {'allmydata': "fake",
'foolscap': "fake",
@ -378,10 +379,33 @@ class Web(WebMixin, unittest.TestCase):
self.failUnless('Welcome To AllMyData' in res)
self.failUnless('Tahoe' 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)
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
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):
d = self.GET("/vdrive/global/foo/bar.txt")
d.addCallback(self.failUnlessIsBarDotTxt)
@ -646,13 +670,6 @@ class Web(WebMixin, unittest.TestCase):
'</td>\s+<td>DIR-RO</td>', res))
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
def test_GET_DIRURL_json(self):

View File

@ -3,6 +3,7 @@ import os
from twisted.application import service
from zope.interface import implements
from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI
from allmydata.util import observer
from allmydata import dirnode
from twisted.internet import defer
@ -24,6 +25,7 @@ class VirtualDrive(service.MultiService):
service.MultiService.__init__(self)
self._global_uri = None
self._private_uri = None
self._private_root_observer = observer.OneShotObserverList()
def log(self, msg):
self.parent.log(msg)
@ -46,6 +48,7 @@ class VirtualDrive(service.MultiService):
self._private_uri = f.read().strip()
f.close()
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)
if os.path.exists(furl_file):
@ -94,6 +97,7 @@ class VirtualDrive(service.MultiService):
f = open(private_uri_file, "w")
f.write(self._private_uri + "\n")
f.close()
self._private_root_observer.fire(self._private_uri)
d.addCallback(_got_directory)
@ -104,6 +108,15 @@ class VirtualDrive(service.MultiService):
return defer.fail(NoGlobalVirtualDriveError())
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):
return bool(self._private_uri)
def get_private_root(self):

View File

@ -1,7 +1,7 @@
from base64 import b32encode
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.python import util, log
from twisted.internet import defer
@ -1021,9 +1021,6 @@ class Root(rend.Page):
if segments[1] == "global":
d = vdrive.get_public_root()
name = "public vdrive"
elif segments[1] == "private":
d = vdrive.get_private_root()
name = "private vdrive"
else:
return rend.NotFound
d.addCallback(lambda dirnode: VDrive(dirnode, name))
@ -1099,9 +1096,13 @@ class Root(rend.Page):
"responding), no vdrive available."]
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, ",
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."]
@ -1131,8 +1132,9 @@ class LocalAccess:
class WebishServer(service.MultiService):
name = "webish"
def __init__(self, webport, local_access=False):
def __init__(self, webport):
service.MultiService.__init__(self)
self.webport = webport
self.root = Root()
self.site = site = appserver.NevowSite(self.root)
self.site.requestFactory = MyRequest
@ -1155,3 +1157,23 @@ class WebishServer(service.MultiService):
# I thought you could do the same with an existing interface, but
# apparently 'ISite' does not exist
#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()