Add group admin profiles

And other fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-08-27 11:53:24 +02:00
parent 8afda73214
commit 1984f71cbf
107 changed files with 3514 additions and 1146 deletions

View File

@@ -18,7 +18,7 @@ defmodule Mobilizon.GraphQL.API.Groups do
with preferred_username <-
args |> Map.get(:preferred_username) |> HTML.strip_tags() |> String.trim(),
{:existing_group, nil} <-
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
{:existing_group, Actors.get_local_actor_by_name(preferred_username)},
args <- args |> Map.put(:type, :Group),
{:ok, %Activity{} = activity, %Actor{} = group} <-
ActivityPub.create(:actor, args, true, %{"actor" => args.creator_actor.url}) do

View File

@@ -0,0 +1,104 @@
defmodule Mobilizon.GraphQL.Resolvers.Actor do
@moduledoc """
Handles the group-related GraphQL calls.
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Admin, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Refresher
alias Mobilizon.Users.User
require Logger
def refresh_profile(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}})
when is_admin(role) do
case Actors.get_actor(id) do
%Actor{domain: domain} = actor when not is_nil(domain) ->
Refresher.refresh_profile(actor)
{:ok, actor}
%Actor{} ->
{:error, "Only remote actors may be refreshed"}
_ ->
{:error, "No actor found with this ID"}
end
end
def suspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
})
when is_moderator(role) do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(user)},
%Actor{suspended: false} = actor <- Actors.get_actor_with_preload(id) do
case actor do
# Suspend a group on this instance
%Actor{type: :Group, domain: nil} ->
Logger.debug("We're suspending a group on this very instance")
ActivityPub.delete(actor, moderator_actor, true, %{suspension: true})
Admin.log_action(moderator_actor, "suspend", actor)
{:ok, actor}
# Delete a remote actor
%Actor{domain: domain} when not is_nil(domain) ->
Logger.debug("We're just deleting a remote instance")
Actors.delete_actor(actor, suspension: true)
Admin.log_action(moderator_actor, "suspend", actor)
{:ok, actor}
%Actor{domain: nil} ->
{:error, "No remote profile found with this ID"}
end
else
{:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"}
%Actor{suspended: true} ->
{:error, "Actor already suspended"}
{:error, _} ->
{:error, "Error while performing background task"}
end
end
def suspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can suspend a profile"}
end
def unsuspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
})
when is_moderator(role) do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(user)},
%Actor{suspended: true} = actor <-
Actors.get_actor_with_preload(id, true),
{:delete_tombstones, {_, nil}} <-
{:delete_tombstones, Mobilizon.Tombstone.delete_actor_tombstones(id)},
{:ok, %Actor{} = actor} <- Actors.update_actor(actor, %{suspended: false}),
{:ok, %Actor{} = actor} <- refresh_if_remote(actor),
{:ok, _} <- Admin.log_action(moderator_actor, "unsuspend", actor) do
{:ok, actor}
else
{:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"}
nil ->
{:error, "No remote profile found with this ID"}
{:error, _} ->
{:error, "Error while performing background task"}
end
end
def unsuspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can unsuspend a profile"}
end
@spec refresh_if_remote(Actor.t()) :: {:ok, Actor.t()}
defp refresh_if_remote(%Actor{domain: nil} = actor), do: {:ok, actor}
defp refresh_if_remote(%Actor{} = actor), do: Refresher.refresh_profile(actor)
end

View File

@@ -20,8 +20,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, Discussions.find_discussions_for_actor(group_id)}
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do
{:ok, Discussions.find_discussions_for_actor(group)}
else
{:member, false} ->
{:ok, %Page{total: 0, elements: []}}
@@ -174,6 +175,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
{:ok, _activity, %Discussion{} = discussion} <-
ActivityPub.delete(discussion, actor) do
{:ok, discussion}
else
{:no_discussion, _} ->
{:error, "No discussion with ID #{discussion_id}"}
{:member, _} ->
{:error, "You are not a member of the group the discussion belongs to"}
end
end
end

View File

@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
Handles the group-related GraphQL calls.
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
@@ -54,13 +55,46 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
end
end
@doc """
Get a group
"""
def get_group(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}}) do
with %Actor{type: :Group, suspended: suspended} = actor <-
Actors.get_actor_with_preload(id, true),
true <- suspended == false or is_moderator(role) do
{:ok, actor}
else
_ ->
{:error, "Group with ID #{id} not found"}
end
end
@doc """
Lists all groups
"""
def list_groups(_parent, %{page: page, limit: limit}, _resolution) do
{:ok, Actors.list_groups(page, limit)}
def list_groups(
_parent,
%{
preferred_username: preferred_username,
name: name,
domain: domain,
local: local,
suspended: suspended,
page: page,
limit: limit
},
%{
context: %{current_user: %User{role: role}}
}
)
when is_moderator(role) do
{:ok,
Actors.list_actors(:Group, preferred_username, name, domain, local, suspended, page, limit)}
end
def list_groups(_parent, _args, _resolution),
do: {:error, "You may not list groups unless moderator."}
@doc """
Create a new group. The creator is automatically added as admin
"""
@@ -127,28 +161,23 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
"""
def delete_group(
_parent,
%{group_id: group_id, actor_id: actor_id},
%{group_id: group_id},
%{
context: %{
current_user: user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)},
group <- Actors.delete_group!(group) do
{:ok, _activity, group} <- ActivityPub.delete(group, actor, true) do
{:ok, %{id: group.id}}
else
{:error, :group_not_found} ->
{:error, "Group not found"}
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:error, :member_not_found} ->
{:error, "Actor id is not a member of this group"}
@@ -231,7 +260,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:group, nil} ->
{:error, "Group not found"}
{:is_only_admin, true} ->
{:is_not_only_admin, false} ->
{:error, "You can't leave this group because you are the only administrator"}
end
end
@@ -245,12 +274,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
_args,
%{
context: %{
current_user: %User{} = user
current_user: %User{role: user_role} = user
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
# TODO : Handle public / restricted to group members events
{:ok, Events.list_organized_events_for_group(group)}
else

View File

@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
Handles the member-related GraphQL calls
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
@@ -19,11 +20,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
%Actor{id: group_id} = group,
%{page: page, limit: limit, roles: roles},
%{
context: %{current_user: %User{} = user}
context: %{current_user: %User{role: user_role} = user}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
roles =
case roles do
"" ->
@@ -167,9 +169,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
{:error, :member_not_found} ->
true
err ->
require Logger
Logger.error(inspect(err))
_err ->
false
end
end

View File

@@ -7,7 +7,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin
alias Mobilizon.Events
alias Mobilizon.Events.Participant
alias Mobilizon.Storage.Page
@@ -321,64 +320,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
end
end
def suspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
})
when is_moderator(role) do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(user)},
%Actor{suspended: false} = actor <- Actors.get_remote_actor_with_preload(id),
{:ok, _} <- Actors.delete_actor(actor),
{:ok, _} <- Admin.log_action(moderator_actor, "suspend", actor) do
{:ok, actor}
else
{:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"}
%Actor{suspended: true} ->
{:error, "Actor already suspended"}
nil ->
{:error, "No remote profile found with this ID"}
{:error, _} ->
{:error, "Error while performing background task"}
end
end
def suspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can suspend a profile"}
end
def unsuspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
})
when is_moderator(role) do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(user)},
%Actor{preferred_username: preferred_username, domain: domain} = actor <-
Actors.get_remote_actor_with_preload(id, true),
{:ok, _} <- Actors.update_actor(actor, %{suspended: false}),
{:ok, %Actor{} = actor} <-
ActivityPub.make_actor_from_nickname("#{preferred_username}@#{domain}"),
{:ok, _} <- Admin.log_action(moderator_actor, "unsuspend", actor) do
{:ok, actor}
else
{:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"}
nil ->
{:error, "No remote profile found with this ID"}
{:error, _} ->
{:error, "Error while performing background task"}
end
end
def unsuspend_profile(_parent, _args, _resolution) do
{:error, "Only moderators and administrators can unsuspend a profile"}
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

View File

@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
Handles the posts-related GraphQL calls
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Posts, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
@@ -24,12 +25,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
%{page: page, limit: limit} = args,
%{
context: %{
current_user: %User{} = user
current_user: %User{role: user_role} = user
}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
{:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)},
%Page{} = page <- Posts.get_posts_for_group(group, page, limit) do
{:ok, page}
else

View File

@@ -175,6 +175,7 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:discussion_mutations)
import_fields(:resource_mutations)
import_fields(:post_mutations)
import_fields(:actor_mutations)
end
@desc """

View File

@@ -5,6 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
use Absinthe.Schema.Notation
alias Mobilizon.Actors.Actor
alias Mobilizon.GraphQL.Resolvers.Actor, as: ActorResolver
alias Mobilizon.GraphQL.Schema
import_types(Schema.Actors.FollowerType)
@@ -59,4 +60,21 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
value(:Organization, description: "An ActivityPub Organization")
value(:Service, description: "An ActivityPub Service")
end
object :actor_mutations do
field :suspend_profile, :deleted_object do
arg(:id, non_null(:id), description: "The profile ID to suspend")
resolve(&ActorResolver.suspend_profile/3)
end
field :unsuspend_profile, :actor do
arg(:id, non_null(:id), description: "The profile ID to unsuspend")
resolve(&ActorResolver.unsuspend_profile/3)
end
field :refresh_profile, :actor do
arg(:id, non_null(:id))
resolve(&ActorResolver.refresh_profile/3)
end
end
end

View File

@@ -130,11 +130,22 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
object :group_queries do
@desc "Get all groups"
field :groups, :paginated_group_list do
arg(:preferred_username, :string, default_value: "")
arg(:name, :string, default_value: "")
arg(:domain, :string, default_value: "")
arg(:local, :boolean, default_value: true)
arg(:suspended, :boolean, default_value: false)
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Group.list_groups/3)
end
@desc "Get a group by its ID"
field :get_group, :group do
arg(:id, non_null(:id))
resolve(&Group.get_group/3)
end
@desc "Get a group by its preferred username"
field :group, :group do
arg(:preferred_username, non_null(:string))
@@ -199,7 +210,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
@desc "Delete a group"
field :delete_group, :deleted_object do
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Group.delete_group/3)
end

View File

@@ -188,16 +188,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
resolve(handle_errors(&Person.register_person/3))
end
field :suspend_profile, :deleted_object do
arg(:id, :id, description: "The profile ID to suspend")
resolve(&Person.suspend_profile/3)
end
field :unsuspend_profile, :person do
arg(:id, :id, description: "The profile ID to unsuspend")
resolve(&Person.unsuspend_profile/3)
end
end
object :person_subscriptions do

View File

@@ -33,6 +33,7 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
field(:inserted_at, :datetime)
field(:updated_at, :datetime)
field(:deleted_at, :datetime)
field(:published_at, :datetime)
end
@desc "The list of visibility options for a comment"