Allow group admins to moderate new members
Closes #881 Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -10,6 +10,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Service.Notifications.Scheduler
|
||||
alias Mobilizon.Web.Email.Member, as: EmailMember
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
@@ -21,7 +22,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@type acceptable_types :: :join | :follow | :invite
|
||||
@type acceptable_types :: :join | :follow | :invite | :member
|
||||
@type acceptable_entities ::
|
||||
accept_join_entities | accept_follow_entities | accept_invite_entities
|
||||
|
||||
@@ -35,6 +36,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
||||
:join -> accept_join(entity, additional)
|
||||
:follow -> accept_follow(entity, additional)
|
||||
:invite -> accept_invite(entity, additional)
|
||||
:member -> accept_member(entity, additional)
|
||||
end
|
||||
|
||||
with {:ok, entity, update_data} <- accept_res do
|
||||
@@ -158,12 +160,47 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_refresh_group(Member.t()) :: :ok | nil
|
||||
defp maybe_refresh_group(%Member{
|
||||
parent: %Actor{domain: parent_domain, url: parent_url},
|
||||
actor: %Actor{} = actor
|
||||
}) do
|
||||
unless is_nil(parent_domain),
|
||||
do: Refresher.fetch_group(parent_url, actor)
|
||||
@spec accept_member(Member.t(), map()) ::
|
||||
{:ok, Member.t(), Activity.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp accept_member(
|
||||
%Member{actor_id: actor_id, actor: actor, parent: %Actor{} = group} = member,
|
||||
%{moderator: %Actor{url: actor_url} = moderator}
|
||||
) do
|
||||
with %Actor{} <- Actors.get_actor!(actor_id),
|
||||
{:ok, %Member{id: member_id} = member} <-
|
||||
Actors.update_member(member, %{role: :member}) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
subject: "member_approved",
|
||||
moderator: moderator
|
||||
)
|
||||
|
||||
Absinthe.Subscription.publish(Endpoint, actor,
|
||||
group_membership_changed: [Actor.preferred_username_and_domain(group), actor_id]
|
||||
)
|
||||
|
||||
EmailMember.send_notification_to_approved_member(member)
|
||||
|
||||
Cachex.del(:activity_pub, "member_#{member_id}")
|
||||
|
||||
maybe_refresh_group(member)
|
||||
|
||||
accept_data = %{
|
||||
"type" => "Accept",
|
||||
"attributedTo" => member.parent.url,
|
||||
"to" => [member.parent.members_url],
|
||||
"cc" => [member.parent.url],
|
||||
"actor" => actor_url,
|
||||
"object" => Convertible.model_to_as(member),
|
||||
"id" => "#{Endpoint.url()}/accept/member/#{member_id}"
|
||||
}
|
||||
|
||||
{:ok, member, accept_data}
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_refresh_group(Member.t()) :: {:ok, Actor.t()} | {:error, atom()} | {:error}
|
||||
defp maybe_refresh_group(%Member{
|
||||
parent: %Actor{} = group
|
||||
}),
|
||||
do: Refresher.refresh_profile(group)
|
||||
end
|
||||
|
||||
@@ -69,14 +69,20 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
|
||||
end
|
||||
|
||||
def leave(
|
||||
%Actor{type: :Group, id: group_id, url: group_url, members_url: group_members_url},
|
||||
%Actor{id: actor_id, url: actor_url},
|
||||
%Actor{
|
||||
type: :Group,
|
||||
domain: group_domain,
|
||||
id: group_id,
|
||||
url: group_url,
|
||||
members_url: group_members_url
|
||||
},
|
||||
%Actor{id: actor_id, url: actor_url, domain: actor_domain},
|
||||
local,
|
||||
additional
|
||||
) do
|
||||
case Actors.get_member(actor_id, group_id) do
|
||||
{:ok, %Member{id: member_id} = member} ->
|
||||
if Map.get(additional, :force_member_removal, false) ||
|
||||
if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain ||
|
||||
!Actors.is_only_administrator?(member_id, group_id) do
|
||||
with {:ok, %Member{} = member} <- Actors.delete_member(member) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")
|
||||
|
||||
@@ -28,6 +28,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Reject do
|
||||
:join -> reject_join(entity, additional)
|
||||
:follow -> reject_follow(entity, additional)
|
||||
:invite -> reject_invite(entity, additional)
|
||||
:member -> reject_member(entity, additional)
|
||||
end
|
||||
|
||||
{:ok, activity} = create_activity(update_data, local)
|
||||
@@ -118,4 +119,28 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Reject do
|
||||
{:ok, member, accept_data}
|
||||
end
|
||||
end
|
||||
|
||||
@spec reject_member(Member.t(), map()) :: {:ok, Member.t(), Activity.t()} | any
|
||||
defp reject_member(
|
||||
%Member{actor_id: actor_id} = member,
|
||||
%{moderator: %Actor{url: actor_url}}
|
||||
) do
|
||||
with %Actor{} <- Actors.get_actor(actor_id),
|
||||
{:ok, %Member{url: member_url, id: member_id} = member} <-
|
||||
Actors.delete_member(member),
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
subject: "member_rejected"
|
||||
),
|
||||
accept_data <- %{
|
||||
"type" => "Reject",
|
||||
"actor" => actor_url,
|
||||
"attributedTo" => member.parent.url,
|
||||
"to" => [member.parent.members_url],
|
||||
"cc" => [member.parent.url],
|
||||
"object" => member_url,
|
||||
"id" => "#{Endpoint.url()}/reject/member/#{member_id}"
|
||||
} do
|
||||
{:ok, member, accept_data}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,23 +21,25 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Remove do
|
||||
@spec remove(Member.t(), Actor.t(), Actor.t(), boolean, map) ::
|
||||
{:ok, Activity.t(), Member.t()} | {:error, :member_not_found | Ecto.Changeset.t()}
|
||||
def remove(
|
||||
%Member{} = member,
|
||||
%Member{id: member_id},
|
||||
%Actor{type: :Group, url: group_url, members_url: group_members_url},
|
||||
%Actor{url: moderator_url} = moderator,
|
||||
local,
|
||||
_additional \\ %{}
|
||||
) do
|
||||
with {:ok, %Member{id: member_id}} <- Actors.update_member(member, %{role: :rejected}),
|
||||
%Member{} = member <- Actors.get_member(member_id) do
|
||||
with %Member{actor: %Actor{url: actor_url}} = member <- Actors.get_member(member_id),
|
||||
{:ok, %Member{}} <- Actors.delete_member(member) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
moderator: moderator,
|
||||
subject: "member_removed"
|
||||
)
|
||||
|
||||
Cachex.del(:activity_pub, "member_#{member_id}")
|
||||
|
||||
EmailMember.send_notification_to_removed_member(member)
|
||||
|
||||
remove_data = %{
|
||||
"to" => [group_members_url],
|
||||
"to" => [actor_url, group_members_url],
|
||||
"type" => "Remove",
|
||||
"actor" => moderator_url,
|
||||
"object" => member.url,
|
||||
|
||||
@@ -740,14 +740,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
) do
|
||||
Logger.info("Handle incoming to remove a member from a group")
|
||||
|
||||
with {:ok, %Actor{id: moderator_id} = moderator} <-
|
||||
with {:ok, %Actor{} = moderator} <-
|
||||
data |> Utils.get_actor() |> ActivityPubActor.get_or_fetch_actor_by_url(),
|
||||
{:ok, person_id} <- get_remove_object(object),
|
||||
{:ok, %Actor{type: :Group, id: group_id} = group} <-
|
||||
origin |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url(),
|
||||
{:is_admin, {:ok, %Member{role: role}}}
|
||||
when role in [:moderator, :administrator, :creator] <-
|
||||
{:is_admin, Actors.get_member(moderator_id, group_id)},
|
||||
{:is_admin, true} <-
|
||||
{:is_admin, can_remove_actor_from_group?(moderator, group)},
|
||||
{:is_member, {:ok, %Member{role: role} = member}} when role != :rejected <-
|
||||
{:is_member, Actors.get_member(person_id, group_id)} do
|
||||
Actions.Remove.remove(member, group, moderator, false)
|
||||
@@ -866,6 +865,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
|
||||
{:error, _err} ->
|
||||
case get_member(join_object) do
|
||||
{:ok, %Member{role: :not_approved} = member} ->
|
||||
do_handle_incoming_accept_join_group(member, :member, %{moderator: actor_accepting})
|
||||
|
||||
{:ok, %Member{invited_by: nil} = member} ->
|
||||
do_handle_incoming_accept_join_group(member, :join)
|
||||
|
||||
@@ -922,15 +924,17 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
|
||||
defp do_handle_incoming_accept_join_group(
|
||||
%Member{role: role, parent: _group} = member,
|
||||
type
|
||||
type,
|
||||
additional \\ %{}
|
||||
)
|
||||
when role in [:not_approved, :rejected, :invited] and type in [:join, :invite] do
|
||||
when role in [:not_approved, :rejected, :invited] and type in [:join, :invite, :member] do
|
||||
# Or maybe for groups it's the group that sends the Accept activity
|
||||
with {:ok, %Activity{} = activity, %Member{role: :member} = member} <-
|
||||
Actions.Accept.accept(
|
||||
type,
|
||||
member,
|
||||
false
|
||||
false,
|
||||
additional
|
||||
) do
|
||||
{:ok, activity, member}
|
||||
end
|
||||
@@ -1194,4 +1198,17 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
Actions.Create.create(:discussion, object_data, false)
|
||||
end
|
||||
end
|
||||
|
||||
@spec can_remove_actor_from_group?(Actor.t(), Actor.t()) :: boolean()
|
||||
defp can_remove_actor_from_group?(%Actor{} = moderator, %Actor{} = group) do
|
||||
case Actors.get_member(moderator.id, group.id) do
|
||||
{:ok, %Member{role: role}} when role in [:moderator, :administrator, :creator] ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
# If member moderator not found, it's probably because no one on this instance is member of this group yet
|
||||
# Therefore we can't access the list of admin/moderators and we just trust the origin domain
|
||||
moderator.domain == group.domain
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -151,7 +151,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
||||
|> Map.update(:message, nil, &String.trim(HTML.strip_tags(&1)))
|
||||
}) do
|
||||
{:ok, %Member{} = member} ->
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_joined")
|
||||
subject =
|
||||
case Mobilizon.Actors.get_default_member_role(group) do
|
||||
:not_approved -> "member_request"
|
||||
:member -> "member_joined"
|
||||
end
|
||||
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member, subject: subject)
|
||||
|
||||
Absinthe.Subscription.publish(Endpoint, actor,
|
||||
group_membership_changed: [Actor.preferred_username_and_domain(group), actor.id]
|
||||
|
||||
Reference in New Issue
Block a user