Merge branch 'feature/delete-identity-last-group-admin' into 'master'

Don't delete the last admin of a group

Closes #135

See merge request framasoft/mobilizon!170
This commit is contained in:
Thomas Citharel 2019-08-27 14:23:07 +02:00
commit 36d66d811b
4 changed files with 76 additions and 7 deletions

View File

@ -68,6 +68,33 @@ defmodule Mobilizon.Actors.Member do
) )
end end
@doc """
Get all group ids where the actor_id is the last administrator
"""
def list_group_id_where_last_administrator(actor_id) do
in_query =
from(
m in Member,
where: m.actor_id == ^actor_id and (m.role == ^:creator or m.role == ^:administrator),
select: m.parent_id
)
Repo.all(
from(
m in Member,
where: m.role == ^:creator or m.role == ^:administrator,
join: m2 in subquery(in_query),
on: m.parent_id == m2.parent_id,
group_by: m.parent_id,
select: m.parent_id,
having: count(m.actor_id) == 1
)
)
end
@doc """
Returns true if the member is an administrator (admin or creator) of the group
"""
def is_administrator(%Member{role: :administrator}), do: {:is_admin, true} def is_administrator(%Member{role: :administrator}), do: {:is_admin, true}
def is_administrator(%Member{role: :creator}), do: {:is_admin, true} def is_administrator(%Member{role: :creator}), do: {:is_admin, true}
def is_administrator(%Member{}), do: {:is_admin, false} def is_administrator(%Member{}), do: {:is_admin, false}

View File

@ -178,7 +178,7 @@ defmodule MobilizonWeb.Resolvers.Group do
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id), with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Member.get_member(actor.id, group_id), {:ok, %Member{} = member} <- Member.get_member(actor.id, group_id),
{:only_administrator, false} <- {:only_administrator, false} <-
{:only_administrator, check_that_member_is_not_only_administrator(group_id, actor_id)}, {:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
{:ok, _} <- {:ok, _} <-
Mobilizon.Actors.delete_member(member) do Mobilizon.Actors.delete_member(member) do
{ {
@ -211,8 +211,8 @@ defmodule MobilizonWeb.Resolvers.Group do
# We check that the actor asking to leave the group is not it's only administrator # We check that the actor asking to leave the group is not it's only administrator
# We start by fetching the list of administrator or creators and if there's only one of them # We start by fetching the list of administrator or creators and if there's only one of them
# and that it's the actor requesting leaving the group we return true # and that it's the actor requesting leaving the group we return true
@spec check_that_member_is_not_only_administrator(integer(), integer()) :: boolean() @spec check_that_member_is_not_last_administrator(integer(), integer()) :: boolean()
defp check_that_member_is_not_only_administrator(group_id, actor_id) do defp check_that_member_is_not_last_administrator(group_id, actor_id) do
case Member.list_administrator_members_for_group(group_id) do case Member.list_administrator_members_for_group(group_id) do
[ [
%Member{ %Member{

View File

@ -3,7 +3,7 @@ defmodule MobilizonWeb.Resolvers.Person do
Handles the person-related GraphQL calls Handles the person-related GraphQL calls
""" """
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Users.User alias Mobilizon.Users.User
alias Mobilizon.Users alias Mobilizon.Users
alias Mobilizon.Events alias Mobilizon.Events
@ -118,6 +118,7 @@ defmodule MobilizonWeb.Resolvers.Person do
{:find_actor, Actors.get_actor_by_name(preferred_username)}, {:find_actor, Actors.get_actor_by_name(preferred_username)},
{:is_owned, true, _} <- User.owns_actor(user, actor.id), {:is_owned, true, _} <- User.owns_actor(user, actor.id),
{:last_identity, false} <- {:last_identity, last_identity?(user)}, {:last_identity, false} <- {:last_identity, last_identity?(user)},
{:last_admin, false} <- {:last_admin, last_admin_of_a_group?(actor.id)},
{:ok, actor} <- Actors.delete_actor(actor) do {:ok, actor} <- Actors.delete_actor(actor) do
{:ok, actor} {:ok, actor}
else else
@ -127,6 +128,9 @@ defmodule MobilizonWeb.Resolvers.Person do
{:last_identity, true} -> {:last_identity, true} ->
{:error, "Cannot remove the last identity of a user"} {:error, "Cannot remove the last identity of a user"}
{:last_admin, true} ->
{:error, "Cannot remove the last administrator of a group"}
{:is_owned, false} -> {:is_owned, false} ->
{:error, "Actor is not owned by authenticated user"} {:error, "Actor is not owned by authenticated user"}
end end
@ -213,6 +217,12 @@ defmodule MobilizonWeb.Resolvers.Person do
|> proxify_banner |> proxify_banner
end end
# We check that the actor is not the last administrator/creator of a group
@spec last_admin_of_a_group?(integer()) :: boolean()
defp last_admin_of_a_group?(actor_id) do
length(Member.list_group_id_where_last_administrator(actor_id)) > 0
end
@spec proxify_avatar(Actor.t()) :: Actor.t() @spec proxify_avatar(Actor.t()) :: Actor.t()
defp proxify_avatar(%Actor{avatar: %{url: avatar_url} = avatar} = actor) do defp proxify_avatar(%Actor{avatar: %{url: avatar_url} = avatar} = actor) do
actor |> Map.put(:avatar, avatar |> Map.put(:url, MobilizonWeb.MediaProxy.url(avatar_url))) actor |> Map.put(:avatar, avatar |> Map.put(:url, MobilizonWeb.MediaProxy.url(avatar_url)))

View File

@ -354,7 +354,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|> auth_conn(user1) |> auth_conn(user1)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["data"]["updatePerson"] == nil assert json_response(res, 200)["data"]["deletePerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"Actor is not owned by authenticated user" "Actor is not owned by authenticated user"
@ -377,7 +377,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|> auth_conn(user) |> auth_conn(user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["data"]["updatePerson"] == nil assert json_response(res, 200)["data"]["deletePerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"Actor not found" "Actor not found"
@ -400,12 +400,44 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|> auth_conn(user) |> auth_conn(user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["data"]["updatePerson"] == nil assert json_response(res, 200)["data"]["deletePerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"Cannot remove the last identity of a user" "Cannot remove the last identity of a user"
end end
test "delete_person/3 should fail to delete an identity that is the last admin of a group",
context do
group = insert(:group)
classic_user = insert(:user)
classic_actor = insert(:actor, user: classic_user, preferred_username: "classic_user")
admin_user = insert(:user)
admin_actor = insert(:actor, user: admin_user, preferred_username: "last_admin")
insert(:actor, user: admin_user)
insert(:member, %{actor: admin_actor, role: :creator, parent: group})
insert(:member, %{actor: classic_actor, role: :member, parent: group})
mutation = """
mutation {
deletePerson(preferredUsername: "last_admin") {
id,
}
}
"""
res =
context.conn
|> auth_conn(admin_user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["data"]["deletePerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] ==
"Cannot remove the last administrator of a group"
end
test "delete_person/3 should delete a user identity", context do test "delete_person/3 should delete a user identity", context do
user = insert(:user) user = insert(:user)
insert(:actor, user: user, preferred_username: "riri") insert(:actor, user: user, preferred_username: "riri")