Allow group admins to moderate new members

Closes #881

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-11-12 15:42:52 +01:00
parent ae24fa17d5
commit 6eba531c89
28 changed files with 795 additions and 212 deletions

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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,