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",], "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",
) )

View File

@ -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]

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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()