Skip to content

Commit 7d95e4f

Browse files
committed
fix: ensure we use the provided read action in bulk destroy
fixes #2660
1 parent c8e8f00 commit 7d95e4f

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

lib/ash/actions/destroy/bulk.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ defmodule Ash.Actions.Destroy.Bulk do
112112
query =
113113
Ash.Query.for_read(
114114
query,
115-
Ash.Resource.Info.primary_action!(query.resource, :read).name,
115+
Ash.Actions.Update.Bulk.get_read_action(query.resource, action, opts).name,
116116
%{},
117117
actor: opts[:actor],
118118
tenant: opts[:tenant],

test/actions/bulk/bulk_destroy_test.exs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,64 @@ defmodule Ash.Test.Actions.BulkDestroyTest do
390390
end
391391
end
392392

393+
defmodule StrictPost do
394+
@moduledoc false
395+
use Ash.Resource,
396+
domain: Domain,
397+
data_layer: Ash.DataLayer.Ets,
398+
authorizers: [Ash.Policy.Authorizer]
399+
400+
ets do
401+
private? true
402+
end
403+
404+
actions do
405+
default_accept :*
406+
defaults [:create]
407+
408+
read :read do
409+
primary? true
410+
pagination keyset?: true, required?: false
411+
end
412+
413+
read :read_for_destroy do
414+
pagination keyset?: true, required?: false
415+
end
416+
417+
destroy :destroy do
418+
primary? true
419+
end
420+
421+
destroy :destroy_with_read_action do
422+
end
423+
end
424+
425+
policies do
426+
default_access_type :strict
427+
428+
policy action(:create) do
429+
authorize_if always()
430+
end
431+
432+
policy action(:read_for_destroy) do
433+
authorize_if always()
434+
end
435+
436+
policy action(:destroy_with_read_action) do
437+
authorize_if always()
438+
end
439+
440+
policy action(:destroy) do
441+
authorize_if always()
442+
end
443+
end
444+
445+
attributes do
446+
uuid_primary_key :id
447+
attribute :title, :string, allow_nil?: false, public?: true
448+
end
449+
end
450+
393451
setup do
394452
capture_log(fn ->
395453
Ash.DataLayer.Mnesia.start(Domain, [MnesiaPost])
@@ -1441,4 +1499,40 @@ defmodule Ash.Test.Actions.BulkDestroyTest do
14411499
assert Exception.message(error) =~ "status cannot be bad"
14421500
end
14431501
end
1502+
1503+
describe "read_action option" do
1504+
test "bulk_destroy respects read_action option when query is not pre-validated" do
1505+
StrictPost
1506+
|> Ash.Changeset.for_create(:create, %{title: "test"})
1507+
|> Ash.create!(authorize?: false)
1508+
1509+
# Build a query without __validated_for_action__ (as AshGraphql does via do_filter)
1510+
query = Ash.Query.do_filter(StrictPost, %{title: "test"})
1511+
1512+
# Primary :read is forbidden under strict access type,
1513+
# but :read_for_destroy is authorized
1514+
assert %Ash.BulkResult{status: :success} =
1515+
Ash.bulk_destroy(query, :destroy_with_read_action, %{},
1516+
read_action: :read_for_destroy,
1517+
authorize?: true,
1518+
return_errors?: true
1519+
)
1520+
end
1521+
1522+
test "bulk_destroy fails when read_action is not specified and primary read is forbidden" do
1523+
StrictPost
1524+
|> Ash.Changeset.for_create(:create, %{title: "test"})
1525+
|> Ash.create!(authorize?: false)
1526+
1527+
query = Ash.Query.do_filter(StrictPost, %{title: "test"})
1528+
1529+
# Without read_action, bulk_destroy should use the primary :read action
1530+
# which is forbidden under strict access type
1531+
assert %Ash.BulkResult{status: :error} =
1532+
Ash.bulk_destroy(query, :destroy_with_read_action, %{},
1533+
authorize?: true,
1534+
return_errors?: true
1535+
)
1536+
end
1537+
end
14441538
end

0 commit comments

Comments
 (0)