Absinthe middleware actor provider

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
chapril
Thomas Citharel 1 year ago
parent ae97339353
commit 55e7696230
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
  1. 40
      lib/graphql/middleware/current_actor_provider.ex
  2. 36
      lib/graphql/resolvers/activity.ex
  3. 63
      lib/graphql/resolvers/actor.ex
  4. 55
      lib/graphql/resolvers/comment.ex
  5. 38
      lib/graphql/resolvers/discussion.ex
  6. 72
      lib/graphql/resolvers/event.ex
  7. 16
      lib/graphql/resolvers/followers.ex
  8. 40
      lib/graphql/resolvers/group.ex
  9. 7
      lib/graphql/resolvers/media.ex
  10. 45
      lib/graphql/resolvers/member.ex
  11. 10
      lib/graphql/resolvers/participant.ex
  12. 177
      lib/graphql/resolvers/person.ex
  13. 26
      lib/graphql/resolvers/post.ex
  14. 28
      lib/graphql/resolvers/report.ex
  15. 33
      lib/graphql/resolvers/resource.ex
  16. 38
      lib/graphql/resolvers/todos.ex
  17. 70
      lib/graphql/resolvers/user.ex
  18. 4
      lib/graphql/schema.ex
  19. 1
      lib/mobilizon.ex
  20. 4
      lib/mobilizon/events/events.ex
  21. 30
      lib/mobilizon/users/users.ex
  22. 7
      lib/service/actor_suspension.ex
  23. 70
      lib/web/email/user.ex
  24. 4
      test/graphql/resolvers/comment_test.exs
  25. 4
      test/graphql/resolvers/discussion_test.exs
  26. 10
      test/graphql/resolvers/event_test.exs
  27. 6
      test/graphql/resolvers/member_test.exs
  28. 1
      test/graphql/resolvers/user_test.exs
  29. 4
      test/service/actor_suspension_test.exs

@ -0,0 +1,40 @@
defmodule Mobilizon.GraphQL.Middleware.CurrentActorProvider do
@moduledoc """
Absinthe Error Handler
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Users
alias Mobilizon.Users.User
@behaviour Absinthe.Middleware
@impl Absinthe.Middleware
@spec call(Absinthe.Resolution.t(), any) :: Absinthe.Resolution.t()
def call(
%Absinthe.Resolution{context: %{current_user: %User{id: user_id} = user} = context} =
resolution,
_config
) do
case Cachex.fetch(:default_actors, to_string(user_id), fn -> default(user) end) do
{status, %Actor{} = current_actor} when status in [:ok, :commit] ->
context = Map.put(context, :current_actor, current_actor)
%Absinthe.Resolution{resolution | context: context}
{_, nil} ->
resolution
end
end
def call(%Absinthe.Resolution{} = resolution, _config), do: resolution
@spec default(User.t()) :: {:commit, Actor.t()} | {:ignore, nil}
defp default(%User{} = user) do
case Users.get_actor_for_user(user) do
%Actor{} = actor ->
{:commit, actor}
nil ->
{:ignore, nil}
end
end
end

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Activities, Actors, Users}
alias Mobilizon.{Activities, Actors}
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Utils
alias Mobilizon.Storage.Page
@ -13,29 +13,23 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
require Logger
def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{
context: %{current_user: %User{role: role} = user}
context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}}
}) do
case Users.get_actor_for_user(user) do
%Actor{id: actor_id} = _actor ->
if Actors.is_member?(actor_id, group_id) or is_moderator(role) do
%Page{total: total, elements: elements} =
Activities.list_group_activities_for_member(
group_id,
actor_id,
[type: Map.get(args, :type), author: Map.get(args, :author)],
page,
limit
)
if Actors.is_member?(actor_id, group_id) or is_moderator(role) do
%Page{total: total, elements: elements} =
Activities.list_group_activities_for_member(
group_id,
actor_id,
[type: Map.get(args, :type), author: Map.get(args, :author)],
page,
limit
)
elements = Enum.map(elements, &Utils.transform_activity/1)
elements = Enum.map(elements, &Utils.transform_activity/1)
{:ok, %Page{total: total, elements: elements}}
else
{:error, :unauthorized}
end
nil ->
{:error, :user_actor_not_found}
{:ok, %Page{total: total, elements: elements}}
else
{:error, :unauthorized}
end
end

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Admin, Users}
alias Mobilizon.{Actors, Admin}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Service.Workers.Background
@ -32,39 +32,35 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
end
def suspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
context: %{
current_user: %User{role: role},
current_actor: %Actor{} = moderator_actor
}
})
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, dgettext("errors", "No remote profile found with this ID")}
end
else
{:moderator_actor, nil} ->
{:error, dgettext("errors", "No profile found for the moderator user")}
case Actors.get_actor_with_preload(id) do
%Actor{suspended: false} = actor ->
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, dgettext("errors", "No remote profile found with this ID")}
end
%Actor{suspended: true} ->
{:error, dgettext("errors", "Profile already suspended")}
{:error, _} ->
{:error, dgettext("errors", "Error while performing background task")}
end
end
@ -73,12 +69,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Actor do
end
def unsuspend_profile(_parent, %{id: id}, %{
context: %{current_user: %User{role: role} = user}
context: %{
current_user: %User{role: role},
current_actor: %Actor{} = moderator_actor
}
})
when is_moderator(role) do
with {:moderator_actor, %Actor{} = moderator_actor} <-
{:moderator_actor, Users.get_actor_for_user(user)},
%Actor{suspended: true} = actor <-
with %Actor{suspended: true} = actor <-
Actors.get_actor_with_preload(id, true),
{:delete_tombstones, {_, nil}} <-
{:delete_tombstones, Mobilizon.Tombstone.delete_actor_tombstones(id)},

@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Handles the comment-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Discussions, Events, Users}
alias Mobilizon.{Actors, Admin, Discussions, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment, as: CommentModel
alias Mobilizon.Events.{Event, EventOptions}
@ -23,12 +23,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
%{event_id: event_id} = args,
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: actor_id}
}
}
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:find_event,
with {:find_event,
{:ok,
%Event{
options: %EventOptions{comment_moderation: comment_moderation},
@ -59,12 +58,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
%{text: text, comment_id: comment_id},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: actor_id}
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{actor_id: comment_actor_id} = comment <-
with %CommentModel{actor_id: comment_actor_id} = comment <-
Mobilizon.Discussions.get_comment_with_preload(comment_id),
true <- actor_id == comment_actor_id,
{:ok, _, %CommentModel{} = comment} <- Comments.update_comment(comment, %{text: text}) do
@ -81,31 +79,34 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
%{comment_id: comment_id},
%{
context: %{
current_user: %User{role: role} = user
current_user: %User{role: role},
current_actor: %Actor{id: actor_id} = actor
}
}
) do
with {:actor, %Actor{id: actor_id} = actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{deleted_at: nil} = comment <-
Discussions.get_comment_with_preload(comment_id) do
cond do
{:comment_can_be_managed, true} == CommentModel.can_be_managed_by(comment, actor_id) ->
do_delete_comment(comment, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_comment(comment, actor),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", comment)
{:ok, res}
end
true ->
{:error, dgettext("errors", "You cannot delete this comment")}
end
else
case Discussions.get_comment_with_preload(comment_id) do
%CommentModel{deleted_at: nil} = comment ->
cond do
{:comment_can_be_managed, true} == CommentModel.can_be_managed_by(comment, actor_id) ->
do_delete_comment(comment, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_comment(comment, actor),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", comment)
{:ok, res}
end
true ->
{:error, dgettext("errors", "You cannot delete this comment")}
end
%CommentModel{deleted_at: deleted_at} when not is_nil(deleted_at) ->
{:error, dgettext("errors", "Comment is already deleted")}
nil ->
{:error, dgettext("errors", "Comment not found")}
end
end

@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
Handles the group-related GraphQL calls.
"""
alias Mobilizon.{Actors, Discussions, Users}
alias Mobilizon.{Actors, Discussions}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Federation.ActivityPub
@ -17,12 +17,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
%{page: page, limit: limit},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: actor_id}
}
}
) 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)},
with {: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, page, limit)}
else
@ -37,11 +36,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
def get_discussion(_parent, %{id: id}, %{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: creator_id}
}
}) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%Discussion{actor_id: actor_id} = discussion <-
with %Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion(id),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
{:ok, discussion}
@ -50,11 +48,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
def get_discussion(_parent, %{slug: slug}, %{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: creator_id}
}
}) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%Discussion{actor_id: actor_id} = discussion <-
with %Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion_by_slug(slug),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
{:ok, discussion}
@ -89,12 +86,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
%{title: title, text: text, actor_id: group_id},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: creator_id}
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(creator_id, group_id)},
with {:member, true} <- {:member, Actors.is_member?(creator_id, group_id)},
{:ok, _activity, %Discussion{} = discussion} <-
Comments.create_discussion(%{
title: title,
@ -120,12 +116,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
%{text: text, discussion_id: discussion_id},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: creator_id}
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion,
with {:no_discussion,
%Discussion{
actor_id: actor_id,
last_comment: %Comment{
@ -161,12 +156,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
%{title: title, discussion_id: discussion_id},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: creator_id}
}
}
) do
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-
@ -187,11 +181,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
def delete_discussion(_parent, %{discussion_id: discussion_id}, %{
context: %{
current_user: %User{} = user
current_user: %User{},
current_actor: %Actor{id: creator_id} = actor
}
}) do
with {:actor, %Actor{id: creator_id} = actor} <- {:actor, Users.get_actor_for_user(user)},
{:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <-

@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
Handles the event-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Events, Users}
alias Mobilizon.{Actors, Admin, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Events.{Event, EventParticipantStats}
@ -24,11 +24,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def organizer_for_event(
%Event{attributed_to_id: attributed_to_id, organizer_actor_id: organizer_actor_id},
_args,
%{context: %{current_user: %User{role: user_role} = user}} = _resolution
%{
context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}}
} = _resolution
)
when not is_nil(attributed_to_id) do
with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id),
%Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)},
%Actor{} = actor <- Actors.get_actor(organizer_actor_id) do
@ -77,10 +78,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
defp find_private_event(
_parent,
%{uuid: uuid},
%{context: %{current_user: %User{} = user}} = _resolution
%{context: %{current_actor: %Actor{} = profile}} = _resolution
) do
%Actor{} = profile = Users.get_actor_for_user(user)
case Events.get_event_by_uuid_with_preload(uuid) do
# Event attributed to group
%Event{attributed_to: %Actor{}} = event ->
@ -136,12 +135,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def list_participants_for_event(
%Event{id: event_id} = event,
%{page: page, limit: limit, roles: roles},
%{context: %{current_user: %User{} = user}} = _resolution
%{context: %{current_actor: %Actor{} = actor}} = _resolution
) do
with %Actor{} = actor <- Users.get_actor_for_user(user),
# Check that moderator has right
{:event_can_be_managed, true} <-
{:event_can_be_managed, can_event_be_updated_by?(event, actor)} do
# Check that moderator has right
if can_event_be_updated_by?(event, actor) do
roles =
case roles do
nil ->
@ -160,9 +157,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
participants = Events.list_participants_for_event(event_id, roles, page, limit)
{:ok, participants}
else
{:event_can_be_managed, _} ->
{:error,
dgettext("errors", "Provided profile doesn't have moderator permissions on this event")}
{:error,
dgettext("errors", "Provided profile doesn't have moderator permissions on this event")}
end
end
@ -290,13 +286,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def update_event(
_parent,
%{event_id: event_id} = args,
%{context: %{current_user: %User{} = user}} = _resolution
%{context: %{current_user: %User{} = user, current_actor: %Actor{} = actor}} = _resolution
) do
# See https://github.com/absinthe-graphql/absinthe/issues/490
args = Map.put(args, :options, args[:options] || %{})
with {:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
%Actor{} = actor <- Users.get_actor_for_user(user),
{:ok, args} <- verify_profile_change(args, event, user, actor),
{:event_can_be_managed, true} <-
{:event_can_be_managed, can_event_be_updated_by?(event, actor)},
@ -335,27 +330,32 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
def delete_event(
_parent,
%{event_id: event_id},
%{context: %{current_user: %User{role: role} = user}}
%{
context: %{
current_user: %User{role: role},
current_actor: %Actor{id: actor_id} = actor
}
}
) do
with {:ok, %Event{local: is_local} = event} <- Events.get_event_with_preload(event_id),
%Actor{id: actor_id} = actor <- Users.get_actor_for_user(user) do
cond do
{:event_can_be_managed, true} ==
{:event_can_be_managed, can_event_be_deleted_by?(event, actor)} ->
do_delete_event(event, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_event(event, actor, !is_local),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", event)
{:ok, res}
end
true ->
{:error, dgettext("errors", "You cannot delete this event")}
end
else
case Events.get_event_with_preload(event_id) do
{:ok, %Event{local: is_local} = event} ->
cond do
{:event_can_be_managed, true} ==
{:event_can_be_managed, can_event_be_deleted_by?(event, actor)} ->
do_delete_event(event, actor)
role in [:moderator, :administrator] ->
with {:ok, res} <- do_delete_event(event, actor, !is_local),
%Actor{} = actor <- Actors.get_actor(actor_id) do
Admin.log_action(actor, "delete", event)
{:ok, res}
end
true ->
{:error, dgettext("errors", "You cannot delete this event")}
end
{:error, :event_not_found} ->
{:error, dgettext("errors", "Event not found")}
end

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Storage.Page
@ -16,17 +16,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
%{page: page, limit: limit} = args,
%{
context: %{
current_user: %User{role: user_role} = user
current_user: %User{role: user_role},
current_actor: %Actor{id: actor_id}
}
}
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <-
{:member, Actors.is_moderator?(actor_id, group_id) or is_moderator(user_role)} do
if Actors.is_moderator?(actor_id, group_id) or is_moderator(user_role) do
{:ok,
Actors.list_paginated_followers_for_actor(group, Map.get(args, :approved), page, limit)}
else
_ -> {:error, :unauthorized}
{:error, :unauthorized}
end
end
@ -35,11 +34,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
@spec update_follower(any(), map(), map()) :: {:ok, Follower.t()} | {:error, any()}
def update_follower(_, %{id: follower_id, approved: approved}, %{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: actor_id}
}
}) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <-
with %Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <-
Actors.get_follower(follower_id),
{:member, true} <-
{:member, Actors.is_moderator?(actor_id, group_id)},

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.{Actors, Events}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
@ -23,13 +23,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
%{preferred_username: name} = args,
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{id: actor_id}
}
}
) do
with {:group, {:ok, %Actor{id: group_id, suspended: false} = group}} <-
{:group, ActivityPubActor.find_or_make_group_from_nickname(name)},
{: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, group}
else
@ -119,12 +118,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
args,
%{
context: %{
current_user: user
current_actor: %Actor{id: creator_actor_id} = creator_actor
}
}
) do
with %Actor{id: creator_actor_id} = creator_actor <- Users.get_actor_for_user(user),
args when is_map(args) <- Map.update(args, :preferred_username, "", &String.downcase/1),
with args when is_map(args) <- Map.update(args, :preferred_username, "", &String.downcase/1),
args when is_map(args) <- Map.put(args, :creator_actor, creator_actor),
args when is_map(args) <- Map.put(args, :creator_actor_id, creator_actor_id),
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
@ -152,12 +150,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
%{id: group_id} = args,
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{} = updater_actor
}
}
) do
with %Actor{} = updater_actor <- Users.get_actor_for_user(user),
{:administrator, true} <-
with {:administrator, true} <-
{:administrator, Actors.is_administrator?(updater_actor.id, group_id)},
args when is_map(args) <- Map.put(args, :updater_actor, updater_actor),
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
@ -188,12 +185,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
%{group_id: group_id},
%{
context: %{
current_user: user
current_actor: %Actor{id: actor_id} = actor
}
}
) do
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)},
{:ok, _activity, group} <- ActivityPub.delete(group, actor, true) do
@ -219,10 +215,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
Join an existing group
"""
def join_group(_parent, %{group_id: group_id} = args, %{
context: %{current_user: %User{} = user}
context: %{current_actor: %Actor{} = actor}
}) do
with %Actor{} = actor <- Users.get_actor_for_user(user),
{:ok, %Actor{type: :Group} = group} <-
with {:ok, %Actor{type: :Group} = group} <-
Actors.get_group_by_actor_id(group_id),
{:error, :member_not_found} <- Actors.get_member(actor.id, group.id),
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
@ -253,12 +248,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
%{group_id: group_id},
%{
context: %{
current_user: %User{} = user
current_actor: %Actor{} = actor
}
}
) do
with {:actor, %Actor{} = actor} <- {:actor, Users.get_actor_for_user(user)},
{:group, %Actor{type: :Group} = group} <- {:group, Actors.get_actor(group_id)},
with {:group, %Actor{type: :Group} = group} <- {:group, Actors.get_actor(group_id)},
{:ok, _activity, %Member{} = member} <- ActivityPub.leave(group, actor, true) do
{:ok, member}
else
@ -286,13 +280,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} = args,
%{
context: %{
current_user: %User{role: user_role} = user
current_user: %User{role: user_role},
current_actor: %Actor{id: actor_id}
}
}
) 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) or is_moderator(user_role)} do
if 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(
@ -304,8 +297,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
limit
)}
else
{:member, false} ->
find_events_for_group(group, args, nil)
find_events_for_group(group, args, nil)
end
end

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.{Medias, Users}
alias Mobilizon.Medias
alias Mobilizon.Medias.Media
alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@ -44,10 +44,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
def upload_media(
_parent,
%{file: %Plug.Upload{} = file} = args,
%{context: %{current_user: %User{} = user}}
%{context: %{current_actor: %Actor{id: actor_id}}}
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:ok,
with {:ok,
%{
name: _name,
url: url,

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
@ -21,12 +21,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
%Actor{id: group_id} = group,
%{page: page, limit: limit, roles: roles},
%{
context: %{current_user: %User{role: user_role} = user}
context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}}
} = _resolution
) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do
roles =
case roles do
"" ->
@ -42,11 +40,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
%Page{} = page = Actors.list_members_for_group(group, roles, page, limit)
{:ok, page}
else
{:member, false} ->
# Actor is not member of group, fallback to public
with %Page{} = page <- Actors.list_members_for_group(group) do
{:ok, %Page{page | elements: []}}
end
# Actor is not member of group, fallback to public
%Page{} = page = Actors.list_members_for_group(group)
{:ok, %Page{page | elements: []}}
end
end
@ -59,10 +55,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
def invite_member(
_parent,
%{group_id: group_id, target_actor_username: target_actor_username},
%{context: %{current_user: %User{} = user}}
%{context: %{current_actor: %Actor{id: actor_id} = actor}}
) do
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:has_rights_to_invite, {:ok, %Member{role: role}}}
when role in [:moderator, :administrator, :creator] <-
{:has_rights_to_invite, Actors.get_member(actor_id, group_id)},
@ -97,9 +92,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
end
end
def accept_invitation(_parent, %{id: member_id}, %{context: %{current_user: %User{} = user}}) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
%Member{actor: %Actor{id: member_actor_id}} = member <-
def accept_invitation(_parent, %{id: member_id}, %{
context: %{current_actor: %Actor{id: actor_id}}
}) do
with %Member{actor: %Actor{id: member_actor_id}} = member <-
Actors.get_member(member_id),
{:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
{:ok, _activity, %Member{} = member} <-
@ -115,9 +111,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
end
end
def reject_invitation(_parent, %{id: member_id}, %{context: %{current_user: %User{} = user}}) do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:invitation_exists, %Member{actor: %Actor{id: member_actor_id}} = member} <-
def reject_invitation(_parent, %{id: member_id}, %{
context: %{current_actor: %Actor{id: actor_id}}
}) do
with {:invitation_exists, %Member{actor: %Actor{id: member_actor_id}} = member} <-
{:invitation_exists, Actors.get_member(member_id)},
{:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
{:ok, _activity, %Member{} = member} <-
@ -137,10 +134,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
end
def update_member(_parent, %{member_id: member_id, role: role}, %{
context: %{current_user: %User{} = user}
context: %{current_actor: %Actor{} = moderator}
}) do
with %Actor{} = moderator <- Users.get_actor_for_user(user),
%Member{} = member <- Actors.get_member(member_id),
with %Member{} = member <- Actors.get_member(member_id),
{:ok, _activity, %Member{} = member} <-
ActivityPub.update(member, %{role: role}, true, %{moderator: moderator}) do
{:ok, member}
@ -161,10 +157,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
do: {:error, "You must be logged-in to update a member"}
def remove_member(_parent, %{member_id: member_id, group_id: group_id}, %{
context: %{current_user: %User{} = user}
context: %{current_actor: %Actor{id: moderator_id} = moderator}
}) do
with %Actor{id: moderator_id} = moderator <- Users.get_actor_for_user(user),
%Member{role: role} = member when role != :rejected <- Actors.get_member(member_id),
with %Member{role: role} = member when role != :rejected <- Actors.get_member(member_id),
%Actor{type: :Group} = group <- Actors.get_actor(group_id),
{:has_rights_to_remove, {:ok, %Member{role: role}}}
when role in [:moderator, :administrator, :creator] <-

@ -2,7 +2,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
@moduledoc """
Handles the participation-related GraphQL calls.
"""
alias Mobilizon.{Actors, Config, Crypto, Events, Users}
alias Mobilizon.{Actors, Config, Crypto, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.GraphQL.API.Participations
@ -225,14 +225,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
%{id: participation_id, role: new_role},
%{
context: %{
current_user: user
current_actor: %Actor{} = moderator_actor
}
}
) do
# Check that moderator provided is rightly authenticated
with %Actor{} = moderator_actor <- Users.get_actor_for_user(user),
# Check that participation already exists
{:has_participation, %Participant{role: old_role, event_id: event_id} = participation} <-
# Check that participation already exists
with {:has_participation, %Participant{role: old_role, event_id: event_id} = participation} <-
{:has_participation, Events.get_participant(participation_id)},
{:same_role, false} <- {:same_role, new_role == old_role},
# Check that moderator has right

@ -91,8 +91,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
@doc """
Returns the current actor for the currently logged-in user
"""
def get_current_person(_parent, _args, %{context: %{current_user: user}}) do
{:ok, Users.get_actor_for_user(user)}
@spec get_current_person(any, any, Absinthe.Resolution.t()) ::
{:error, :unauthenticated} | {:ok, Actor.t()}
def get_current_person(_parent, _args, %{context: %{current_actor: %Actor{} = actor}}) do
{:ok, actor}
end
def get_current_person(_parent, _args, %{context: %{current_user: %User{}}}) do
{:error, :no_current_person}
end
def get_current_person(_parent, _args, _resolution) do
@ -102,6 +108,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
@doc """
Returns the list of identities for the logged-in user
"""
@spec identities(any, any, Absinthe.Resolution.t()) ::
{:error, :unauthenticated} | {:ok, list(Actor.t())}
def identities(_parent, _args, %{context: %{current_user: user}}) do
{:ok, Users.get_actors_for_user(user)}
end
@ -148,21 +156,24 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
require Logger
args = Map.put(args, :user_id, user.id)
with {:find_actor, %Actor{} = actor} <-
{:find_actor, Actors.get_actor(id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
{:ok, _activity, %Actor{} = actor} <- ActivityPub.update(actor, args, true) do
{:ok, actor}
else
{:picture, {:error, :file_too_large}} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
case owned_actor(user, id) do
{:ok, %Actor{} = actor} ->
case save_attached_pictures(args) do
args when is_map(args) ->
case ActivityPub.update(actor, args, true) do
{:ok, _activity, %Actor{} = actor} ->
{:ok, actor}
{:find_actor, nil} ->
{:error, dgettext("errors", "Profile not found")}
{:error, err} ->
{:error, err}
end
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :file_too_large} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
end
{:error, err} ->
{:error, err}
end
end
@ -176,59 +187,88 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
def delete_person(
_parent,
%{id: id} = _args,
%{context: %{current_user: user}} = _resolution
%{context: %{current_user: %User{} = user}} = _resolution
) do
case owned_actor(user, id) do
{:ok, %Actor{} = actor} ->
if last_identity?(user) do
{:error, dgettext("errors", "Cannot remove the last identity of a user")}
else
if last_admin_of_a_group?(actor.id) do
{:error, dgettext("errors", "Cannot remove the last administrator of a group")}
else
Actors.delete_actor(actor)
end
end
{:error, err} ->
{:error, err}
end
end
def delete_person(_parent, _args, _resolution) do
{:error, :unauthenticated}
end
@spec owned_actor(User.t(), integer() | String.t()) :: {:error, String.t()} | {:ok, Actor.t()}
defp owned_actor(%User{} = user, actor_id) do
with {:find_actor, %Actor{} = actor} <-
{:find_actor, Actors.get_actor(id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
{: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
{:find_actor, Actors.get_actor(actor_id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id) do
{:ok, actor}
else
{:find_actor, nil} ->
{:error, dgettext("errors", "Profile not found")}
{:last_identity, true} ->
{:error, dgettext("errors", "Cannot remove the last identity of a user")}
{:last_admin, true} ->
{:error, dgettext("errors", "Cannot remove the last administrator of a group")}
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
end
end
def delete_person(_parent, _args, _resolution) do
{:error, :unauthenticated}
end
defp last_identity?(user) do
length(Users.get_actors_for_user(user)) <= 1
end
@spec save_attached_pictures(map()) :: map() | {:error, any()}
defp save_attached_pictures(args) do
with args when is_map(args) <- save_attached_picture(args, :avatar),
args when is_map(args) <- save_attached_picture(args, :banner) do
args
case save_attached_picture(args, :avatar) do
{:error, err} ->
{:error, err}
args when is_map(args) ->
case save_attached_picture(args, :banner) do
{:error, err} ->
{:error, err}
args when is_map(args) ->
args
end
end
end
@spec save_attached_picture(map(), :avatar | :banner) :: map() | {:error, any}
defp save_attached_picture(args, key) do
if Map.has_key?(args, key) && !is_nil(args[key][:media]) do
with media when is_map(media) <- save_picture(args[key][:media], key) do
Map.put(args, key, media)
case save_picture(args[key][:media], key) do
{:error, err} ->
{:error, err}
media when is_map(media) ->
Map.put(args, key, media)
end
else
args
end
end
@spec save_picture(map(), :avatar | :banner) :: {:ok, map()} | {:error, any()}
defp save_picture(media, key) do
with {:ok, %{name: name, url: url, content_type: content_type, size: size}} <-
Upload.store(media.file, type: key, description: media.alt) do
%{"name" => name, "url" => url, "content_type" => content_type, "size" => size}
case Upload.store(media.file, type: key, description: media.alt) do
{:ok, %{name: name, url: url, content_type: content_type, size: size}} ->
%{"name" => name, "url" => url, "content_type" => content_type, "size" => size}
{:error, err} ->
{:error, err}
end
end
@ -237,30 +277,37 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
"""
def register_person(_parent, args, _resolution) do
# When registering, email is assumed confirmed (unlike changing email)
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email, unconfirmed: false),
user_actor <- Users.get_actor_for_user(user),
no_actor <- is_nil(user_actor),
{:no_actor, true} <- {:no_actor, no_actor},
args <- Map.update(args, :preferred_username, "", &String.downcase/1),
args <- Map.put(args, :user_id, user.id),
{:picture, args} when is_map(args) <- {:picture, save_attached_pictures(args)},
{:ok, %Actor{} = new_person} <- Actors.new_person(args, true) do
{:ok, new_person}
else
{:picture, {:error, :file_too_large}} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
case Users.get_user_by_email(args.email, unconfirmed: false) do
{:ok, %User{} = user} ->
if is_nil(Users.get_actor_for_user(user)) do
# No profile yet, we can create one
case prepare_args(args, user) do
args when is_map(args) ->
Actors.new_person(args, true)
{:error, :file_too_large} ->
{:error, dgettext("errors", "The provided picture is too heavy")}
{:error, _err} ->
{:error, dgettext("errors", "Error while uploading pictures")}
end
else
{:error, dgettext("errors", "You already have a profile for this user")}
end
{:error, :user_not_found} ->
{:error, dgettext("errors", "No user with this email was found")}
{:no_actor, _} ->
{:error, dgettext("errors", "You already have a profile for this user")}
{:error, %Ecto.Changeset{} = e} ->
{:error, e}
end
end
@spec prepare_args(map(), User.t()) :: map() | {:error, any()}
defp prepare_args(args, %User{} = user) do
args
|> Map.update(:preferred_username, "", &String.downcase/1)
|> Map.put(:user_id, user.id)
|> save_attached_pictures()
end
@doc """
Returns the participations, optionally restricted to an event
"""
@ -269,17 +316,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
%{event_id: event_id},
%{context: %{current_user: %User{} = user}}
) do
with {:can_get_participations, true} <-
{:can_get_participations, user_can_access_person_details?(person, user)},
{:no_participant, {:ok, %Participant{} = participant}} <-
{:no_participant, Events.get_participant(event_id, actor_id)} do
{:ok, %Page{elements: [participant], total: 1}}
if user_can_access_person_details?(person, user) do
case Events.get_participant(event_id, actor_id) do
{:ok, %Participant{} = participant} -> {:ok, %Page{elements: [participant], total: 1}}
{:error, :participant_not_found} -> {:ok, %Page{elements: [], total: 0}}
end
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:no_participant, _} ->
{:ok, %Page{elements: [], total: 0}}
{:error, :unauthorized}
end
end

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
"""
import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Posts, Users}
alias Mobilizon.{Actors, Posts}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Permission, Utils}
@ -27,12 +27,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
%{page: page, limit: limit} = args,
%{
context: %{
current_user: %User{role: user_role} = user
current_user: %User{role: user_role},
current_actor: %Actor{id: actor_id}
}
} = _resolution
) do