mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Do not allow downloads of mutable databases - closes #474
This commit is contained in:
parent
88976d6cd6
commit
f4eefdf193
4 changed files with 35 additions and 25 deletions
|
|
@ -56,7 +56,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if config.allow_download and database != ":memory:" %}
|
{% if allow_download %}
|
||||||
<p class="download-sqlite">Download SQLite DB: <a href="{{ database_url(database) }}.db">{{ database }}.db</a> <em>{{ format_bytes(size) }}</em></p>
|
<p class="download-sqlite">Download SQLite DB: <a href="{{ database_url(database) }}.db">{{ database }}.db</a> <em>{{ format_bytes(size) }}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ class DatabaseView(BaseView):
|
||||||
"show_hidden": request.args.get("_show_hidden"),
|
"show_hidden": request.args.get("_show_hidden"),
|
||||||
"editable": True,
|
"editable": True,
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
|
"allow_download": self.ds.config("allow_download")
|
||||||
|
and not db.is_mutable
|
||||||
|
and database != ":memory:",
|
||||||
},
|
},
|
||||||
("database-{}.html".format(to_css_class(database)), "database.html"),
|
("database-{}.html".format(to_css_class(database)), "database.html"),
|
||||||
)
|
)
|
||||||
|
|
@ -76,13 +79,13 @@ class DatabaseDownload(BaseView):
|
||||||
name = "database_download"
|
name = "database_download"
|
||||||
|
|
||||||
async def view_get(self, request, database, hash, correct_hash_present, **kwargs):
|
async def view_get(self, request, database, hash, correct_hash_present, **kwargs):
|
||||||
if not self.ds.config("allow_download"):
|
|
||||||
raise DatasetteError("Database download is forbidden", status=403)
|
|
||||||
if database not in self.ds.databases:
|
if database not in self.ds.databases:
|
||||||
raise DatasetteError("Invalid database", status=404)
|
raise DatasetteError("Invalid database", status=404)
|
||||||
db = self.ds.databases[database]
|
db = self.ds.databases[database]
|
||||||
if db.is_memory:
|
if db.is_memory:
|
||||||
raise DatasetteError("Cannot download :memory: database", status=404)
|
raise DatasetteError("Cannot download :memory: database", status=404)
|
||||||
|
if not self.ds.config("allow_download") or db.is_mutable:
|
||||||
|
raise DatasetteError("Database download is forbidden", status=403)
|
||||||
if not db.path:
|
if not db.path:
|
||||||
raise DatasetteError("Cannot download database", status=404)
|
raise DatasetteError("Cannot download database", status=404)
|
||||||
filepath = db.path
|
filepath = db.path
|
||||||
|
|
|
||||||
|
|
@ -107,11 +107,6 @@ def app_client_two_attached_databases_one_immutable():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_client_with_memory():
|
|
||||||
yield from make_app_client(memory=True)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def app_client_with_hash():
|
def app_client_with_hash():
|
||||||
yield from make_app_client(config={"hash_urls": True}, is_immutable=True)
|
yield from make_app_client(config={"hash_urls": True}, is_immutable=True)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ from .fixtures import ( # noqa
|
||||||
app_client_shorter_time_limit,
|
app_client_shorter_time_limit,
|
||||||
app_client_two_attached_databases,
|
app_client_two_attached_databases,
|
||||||
app_client_with_hash,
|
app_client_with_hash,
|
||||||
app_client_with_memory,
|
|
||||||
make_app_client,
|
make_app_client,
|
||||||
METADATA,
|
METADATA,
|
||||||
)
|
)
|
||||||
|
|
@ -45,9 +44,10 @@ def test_homepage(app_client_two_attached_databases):
|
||||||
] == table_links
|
] == table_links
|
||||||
|
|
||||||
|
|
||||||
def test_memory_database_page(app_client_with_memory):
|
def test_memory_database_page():
|
||||||
response = app_client_with_memory.get("/:memory:")
|
for client in make_app_client(memory=True):
|
||||||
assert response.status == 200
|
response = client.get("/:memory:")
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
def test_database_page_redirects_with_url_hash(app_client_with_hash):
|
def test_database_page_redirects_with_url_hash(app_client_with_hash):
|
||||||
|
|
@ -724,23 +724,35 @@ def test_table_metadata(app_client):
|
||||||
assert_footer_links(soup)
|
assert_footer_links(soup)
|
||||||
|
|
||||||
|
|
||||||
def test_database_download(app_client_with_memory):
|
def test_database_download_allowed_for_immutable():
|
||||||
# Regular page should have a download link
|
for client in make_app_client(is_immutable=True):
|
||||||
response = app_client_with_memory.get("/fixtures")
|
assert not client.ds.databases["fixtures"].is_mutable
|
||||||
|
# Regular page should have a download link
|
||||||
|
response = client.get("/fixtures")
|
||||||
|
soup = Soup(response.body, "html.parser")
|
||||||
|
assert len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
||||||
|
# Check we can actually download it
|
||||||
|
assert 200 == client.get("/fixtures.db").status
|
||||||
|
|
||||||
|
|
||||||
|
def test_database_download_disallowed_for_mutable(app_client):
|
||||||
|
response = app_client.get("/fixtures")
|
||||||
soup = Soup(response.body, "html.parser")
|
soup = Soup(response.body, "html.parser")
|
||||||
assert len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
assert 0 == len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
||||||
# Check we can actually download it
|
assert 403 == app_client.get("/fixtures.db").status
|
||||||
assert 200 == app_client_with_memory.get("/fixtures.db").status
|
|
||||||
# Memory page should NOT have a download link
|
|
||||||
response2 = app_client_with_memory.get("/:memory:")
|
def test_database_download_disallowed_for_memory():
|
||||||
soup2 = Soup(response2.body, "html.parser")
|
for client in make_app_client(memory=True):
|
||||||
assert 0 == len(soup2.findAll("a", {"href": re.compile(r"\.db$")}))
|
# Memory page should NOT have a download link
|
||||||
# The URL itself should 404
|
response = client.get("/:memory:")
|
||||||
assert 404 == app_client_with_memory.get("/:memory:.db").status
|
soup = Soup(response.body, "html.parser")
|
||||||
|
assert 0 == len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
||||||
|
assert 404 == client.get("/:memory:.db").status
|
||||||
|
|
||||||
|
|
||||||
def test_allow_download_off():
|
def test_allow_download_off():
|
||||||
for client in make_app_client(config={"allow_download": False}):
|
for client in make_app_client(is_immutable=True, config={"allow_download": False}):
|
||||||
response = client.get("/fixtures")
|
response = client.get("/fixtures")
|
||||||
soup = Soup(response.body, "html.parser")
|
soup = Soup(response.body, "html.parser")
|
||||||
assert not len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
assert not len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue