- ActorPermissionSetIs check; bypass policy filters by member_id for own_data only. - Admin with member_id still gets :all via HasPermission. Tests added.
234 lines
7.7 KiB
Elixir
234 lines
7.7 KiB
Elixir
defmodule Mv.Membership.MemberGroupPoliciesTest do
|
|
@moduledoc """
|
|
Tests for MemberGroup resource authorization policies.
|
|
|
|
Verifies own_data can only read linked member's associations;
|
|
read_only can read all, cannot create/destroy;
|
|
normal_user and admin can read, create, destroy.
|
|
"""
|
|
use Mv.DataCase, async: false
|
|
|
|
alias Mv.Membership
|
|
|
|
require Ash.Query
|
|
|
|
setup do
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
%{actor: system_actor}
|
|
end
|
|
|
|
defp create_member_fixture do
|
|
Mv.Fixtures.member_fixture()
|
|
end
|
|
|
|
defp create_group_fixture do
|
|
Mv.Fixtures.group_fixture()
|
|
end
|
|
|
|
defp create_member_group_fixture(member_id, group_id) do
|
|
admin = Mv.Fixtures.user_with_role_fixture("admin")
|
|
|
|
{:ok, member_group} =
|
|
Membership.create_member_group(%{member_id: member_id, group_id: group_id}, actor: admin)
|
|
|
|
member_group
|
|
end
|
|
|
|
describe "own_data permission set" do
|
|
setup %{actor: actor} do
|
|
user = Mv.Fixtures.user_with_role_fixture("own_data")
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
# Link user to member so actor.member_id is set
|
|
admin = Mv.Fixtures.user_with_role_fixture("admin")
|
|
|
|
user =
|
|
user
|
|
|> Ash.Changeset.for_update(:update, %{})
|
|
|> Ash.Changeset.force_change_attribute(:member_id, member.id)
|
|
|> Ash.update(actor: admin)
|
|
|
|
{:ok, user} = Ash.load(user, :role, domain: Mv.Accounts, actor: actor)
|
|
mg_linked = create_member_group_fixture(member.id, group.id)
|
|
# MemberGroup for another member (not linked to user)
|
|
other_member = create_member_fixture()
|
|
other_group = create_group_fixture()
|
|
mg_other = create_member_group_fixture(other_member.id, other_group.id)
|
|
%{user: user, member: member, group: group, mg_linked: mg_linked, mg_other: mg_other}
|
|
end
|
|
|
|
test "can read member_groups for linked member only", %{user: user, mg_linked: mg_linked} do
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg_linked.id in ids
|
|
refute Enum.empty?(list)
|
|
end
|
|
|
|
test "list returns only member_groups where member_id == actor.member_id", %{
|
|
user: user,
|
|
mg_linked: mg_linked,
|
|
mg_other: mg_other
|
|
} do
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg_linked.id in ids
|
|
refute mg_other.id in ids
|
|
end
|
|
|
|
test "cannot create member_group (returns forbidden)", %{user: user, actor: _actor} do
|
|
# Use fresh member/group so we assert on Forbidden, not on duplicate validation
|
|
other_member = create_member_fixture()
|
|
other_group = create_group_fixture()
|
|
|
|
assert {:error, %Ash.Error.Forbidden{}} =
|
|
Membership.create_member_group(
|
|
%{member_id: other_member.id, group_id: other_group.id},
|
|
actor: user
|
|
)
|
|
end
|
|
|
|
test "cannot destroy member_group (returns forbidden)", %{user: user, mg_linked: mg_linked} do
|
|
assert {:error, %Ash.Error.Forbidden{}} =
|
|
Membership.destroy_member_group(mg_linked, actor: user)
|
|
end
|
|
end
|
|
|
|
describe "read_only permission set" do
|
|
setup %{actor: actor} do
|
|
user = Mv.Fixtures.user_with_role_fixture("read_only")
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
mg = create_member_group_fixture(member.id, group.id)
|
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
|
end
|
|
|
|
test "can read all member_groups", %{user: user, mg: mg} do
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg.id in ids
|
|
end
|
|
|
|
test "cannot create member_group (returns forbidden)", %{user: user, actor: _actor} do
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
|
|
assert {:error, %Ash.Error.Forbidden{}} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
|
actor: user
|
|
)
|
|
end
|
|
|
|
test "cannot destroy member_group (returns forbidden)", %{user: user, mg: mg} do
|
|
assert {:error, %Ash.Error.Forbidden{}} =
|
|
Membership.destroy_member_group(mg, actor: user)
|
|
end
|
|
end
|
|
|
|
describe "normal_user permission set" do
|
|
setup %{actor: actor} do
|
|
user = Mv.Fixtures.user_with_role_fixture("normal_user")
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
mg = create_member_group_fixture(member.id, group.id)
|
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
|
end
|
|
|
|
test "can read all member_groups", %{user: user, mg: mg} do
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg.id in ids
|
|
end
|
|
|
|
test "can create member_group", %{user: user, actor: _actor} do
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
|
|
assert {:ok, _mg} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
|
actor: user
|
|
)
|
|
end
|
|
|
|
test "can destroy member_group", %{user: user, mg: mg} do
|
|
assert :ok = Membership.destroy_member_group(mg, actor: user)
|
|
end
|
|
end
|
|
|
|
describe "admin permission set" do
|
|
setup %{actor: actor} do
|
|
user = Mv.Fixtures.user_with_role_fixture("admin")
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
mg = create_member_group_fixture(member.id, group.id)
|
|
%{actor: actor, user: user, member: member, group: group, mg: mg}
|
|
end
|
|
|
|
test "can read all member_groups", %{user: user, mg: mg} do
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: user, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg.id in ids
|
|
end
|
|
|
|
test "admin with member_id set (linked to member) still reads all member_groups", %{
|
|
actor: actor
|
|
} do
|
|
# Admin linked to a member (e.g. viewing as member context) must still get :all scope,
|
|
# not restricted to linked member's groups (bypass is only for own_data).
|
|
admin = Mv.Fixtures.user_with_role_fixture("admin")
|
|
linked_member = create_member_fixture()
|
|
other_member = create_member_fixture()
|
|
group_a = create_group_fixture()
|
|
group_b = create_group_fixture()
|
|
|
|
admin =
|
|
admin
|
|
|> Ash.Changeset.for_update(:update, %{})
|
|
|> Ash.Changeset.force_change_attribute(:member_id, linked_member.id)
|
|
|> Ash.update(actor: actor)
|
|
|
|
{:ok, admin} = Ash.load(admin, :role, domain: Mv.Accounts, actor: actor)
|
|
|
|
mg_linked = create_member_group_fixture(linked_member.id, group_a.id)
|
|
mg_other = create_member_group_fixture(other_member.id, group_b.id)
|
|
|
|
{:ok, list} =
|
|
Mv.Membership.MemberGroup
|
|
|> Ash.read(actor: admin, domain: Mv.Membership)
|
|
|
|
ids = Enum.map(list, & &1.id)
|
|
assert mg_linked.id in ids, "Admin with member_id must see linked member's MemberGroups"
|
|
|
|
assert mg_other.id in ids,
|
|
"Admin with member_id must see all MemberGroups (:all), not only linked"
|
|
end
|
|
|
|
test "can create member_group", %{user: user, actor: _actor} do
|
|
member = create_member_fixture()
|
|
group = create_group_fixture()
|
|
|
|
assert {:ok, _mg} =
|
|
Membership.create_member_group(%{member_id: member.id, group_id: group.id},
|
|
actor: user
|
|
)
|
|
end
|
|
|
|
test "can destroy member_group", %{user: user, mg: mg} do
|
|
assert :ok = Membership.destroy_member_group(mg, actor: user)
|
|
end
|
|
end
|
|
end
|