Skip to content

Commit

Permalink
Disable uploading and creating browser profiles when org is read-only (
Browse files Browse the repository at this point in the history
…#1907)

Fixes #1904 

Follow-up to read-only enforcement, with improved tests.
  • Loading branch information
tw4l authored Jul 2, 2024
1 parent e1ef894 commit bdfc094
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 7 deletions.
11 changes: 7 additions & 4 deletions backend/btrixcloud/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async def navigate_profile_browser(self, browserid, urlin: UrlIn):
async def commit_to_profile(
self,
browser_commit: ProfileCreate,
storage: StorageRef,
org: Organization,
user: User,
metadata: dict,
existing_profile: Optional[Profile] = None,
Expand Down Expand Up @@ -196,7 +196,7 @@ async def commit_to_profile(
hash=resource["hash"],
size=file_size,
filename=resource["path"],
storage=storage,
storage=org.storage,
)

baseid = metadata.get("btrix.baseprofile")
Expand All @@ -206,6 +206,9 @@ async def commit_to_profile(

oid = UUID(metadata.get("btrix.org"))

if org.readOnly:
raise HTTPException(status_code=403, detail="org_set_to_read_only")

if await self.orgs.storage_quota_reached(oid):
raise HTTPException(status_code=403, detail="storage_quota_reached")

Expand Down Expand Up @@ -493,7 +496,7 @@ async def commit_browser_to_new(
):
metadata = await browser_get_metadata(browser_commit.browserid, org)

return await ops.commit_to_profile(browser_commit, org.storage, user, metadata)
return await ops.commit_to_profile(browser_commit, org, user, metadata)

@router.patch("/{profileid}")
async def commit_browser_to_existing(
Expand All @@ -515,7 +518,7 @@ async def commit_browser_to_existing(
description=browser_commit.description or profile.description,
crawlerChannel=profile.crawlerChannel,
),
storage=org.storage,
org=org,
user=user,
metadata=metadata,
existing_profile=profile,
Expand Down
6 changes: 6 additions & 0 deletions backend/btrixcloud/uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ async def upload_stream(
replaceId: Optional[str],
) -> dict[str, Any]:
"""Upload streaming file, length unknown"""
if org.readOnly:
raise HTTPException(status_code=403, detail="org_set_to_read_only")

if await self.orgs.storage_quota_reached(org.id):
raise HTTPException(status_code=403, detail="storage_quota_reached")

Expand Down Expand Up @@ -122,6 +125,9 @@ async def upload_formdata(
user: User,
) -> dict[str, Any]:
"""handle uploading content to uploads subdir + request subdir"""
if org.readOnly:
raise HTTPException(status_code=403, detail="org_set_to_read_only")

if await self.orgs.storage_quota_reached(org.id):
raise HTTPException(status_code=403, detail="storage_quota_reached")

Expand Down
5 changes: 5 additions & 0 deletions backend/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,11 @@ def profile_browser_3_id(admin_auth_headers, default_org_id):
return _create_profile_browser(admin_auth_headers, default_org_id)


@pytest.fixture(scope="session")
def profile_browser_4_id(admin_auth_headers, default_org_id):
return _create_profile_browser(admin_auth_headers, default_org_id)


def _create_profile_browser(
headers: Dict[str, str], oid: UUID, url: str = "https://webrecorder.net"
):
Expand Down
32 changes: 29 additions & 3 deletions backend/test/test_org.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import os
import requests
import uuid

import pytest

from .conftest import API_PREFIX
from .utils import read_in_chunks

curr_dir = os.path.dirname(os.path.realpath(__file__))

new_oid = None

Expand Down Expand Up @@ -524,7 +528,7 @@ def test_update_read_only(admin_auth_headers, default_org_id):
assert data["readOnly"] is True
assert data["readOnlyReason"] == "Payment suspended"

# Try to start crawls, should fail
# Try to start crawl from new workflow, should fail
crawl_data = {
"runNow": True,
"name": "Read Only Test Crawl",
Expand All @@ -543,10 +547,32 @@ def test_update_read_only(admin_auth_headers, default_org_id):
data = r.json()

assert data["added"]
assert data["id"]
assert data["run_now_job"] is None

# Reset back to False, future crawls in tests should run fine
cid = data["id"]
assert cid

# Try to start crawl from existing workflow, should fail
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/{cid}/run",
headers=admin_auth_headers,
json=crawl_data,
)
assert r.status_code == 403
assert r.json()["detail"] == "org_set_to_read_only"

# Try to upload a WACZ, should fail
with open(os.path.join(curr_dir, "data", "example.wacz"), "rb") as fh:
r = requests.put(
f"{API_PREFIX}/orgs/{default_org_id}/uploads/stream?filename=test.wacz&name=My%20New%20Upload&description=Should%20Fail&collections=&tags=",
headers=admin_auth_headers,
data=read_in_chunks(fh),
)

assert r.status_code == 403
assert r.json()["detail"] == "org_set_to_read_only"

# Reset back to False, future tests should be unaffected
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/read-only",
headers=admin_auth_headers,
Expand Down
51 changes: 51 additions & 0 deletions backend/test/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,54 @@ def test_delete_profile(admin_auth_headers, default_org_id, profile_2_id):
)
assert r.status_code == 404
assert r.json()["detail"] == "profile_not_found"


def test_create_profile_read_only_org(
admin_auth_headers, default_org_id, profile_browser_4_id
):
# Set org to read-only
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/read-only",
headers=admin_auth_headers,
json={"readOnly": True, "readOnlyReason": "For testing purposes"},
)
assert r.json()["updated"]

prepare_browser_for_profile_commit(
profile_browser_4_id, admin_auth_headers, default_org_id
)

# Try to create profile, verify we get 403 forbidden
start_time = time.monotonic()
time_limit = 300
while True:
try:
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/profiles",
headers=admin_auth_headers,
json={
"browserid": profile_browser_4_id,
"name": "uncreatable",
"description": "because org is read-only",
},
timeout=10,
)
detail = r.json().get("detail")
if detail == "waiting_for_browser":
time.sleep(5)
continue
if detail == "org_set_to_read_only":
assert r.status_code == 403
break
except:
if time.monotonic() - start_time > time_limit:
raise
time.sleep(5)

# Set readOnly back to false on org
r = requests.post(
f"{API_PREFIX}/orgs/{default_org_id}/read-only",
headers=admin_auth_headers,
json={"readOnly": False},
)
assert r.json()["updated"]

0 comments on commit bdfc094

Please sign in to comment.