summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-31 18:14:30 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-31 18:14:30 +0200
commit464af1625349d557f688da9f845471ef8b80a5f9 (patch)
treeaab415abf115f20ccbcdfad49c27ce5d1bd61134 /test
parent8c6a6516e1dc5c1b4f12740a6f7b32765b530bb7 (diff)
Gate live-content changes on restricted surfaces
publish_draft!, trash!, destroy_from_trash!, attach_asset! and Asset#destroy_witnessed! now refuse unless the acting user holds redaktion, and only when the subject is on a restricted surface: the front page, the updates tree that feeds ~100k subscribers, or disclosure. Drafting, autosaving, tagging and creating stay free everywhere for everyone. Enforcement is in the models rather than the controllers, since attach_asset! and the rest are reachable from rake tasks and internal paths. It follows the errors.add-plus-bare-raise pattern the rest of Node already uses, so every existing RecordInvalid rescue reports it with a localised message; only assets_controller#destroy needed a rescue added. A nil user is treated as a system context and bypasses the gate. The default nil on three of those verbs is what makes that reachable, and removing those defaults once every call site passes a user is the next tightening.
Diffstat (limited to 'test')
-rw-r--r--test/controllers/nodes_controller_test.rb14
-rw-r--r--test/models/asset_destroy_test.rb13
-rw-r--r--test/models/node_test.rb68
-rw-r--r--test/models/user_test.rb16
4 files changed, 111 insertions, 0 deletions
diff --git a/test/controllers/nodes_controller_test.rb b/test/controllers/nodes_controller_test.rb
index 0b6e65ee..f15be067 100644
--- a/test/controllers/nodes_controller_test.rb
+++ b/test/controllers/nodes_controller_test.rb
@@ -770,6 +770,20 @@ class NodesControllerTest < ActionController::TestCase
770 assert_select "form[action=?]", node_path(node), count: 1 770 assert_select "form[action=?]", node_path(node), count: 1
771 end 771 end
772 772
773 test "publishing a restricted node without the redaktion role flashes and does not publish" do
774 login_as :quentin
775 updates = Node.root.children.create!(:slug => "updates")
776 node = updates.children.create!(:slug => "controller-gated")
777 node.reload.draft.update!(:title => "Entwurf")
778
779 put :publish, params: { :id => node.id }
780
781 assert_redirected_to node_path(node)
782 assert_match I18n.t("activerecord.errors.models.node.attributes.base.not_permitted"),
783 flash[:error]
784 assert_nil node.reload.head
785 end
786
773test "show annotates history rows with their lifecycle" do 787test "show annotates history rows with their lifecycle" do
774 login_as :quentin 788 login_as :quentin
775 node = Node.root.children.create!(:slug => "history_annotation_test") 789 node = Node.root.children.create!(:slug => "history_annotation_test")
diff --git a/test/models/asset_destroy_test.rb b/test/models/asset_destroy_test.rb
index 5583f685..2f38d692 100644
--- a/test/models/asset_destroy_test.rb
+++ b/test/models/asset_destroy_test.rb
@@ -48,4 +48,17 @@ class AssetDestroyTest < ActiveSupport::TestCase
48 assert_equal "Doomed asset", action.metadata["asset_name"] 48 assert_equal "Doomed asset", action.metadata["asset_name"]
49 assert_nil action.action_participants.first.subject 49 assert_nil action.action_participants.first.subject
50 end 50 end
51
52 test "destroying an asset attached to a restricted node needs the redaktion role" do
53 editor = User.create!(:login => "asset_gate", :email => "ag@example.com",
54 :password => "secret", :password_confirmation => "secret")
55 updates = Node.root.children.create!(:slug => "updates")
56 node = updates.children.create!(:slug => "gated-attachment")
57 node.reload.attach_asset!(@asset, :user => nil)
58
59 error = assert_raises(ActiveRecord::RecordInvalid) { @asset.destroy_witnessed!(:user => editor) }
60 assert_includes error.message,
61 I18n.t("activerecord.errors.models.asset.attributes.base.not_permitted")
62 assert Asset.exists?(@asset.id)
63 end
51end 64end
diff --git a/test/models/node_test.rb b/test/models/node_test.rb
index c1316ea6..f57f83bf 100644
--- a/test/models/node_test.rb
+++ b/test/models/node_test.rb
@@ -936,4 +936,72 @@ class NodeTest < ActiveSupport::TestCase
936 I18n.t("activerecord.errors.models.node.attributes.base.trash_the_trash") 936 I18n.t("activerecord.errors.models.node.attributes.base.trash_the_trash")
937 end 937 end
938 end 938 end
939
940 test "restricted? covers the root node, the restricted subtrees and their descendants" do
941 assert Node.root.restricted?, "the front page aggregates the feed"
942
943 updates = Node.root.children.create!(:slug => "updates")
944 assert updates.restricted?
945 year = updates.children.create!(:slug => "2026")
946 assert year.reload.restricted?
947 post = year.children.create!(:slug => "some-post")
948 assert post.reload.restricted?
949
950 disclosure = Node.root.children.create!(:slug => "disclosure")
951 assert disclosure.restricted?
952
953 plain = Node.root.children.create!(:slug => "club")
954 assert_not plain.restricted?
955 child = plain.children.create!(:slug => "erfas")
956 assert_not child.reload.restricted?
957 end
958
959 test "restricted? does not match a prefix that is merely a substring" do
960 decoy = Node.root.children.create!(:slug => "updatesomething")
961 assert_not decoy.restricted?
962 end
963
964 test "publishing a restricted node is refused without the redaktion role" do
965 editor = User.create!(:login => "guard_editor", :email => "gd@example.com",
966 :password => "secret", :password_confirmation => "secret")
967 updates = Node.root.children.create!(:slug => "updates")
968 node = updates.children.create!(:slug => "guarded-post")
969 node.reload.draft.update!(:title => "Entwurf")
970
971 error = assert_raises(ActiveRecord::RecordInvalid) { node.publish_draft!(editor) }
972 assert_includes error.message,
973 I18n.t("activerecord.errors.models.node.attributes.base.not_permitted")
974 assert_nil node.reload.head
975 end
976
977 test "publishing a restricted node succeeds with the redaktion role" do
978 red = User.create!(:login => "guard_red", :email => "gr2@example.com",
979 :password => "secret", :password_confirmation => "secret",
980 :roles => ["redaktion"])
981 updates = Node.root.children.create!(:slug => "updates")
982 node = updates.children.create!(:slug => "allowed-post")
983 node.reload.draft.update!(:title => "Entwurf")
984
985 node.publish_draft!(red)
986 assert_not_nil node.reload.head
987 end
988
989 test "publishing outside the restricted subtrees needs no role" do
990 editor = User.create!(:login => "guard_free", :email => "gf@example.com",
991 :password => "secret", :password_confirmation => "secret")
992 node = Node.root.children.create!(:slug => "guard-free-post")
993 node.reload.draft.update!(:title => "Entwurf")
994
995 node.publish_draft!(editor)
996 assert_not_nil node.reload.head
997 end
998
999 test "a nil user is a system context and bypasses the gate" do
1000 updates = Node.root.children.create!(:slug => "updates")
1001 node = updates.children.create!(:slug => "system-post")
1002 node.reload.draft.update!(:title => "Entwurf")
1003
1004 node.publish_draft!
1005 assert_not_nil node.reload.head
1006 end
939end 1007end
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
index feccce25..9942385c 100644
--- a/test/models/user_test.rb
+++ b/test/models/user_test.rb
@@ -126,6 +126,22 @@ class UserTest < ActiveSupport::TestCase
126 126
127 assert user.update(:email => "quentin@example.org") 127 assert user.update(:email => "quentin@example.org")
128 end 128 end
129
130 test "may_change_live? gates restricted subjects on the redaktion role" do
131 editor = User.create!(:login => "gate_editor", :email => "ge@example.com",
132 :password => "secret", :password_confirmation => "secret")
133 redaktion = User.create!(:login => "gate_red", :email => "gr@example.com",
134 :password => "secret", :password_confirmation => "secret",
135 :roles => ["redaktion"])
136
137 restricted = Node.root
138 plain = Node.root.children.create!(:slug => "gate_plain")
139
140 assert editor.may_change_live?(plain)
141 assert_not editor.may_change_live?(restricted)
142 assert redaktion.may_change_live?(plain)
143 assert redaktion.may_change_live?(restricted)
144 end
129 145
130protected 146protected
131 def create_user(options = {}) 147 def create_user(options = {})