From 3fcf1f899ad578080fc92de645f90a98e71676cd Mon Sep 17 00:00:00 2001 From: deseven Date: Sun, 3 May 2026 20:54:24 +0200 Subject: [PATCH] make 'approved' new default for write and upload access --- docker-compose.override.yml.skeleton | 4 +- otterwiki/server.py | 4 +- settings.cfg.skeleton | 5 +- tests/conftest.py | 31 ++++++- tests/test_attachments.py | 26 ++++-- tests/test_auth.py | 116 +++++++++++++-------------- tests/test_custom_html.py | 4 +- tests/test_housekeeping.py | 6 +- tests/test_preview.py | 14 ++-- tests/test_remote.py | 2 + 10 files changed, 127 insertions(+), 85 deletions(-) diff --git a/docker-compose.override.yml.skeleton b/docker-compose.override.yml.skeleton index cb2f9787..9834f760 100644 --- a/docker-compose.override.yml.skeleton +++ b/docker-compose.override.yml.skeleton @@ -26,8 +26,8 @@ services: - SITE_DESCRIPTION=Otter Wiki Development # Permissions allow everything (for development) - READ_ACCESS=ANONYMOUS - - WRITE_ACCESS=ANONYMOUS - - ATTACHMENT_ACCESS=ANONYMOUS + - WRITE_ACCESS=APPROVED + - ATTACHMENT_ACCESS=APPROVED - AUTO_APPROVAL=True - NOTIFY_ADMINS_ON_REGISTER=False - EMAIL_NEEDS_CONFIRMATION=False diff --git a/otterwiki/server.py b/otterwiki/server.py index 744d2087..082f4343 100644 --- a/otterwiki/server.py +++ b/otterwiki/server.py @@ -37,8 +37,8 @@ AUTH_HEADERS_EMAIL="x-otterwiki-email", AUTH_HEADERS_PERMISSIONS="x-otterwiki-permissions", READ_ACCESS="ANONYMOUS", - WRITE_ACCESS="ANONYMOUS", - ATTACHMENT_ACCESS="ANONYMOUS", + WRITE_ACCESS="APPROVED", + ATTACHMENT_ACCESS="APPROVED", AUTO_APPROVAL=True, DISABLE_REGISTRATION=False, EMAIL_NEEDS_CONFIRMATION=True, diff --git a/settings.cfg.skeleton b/settings.cfg.skeleton index 69fb24b5..7ae7f37c 100644 --- a/settings.cfg.skeleton +++ b/settings.cfg.skeleton @@ -14,8 +14,8 @@ REPOSITORY = '/path/to/the/repository/root' # 'ANONYMOUS' or 'REGISTERED' or 'APPROVED' READ_ACCESS = 'ANONYMOUS' -WRITE_ACCESS = 'REGISTERED' -ATTACHMENT_ACCESS = 'REGISTERED' +WRITE_ACCESS = 'APPROVED' +ATTACHMENT_ACCESS = 'APPROVED' AUTO_APPROVAL = True EMAIL_NEEDS_CONFIRMATION = True # NOTIFY_ADMINS_ON_REGISTER = True @@ -47,4 +47,3 @@ SQLALCHEMY_DATABASE_URI = '' # MAIL_PORT = 1025 # MAIL_USERNAME = None # MAIL_PASSWORD = None - diff --git a/tests/conftest.py b/tests/conftest.py index 889b574b..fb259c10 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,8 +74,24 @@ def create_app(tmpdir): @pytest.fixture -def test_client(create_app): - client = create_app.test_client() +def test_client(app_with_user): + client = app_with_user.test_client() + result = client.post( + "/-/login", + data={ + "email": "another@user.org", + "password": "password4567", + }, + follow_redirects=True, + ) + html = result.data.decode() + assert "You logged in successfully." in html + return client + + +@pytest.fixture +def anonymous_client(app_with_user): + client = app_with_user.test_client() return client @@ -85,6 +101,17 @@ def req_ctx(create_app): yield ctx +@pytest.fixture +def anonymous_write_access(create_app): + """Temporarily set WRITE_ACCESS to ANONYMOUS for tests that don't + use authenticated users (e.g. rendering/preview unit tests). + Automatically restores the original value after the test.""" + original = create_app.config["WRITE_ACCESS"] + create_app.config["WRITE_ACCESS"] = "ANONYMOUS" + yield + create_app.config["WRITE_ACCESS"] = original + + @pytest.fixture def app_with_user(create_app, req_ctx): from otterwiki.auth import SimpleAuth, generate_password_hash, db diff --git a/tests/test_attachments.py b/tests/test_attachments.py index de004b8c..6bec1953 100644 --- a/tests/test_attachments.py +++ b/tests/test_attachments.py @@ -7,46 +7,56 @@ @pytest.fixture -def app_with_attachments(create_app): +def app_with_attachments(app_with_user): # create test page message = "Test.md commit" filename = "test.md" author = ("Example Author", "mail@example.com") # create attachment - create_app.storage.store( + app_with_user.storage.store( filename, content="# Test\nAttachment Test.", author=author, message=message, ) - assert True == create_app.storage.exists(filename) + assert True == app_with_user.storage.exists(filename) # create txt attachment message = "Test/attachment0.txt attach0-commit" - create_app.storage.store( + app_with_user.storage.store( "test/attachment0.txt", content="attachment0-content0", author=author, message=message, ) - assert True == create_app.storage.exists("test/attachment0.txt") + assert True == app_with_user.storage.exists("test/attachment0.txt") # create gif attachment message = "Test/attachment1.gif attach1-commit" content_b64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" - create_app.storage.store( + app_with_user.storage.store( "test/attachment1.gif", content=base64.b64decode(content_b64), author=author, message=message, mode="wb", ) - assert True == create_app.storage.exists("test/attachment1.gif") - yield create_app + assert True == app_with_user.storage.exists("test/attachment1.gif") + yield app_with_user @pytest.fixture def test_client(app_with_attachments): client = app_with_attachments.test_client() client._app = app_with_attachments + # Log in as approved user for write/attachment access + result = client.post( + "/-/login", + data={ + "email": "another@user.org", + "password": "password4567", + }, + follow_redirects=True, + ) + assert "You logged in successfully." in result.data.decode() yield client diff --git a/tests/test_auth.py b/tests/test_auth.py index e4c19233..0c34e5b1 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -162,8 +162,8 @@ def test_logout(test_client): assert "You logged out successfully." in html -def test_login_required(test_client): - html = test_client.get( +def test_login_required(anonymous_client): + html = anonymous_client.get( "/-/settings", follow_redirects=True, ).data.decode() @@ -184,11 +184,11 @@ def test_settins_minimal(app_with_user, test_client): # test permissions # @pytest.fixture -def app_with_permissions(app_with_user, test_client): +def app_with_permissions(app_with_user, anonymous_client): app_with_user.config["READ_ACCESS"] = "ANONYMOUS" app_with_user.config["WRITE_ACCESS"] = "ANONYMOUS" # create a Home - html = test_client.post( + html = anonymous_client.post( "/Home/save", data={ "content": "There is no place like Home.", @@ -196,25 +196,25 @@ def app_with_permissions(app_with_user, test_client): }, follow_redirects=True, ).data.decode() - html = test_client.get("/Home").data.decode() + html = anonymous_client.get("/Home").data.decode() assert "There is no place like Home." in html # update permissions app_with_user.config["READ_ACCESS"] = "REGISTERED" # and fetch again - html = test_client.get("/Home").data.decode() + html = anonymous_client.get("/Home").data.decode() assert "There is no place like Home." not in html with app_with_user.test_request_context() as ctx: yield app_with_user -def test_page_view_permissions(app_with_permissions, test_client): +def test_page_view_permissions(app_with_permissions, anonymous_client): fun = "view" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert "There is no place like Home." in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "REGISTERED" - rv = test_client.get(url_for(fun, path="Home"), follow_redirects=True) + rv = anonymous_client.get(url_for(fun, path="Home"), follow_redirects=True) assert "There is no place like Home." not in rv.data.decode() # check for the toast assert "lack the permissions to access" in rv.data.decode() @@ -222,83 +222,83 @@ def test_page_view_permissions(app_with_permissions, test_client): assert url_for("login") in rv.data.decode() assert 'name="password"' in rv.data.decode() assert rv.status_code == 200 - login(test_client) - rv = test_client.get(url_for(fun, path="Home")) + login(anonymous_client) + rv = anonymous_client.get(url_for(fun, path="Home")) assert "There is no place like Home." in rv.data.decode() assert rv.status_code == 200 -def test_page_blame_permissions(app_with_permissions, test_client): +def test_page_blame_permissions(app_with_permissions, anonymous_client): fun = "blame" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 200 assert "There is no place like Home." in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "REGISTERED" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 302 assert "/-/login" in rv.location - login(test_client) - rv = test_client.get(url_for(fun, path="Home")) + login(anonymous_client) + rv = anonymous_client.get(url_for(fun, path="Home")) assert "There is no place like Home." in rv.data.decode() -def test_page_history_permissions(app_with_permissions, test_client): +def test_page_history_permissions(app_with_permissions, anonymous_client): fun = "history" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 200 assert "initial test commit" in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "REGISTERED" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 302 assert "/-/login" in rv.location - login(test_client) - rv = test_client.get(url_for(fun, path="Home")) + login(anonymous_client) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 200 assert "initial test commit" in rv.data.decode() -def test_page_index_permissions(app_with_permissions, test_client): +def test_page_index_permissions(app_with_permissions, anonymous_client): fun = "pageindex" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun)) + rv = anonymous_client.get(url_for(fun)) assert rv.status_code == 200 assert "Page Index" in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "REGISTERED" - rv = test_client.get(url_for(fun)) + rv = anonymous_client.get(url_for(fun)) assert rv.status_code == 302 assert "/-/login" in rv.location - login(test_client) - rv = test_client.get(url_for(fun)) + login(anonymous_client) + rv = anonymous_client.get(url_for(fun)) assert rv.status_code == 200 assert "Page Index" in rv.data.decode() -def test_page_changelog_permissions(app_with_permissions, test_client): +def test_page_changelog_permissions(app_with_permissions, anonymous_client): fun = "changelog" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 200 assert "initial test commit" in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "REGISTERED" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 302 assert "/-/login" in rv.location - login(test_client) - rv = test_client.get(url_for(fun, path="Home")) + login(anonymous_client) + rv = anonymous_client.get(url_for(fun, path="Home")) assert rv.status_code == 200 assert "initial test commit" in rv.data.decode() -def test_page_edit_permissions(app_with_permissions, test_client): +def test_page_edit_permissions(app_with_permissions, anonymous_client): # update permissions app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" app_with_permissions.config["WRITE_ACCESS"] = "ANONYMOUS" # helper pagename = "RandomEdit" # try to edit anonymous - rv = test_client.get(url_for("edit", path=pagename)) + rv = anonymous_client.get(url_for("edit", path=pagename)) assert rv.status_code == 200 html = rv.data.decode() # check that there is an editor in the html @@ -307,21 +307,21 @@ def test_page_edit_permissions(app_with_permissions, test_client): app_with_permissions.config["READ_ACCESS"] = "REGISTERED" app_with_permissions.config["WRITE_ACCESS"] = "REGISTERED" # try edit - rv = test_client.get(url_for("edit", path=pagename)) + rv = anonymous_client.get(url_for("edit", path=pagename)) html = rv.data.decode() # check that there is an editor in the html assert rv.status_code == 403 assert "([A-z0-9]+)", html @@ -405,14 +405,14 @@ def test_page_revert_permissions(app_with_permissions, test_client): app_with_permissions.config["WRITE_ACCESS"] = "REGISTERED" # try to revert non existing commit - rv = test_client.get("/-/revert/{}".format(0000000)) + rv = anonymous_client.get("/-/revert/{}".format(0000000)) assert rv.status_code == 403 # try revert form - rv = test_client.get("/-/revert/{}".format(latest_revision)) + rv = anonymous_client.get("/-/revert/{}".format(latest_revision)) assert rv.status_code == 403 # try to revert latest commit - rv = test_client.post("/-/revert/{}".format(latest_revision)) + rv = anonymous_client.post("/-/revert/{}".format(latest_revision)) assert rv.status_code == 403 # change permissions again @@ -420,36 +420,36 @@ def test_page_revert_permissions(app_with_permissions, test_client): app_with_permissions.config["WRITE_ACCESS"] = "ANONYMOUS" # check revert form - rv = test_client.get("/-/revert/{}".format(latest_revision)) + rv = anonymous_client.get("/-/revert/{}".format(latest_revision)) html = rv.data.decode() assert rv.status_code == 200 assert "Revert commit [{}]".format(latest_revision) in html # try to revert latest commit - rv = test_client.post( + rv = anonymous_client.post( "/-/revert/{}".format(latest_revision), follow_redirects=True ) assert rv.status_code == 200 # check if content changed - html = test_client.get("/{}/view".format(pagename)).data.decode() + html = anonymous_client.get("/{}/view".format(pagename)).data.decode() assert old_content in html -def test_permissions_per_user(app_with_permissions, test_client): +def test_permissions_per_user(app_with_permissions, anonymous_client): fun = "view" app_with_permissions.config["READ_ACCESS"] = "ANONYMOUS" - rv = test_client.get(url_for(fun, path="Home")) + rv = anonymous_client.get(url_for(fun, path="Home")) assert "There is no place like Home." in rv.data.decode() app_with_permissions.config["READ_ACCESS"] = "ADMIN" app_with_permissions.config["WRITE_ACCESS"] = "ADMIN" app_with_permissions.config["ATTACHMENT_ACCESS"] = "ADMIN" - rv = test_client.get(url_for(fun, path="Home"), follow_redirects=True) + rv = anonymous_client.get(url_for(fun, path="Home"), follow_redirects=True) assert "There is no place like Home." not in rv.data.decode() # check for the toast assert "lack the permissions to access" in rv.data.decode() - rv = test_client.post( + rv = anonymous_client.post( "/-/login", data={ "email": "another@user.org", @@ -470,10 +470,10 @@ def test_permissions_per_user(app_with_permissions, test_client): db.session.add(user) db.session.commit() # check if the read_access works - rv = test_client.get(url_for(fun, path="Home"), follow_redirects=True) + rv = anonymous_client.get(url_for(fun, path="Home"), follow_redirects=True) assert "There is no place like Home." in rv.data.decode() # try to save - rv = test_client.post( + rv = anonymous_client.post( url_for("save", path="SaveTest"), data={ "content": "Another save test", @@ -488,7 +488,7 @@ def test_permissions_per_user(app_with_permissions, test_client): db.session.add(user) db.session.commit() # try to save - rv = test_client.post( + rv = anonymous_client.post( url_for("save", path="SaveTest"), data={ "content": "Another save test", diff --git a/tests/test_custom_html.py b/tests/test_custom_html.py index 0cc273b1..7ee5c85a 100644 --- a/tests/test_custom_html.py +++ b/tests/test_custom_html.py @@ -17,7 +17,7 @@ def test_load_custom_html_nonexistent_file(create_app): assert result == "" -def test_custom_html(test_client, create_app): +def test_custom_html(anonymous_client, create_app): """Test that custom HTML works with HTML_EXTRA_HEAD, HTML_EXTRA_BODY and custom files""" app = create_app @@ -44,7 +44,7 @@ def test_custom_html(test_client, create_app): f.write(body_content) try: - response = test_client.get('/') + response = anonymous_client.get('/') html = response.data.decode('utf-8') assert 'env-head-value' in html diff --git a/tests/test_housekeeping.py b/tests/test_housekeeping.py index 3b4a3dc5..b95c5d19 100644 --- a/tests/test_housekeeping.py +++ b/tests/test_housekeeping.py @@ -602,9 +602,11 @@ def test_housekeeping_unknown_task(self, app_with_user, admin_client): html = rv.data.decode() assert "Unkown task" in html or "Unknown task" in html - def test_housekeeping_requires_login(self, app_with_user, test_client): + def test_housekeeping_requires_login( + self, app_with_user, anonymous_client + ): """Test that housekeeping requires login.""" - rv = test_client.get("/-/housekeeping", follow_redirects=False) + rv = anonymous_client.get("/-/housekeeping", follow_redirects=False) # Should redirect to login assert rv.status_code == 302 assert "login" in rv.location diff --git a/tests/test_preview.py b/tests/test_preview.py index 44694d1e..a83863a9 100644 --- a/tests/test_preview.py +++ b/tests/test_preview.py @@ -61,7 +61,7 @@ """ -def test_preview(create_app, req_ctx): +def test_preview(create_app, req_ctx, anonymous_write_access): from otterwiki.wiki import Page p = Page("test") @@ -69,7 +69,7 @@ def test_preview(create_app, req_ctx): assert render.htmlcursor in data['preview_content'] -def test_preview_all(create_app, req_ctx): +def test_preview_all(create_app, req_ctx, anonymous_write_access): whitespace = re.compile(r"\s+") markdown_arr = markdown_example.splitlines() html_example, _, _ = render.markdown(markdown_example) @@ -111,7 +111,7 @@ def test_preview_all(create_app, req_ctx): assert str(element) in preview_html -def test_preview_list_bug(create_app, req_ctx): +def test_preview_list_bug(create_app, req_ctx, anonymous_write_access): from otterwiki.wiki import Page p = Page("test") @@ -125,7 +125,7 @@ def test_preview_list_bug(create_app, req_ctx): assert "
  • drei" in data['preview_content'] -def test_preview_italic_bug(create_app, req_ctx): +def test_preview_italic_bug(create_app, req_ctx, anonymous_write_access): from otterwiki.wiki import Page p = Page("test") @@ -140,7 +140,9 @@ def test_preview_italic_bug(create_app, req_ctx): assert "A paragraph" == text -def test_preview_cursor_in_codeblock(create_app, req_ctx): +def test_preview_cursor_in_codeblock( + create_app, req_ctx, anonymous_write_access +): from otterwiki.wiki import Page p = Page("test") @@ -160,7 +162,7 @@ def test_preview_cursor_in_codeblock(create_app, req_ctx): assert "code in block " == text -def test_preview_cursor_in_abbr(create_app, req_ctx): +def test_preview_cursor_in_abbr(create_app, req_ctx, anonymous_write_access): from otterwiki.wiki import Page p = Page("test") diff --git a/tests/test_remote.py b/tests/test_remote.py index 4d137568..0f8cfb2e 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -41,6 +41,7 @@ def test_git_info_refs_404(create_app, test_client): def test_git_info_refs(create_app_with_git, test_client_with_git): create_app_with_git.config["READ_ACCESS"] = "ANONYMOUS" create_app_with_git.config["WRITE_ACCESS"] = "ANONYMOUS" + create_app_with_git.config["ATTACHMENT_ACCESS"] = "ANONYMOUS" rv = test_client_with_git.get( "/.git/info/refs?service=git-upload-pack", follow_redirects=True, @@ -62,6 +63,7 @@ def test_git_info_refs(create_app_with_git, test_client_with_git): def test_git_info_refs_permissions(create_app_with_git, test_client_with_git): create_app_with_git.config["READ_ACCESS"] = "ANONYMOUS" create_app_with_git.config["WRITE_ACCESS"] = "ANONYMOUS" + create_app_with_git.config["ATTACHMENT_ACCESS"] = "ANONYMOUS" rv = test_client_with_git.get( "/.git/info/refs?service=git-upload-pack", follow_redirects=True,