Introduce backend for reports

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-07-23 13:49:22 +02:00
parent 33a8da4570
commit aef841e192
36 changed files with 2028 additions and 36 deletions

View File

@@ -41,7 +41,7 @@ defmodule Mobilizon.Service.ActivityPub do
@spec insert(map(), boolean()) :: {:ok, %Activity{}} | {:error, any()}
def insert(map, local \\ true) when is_map(map) do
with map <- lazy_put_activity_defaults(map),
:ok <- insert_full_object(map) do
{:ok, object} <- insert_full_object(map) do
object_id = if is_map(map["object"]), do: map["object"]["id"], else: map["id"]
map = if local, do: Map.put(map, "id", "#{object_id}/activity"), else: map
@@ -55,7 +55,7 @@ defmodule Mobilizon.Service.ActivityPub do
# Notification.create_notifications(activity)
# stream_out(activity)
{:ok, activity}
{:ok, activity, object}
else
%Activity{} = activity -> {:ok, activity}
error -> {:error, error}
@@ -130,7 +130,7 @@ defmodule Mobilizon.Service.ActivityPub do
additional
),
:ok <- Logger.debug(inspect(create_data)),
{:ok, activity} <- insert(create_data, local),
{:ok, activity, _object} <- insert(create_data, local),
:ok <- maybe_federate(activity) do
# {:ok, actor} <- Actors.increase_event_count(actor) do
{:ok, activity}
@@ -147,7 +147,7 @@ defmodule Mobilizon.Service.ActivityPub do
local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
@@ -164,7 +164,7 @@ defmodule Mobilizon.Service.ActivityPub do
"actor" => actor,
"object" => object
},
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
@@ -179,7 +179,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# with nil <- get_existing_like(url, object),
# like_data <- make_like_data(user, object, activity_id),
# {:ok, activity} <- insert(like_data, local),
# {:ok, activity, _object} <- insert(like_data, local),
# {:ok, object} <- add_like_to_object(activity, object),
# :ok <- maybe_federate(activity) do
# {:ok, activity, object}
@@ -197,7 +197,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
# unlike_data <- make_unlike_data(actor, like_activity, activity_id),
# {:ok, unlike_activity} <- insert(unlike_data, local),
# {:ok, unlike_activity, _object} <- insert(unlike_data, local),
# {:ok, _activity} <- Repo.delete(like_activity),
# {:ok, object} <- remove_like_from_object(like_activity, object),
# :ok <- maybe_federate(unlike_activity) do
@@ -215,7 +215,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# #with true <- is_public?(object),
# with announce_data <- make_announce_data(actor, object, activity_id),
# {:ok, activity} <- insert(announce_data, local),
# {:ok, activity, _object} <- insert(announce_data, local),
# # {:ok, object} <- add_announce_to_object(activity, object),
# :ok <- maybe_federate(activity) do
# {:ok, activity, object}
@@ -232,7 +232,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
# unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
# {:ok, unannounce_activity} <- insert(unannounce_data, local),
# {:ok, unannounce_activity, _object} <- insert(unannounce_data, local),
# :ok <- maybe_federate(unannounce_activity),
# {:ok, _activity} <- Repo.delete(announce_activity),
# {:ok, object} <- remove_announce_from_object(announce_activity, object) do
@@ -250,7 +250,7 @@ defmodule Mobilizon.Service.ActivityPub do
activity_follow_id <-
activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity",
data <- make_follow_data(followed, follower, activity_follow_id),
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@@ -267,9 +267,9 @@ defmodule Mobilizon.Service.ActivityPub do
with {:ok, %Follower{id: follow_id}} <- Actor.unfollow(followed, follower),
# We recreate the follow activity
data <- make_follow_data(followed, follower, follow_id),
{:ok, follow_activity} <- insert(data, local),
{:ok, follow_activity, _object} <- insert(data, local),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local),
{:ok, activity, _object} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@@ -290,7 +290,7 @@ defmodule Mobilizon.Service.ActivityPub do
}
with Events.delete_event(event),
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
@@ -305,7 +305,7 @@ defmodule Mobilizon.Service.ActivityPub do
}
with Events.delete_comment(comment),
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
@@ -320,12 +320,33 @@ defmodule Mobilizon.Service.ActivityPub do
}
with Actors.delete_actor(actor),
{:ok, activity} <- insert(data, local),
{:ok, activity, _object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
end
def flag(params) do
# only accept false as false value
local = !(params[:local] == false)
forward = !(params[:forward] == false)
additional = params[:additional] || %{}
additional =
if forward do
Map.merge(additional, %{"to" => [], "cc" => [params.reported_actor_url]})
else
Map.merge(additional, %{"to" => [], "cc" => []})
end
with flag_data <- make_flag_data(params, additional),
{:ok, activity, report} <- insert(flag_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, report}
end
end
@doc """
Create an actor locally by it's URL (AP ID)
"""

View File

@@ -0,0 +1,85 @@
defmodule Mobilizon.Service.ActivityPub.Converters.Flag do
@moduledoc """
Flag converter
This module allows to convert reports from ActivityStream format to our own internal one, and back.
Note: Reports are named Flag in AS.
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.Event
alias Mobilizon.Reports.Report
alias Mobilizon.Service.ActivityPub.Converter
@behaviour Converter
@doc """
Converts an AP object data to our internal data structure
"""
@impl Converter
@spec as_to_model_data(map()) :: map()
def as_to_model_data(object) do
with params <- as_to_model(object) do
%{
"reporter_id" => params["reporter"].id,
"uri" => params["uri"],
"content" => params["content"],
"reported_id" => params["reported"].id,
"event_id" => (!is_nil(params["event"]) && params["event"].id) || nil,
"comments" => params["comments"]
}
end
end
def as_to_model(%{"object" => objects} = object) do
with {:ok, %Actor{} = reporter} <- Actors.get_actor_by_url(object["actor"]),
%Actor{} = reported <-
Enum.reduce_while(objects, nil, fn url, _ ->
with {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(url) do
{:halt, actor}
else
_ -> {:cont, nil}
end
end),
event <-
Enum.reduce_while(objects, nil, fn url, _ ->
with %Event{} = event <- Events.get_event_by_url(url) do
{:halt, event}
else
_ -> {:cont, nil}
end
end),
# Remove the reported user from the object list.
comments <-
Enum.filter(objects, fn url ->
!(url == reported.url || (!is_nil(event) && event.url == url))
end),
comments <- Enum.map(comments, &Events.get_comment_from_url/1) do
%{
"reporter" => reporter,
"uri" => object["id"],
"content" => object["content"],
"reported" => reported,
"event" => event,
"comments" => comments
}
end
end
@doc """
Convert an event struct to an ActivityStream representation
"""
@impl Converter
@spec model_to_as(EventModel.t()) :: map()
def model_to_as(%Report{} = report) do
%{
"type" => "Flag",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => report.reporter.url,
"id" => report.url
}
end
end

View File

@@ -116,6 +116,22 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|> Map.put("tag", combined)
end
def handle_incoming(%{"type" => "Flag"} = data) do
with params <- Mobilizon.Service.ActivityPub.Converters.Flag.as_to_model(data) do
params = %{
reporter_url: params["reporter"].url,
reported_actor_url: params["reported"].url,
comments_url: params["comments"] |> Enum.map(& &1.url),
content: params["content"] || "",
additional: %{
"cc" => [params["reported"].url]
}
}
ActivityPub.flag(params)
end
end
# TODO: validate those with a Ecto scheme
# - tags
# - emoji

View File

@@ -18,6 +18,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
alias Mobilizon.Media.Picture
alias Mobilizon.Events
alias Mobilizon.Activity
alias Mobilizon.Reports
alias Mobilizon.Reports.Report
alias Mobilizon.Users
alias Mobilizon.Service.ActivityPub.Converters
alias Ecto.Changeset
require Logger
alias MobilizonWeb.Router.Helpers, as: Routes
@@ -119,9 +123,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
def insert_full_object(%{"object" => %{"type" => "Event"} = object_data})
when is_map(object_data) do
with object_data <-
Mobilizon.Service.ActivityPub.Converters.Event.as_to_model_data(object_data),
{:ok, _} <- Events.create_event(object_data) do
:ok
Converters.Event.as_to_model_data(object_data),
{:ok, %Event{} = event} <- Events.create_event(object_data) do
{:ok, event}
end
end
@@ -129,8 +133,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
when is_map(object_data) do
with object_data <-
Map.put(object_data, "preferred_username", object_data["preferredUsername"]),
{:ok, _} <- Actors.create_group(object_data) do
:ok
{:ok, %Actor{} = group} <- Actors.create_group(object_data) do
{:ok, group}
end
end
@@ -139,9 +143,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"""
def insert_full_object(%{"object" => %{"type" => "Note"} = object_data})
when is_map(object_data) do
with data <- Mobilizon.Service.ActivityPub.Converters.Comment.as_to_model_data(object_data),
{:ok, _comment} <- Events.create_comment(data) do
:ok
with data <- Converters.Comment.as_to_model_data(object_data),
{:ok, %Comment{} = comment} <- Events.create_comment(data) do
{:ok, comment}
else
err ->
Logger.error("Error while inserting a remote comment inside database")
@@ -150,7 +154,29 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
end
end
def insert_full_object(_), do: :ok
@doc """
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"type" => "Flag"} = object_data)
when is_map(object_data) do
with data <- Converters.Flag.as_to_model_data(object_data),
{:ok, %Report{} = report} <- Reports.create_report(data) do
Enum.each(Users.list_moderators(), fn moderator ->
moderator
|> Mobilizon.Email.Admin.report(moderator, report)
|> Mobilizon.Mailer.deliver_later()
end)
{:ok, report}
else
err ->
Logger.error("Error while inserting a remote comment inside database")
Logger.error(inspect(err))
{:error, err}
end
end
def insert_full_object(_), do: {:ok, nil}
#### Like-related helpers
@@ -497,6 +523,24 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|> Map.merge(additional)
end
#### Flag-related helpers
@spec make_flag_data(map(), map()) :: map()
def make_flag_data(params, additional) do
object = [params.reported_actor_url] ++ params.comments_url
object = if params[:event_url], do: object ++ [params.event_url], else: object
%{
"type" => "Flag",
"id" => "#{MobilizonWeb.Endpoint.url()}/report/#{Ecto.UUID.generate()}",
"actor" => params.reporter_url,
"content" => params.content,
"object" => object,
"state" => "open"
}
|> Map.merge(additional)
end
@doc """
Converts PEM encoded keys to a public key representation
"""

View File

@@ -0,0 +1,30 @@
defmodule Mobilizon.Service.Admin.ActionLogService do
@moduledoc """
Module to handle action log creations
"""
alias Mobilizon.Users
alias Mobilizon.Users.User
alias Mobilizon.Actors.Actor
alias Mobilizon.Admin
alias Mobilizon.Admin.ActionLog
@doc """
Log an admin action
"""
@spec log_action(Actor.t(), String.t(), String.t()) :: {:ok, ActionLog.t()}
def log_action(%Actor{user_id: user_id, id: actor_id}, action, target) do
with %User{role: role} <- Users.get_user!(user_id),
{:role, true} <- {:role, role in [:administrator, :moderator]},
{:ok, %ActionLog{} = create_action_log} <-
Admin.create_action_log(%{
"actor_id" => actor_id,
"target_type" => to_string(target.__struct__),
"target_id" => target.id,
"action" => action,
"changes" => Map.from_struct(target) |> Map.take([:status, :uri, :content])
}) do
{:ok, create_action_log}
end
end
end