Merge branch 'feature/reports' into 'develop'
Reports Closes #83 See merge request pleroma/pleroma!701
This commit is contained in:
commit
54f9e3d382
|
@ -164,7 +164,8 @@
|
|||
max_pinned_statuses: 1,
|
||||
no_attachment_links: false,
|
||||
welcome_user_nickname: nil,
|
||||
welcome_message: nil
|
||||
welcome_message: nil,
|
||||
max_report_comment_size: 1000
|
||||
|
||||
config :pleroma, :markup,
|
||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||
|
@ -340,7 +341,8 @@
|
|||
|
||||
config :pleroma, Pleroma.Jobs,
|
||||
federator_incoming: [max_jobs: 50],
|
||||
federator_outgoing: [max_jobs: 50]
|
||||
federator_outgoing: [max_jobs: 50],
|
||||
mailer: [max_jobs: 10]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
|
|
@ -100,6 +100,7 @@ config :pleroma, Pleroma.Mailer,
|
|||
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
|
||||
* `welcome_message`: A message that will be send to a newly registered users as a direct message.
|
||||
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
|
||||
* `max_report_size`: The maximum size of the report comment (Default: `1000`)
|
||||
|
||||
## :logger
|
||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
|
||||
|
|
|
@ -113,4 +113,14 @@ def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
|
|||
end
|
||||
|
||||
def mastodon_notification_type(%Activity{}), do: nil
|
||||
|
||||
def all_by_actor_and_id(actor, status_ids \\ [])
|
||||
def all_by_actor_and_id(_actor, []), do: []
|
||||
|
||||
def all_by_actor_and_id(actor, status_ids) do
|
||||
Activity
|
||||
|> where([s], s.id in ^status_ids)
|
||||
|> where([s], s.actor == ^actor)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
||||
|
|
63
lib/pleroma/emails/admin_email.ex
Normal file
63
lib/pleroma/emails/admin_email.ex
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.AdminEmail do
|
||||
@moduledoc "Admin emails"
|
||||
|
||||
import Swoosh.Email
|
||||
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
||||
defp instance_config, do: Pleroma.Config.get(:instance)
|
||||
defp instance_name, do: instance_config()[:name]
|
||||
defp instance_email, do: instance_config()[:email]
|
||||
|
||||
defp user_url(user) do
|
||||
Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname)
|
||||
end
|
||||
|
||||
def report(to, reporter, account, statuses, comment) do
|
||||
comment_html =
|
||||
if comment do
|
||||
"<p>Comment: #{comment}"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
statuses_html =
|
||||
if length(statuses) > 0 do
|
||||
statuses_list_html =
|
||||
statuses
|
||||
|> Enum.map(fn %{id: id} ->
|
||||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
||||
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
||||
end)
|
||||
|> Enum.join("\n")
|
||||
|
||||
"""
|
||||
<p> Statuses:
|
||||
<ul>
|
||||
#{statuses_list_html}
|
||||
</ul>
|
||||
</p>
|
||||
"""
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
html_body = """
|
||||
<p>Reported by: <a href="#{user_url(reporter)}">#{reporter.nickname}</a></p>
|
||||
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
|
||||
#{comment_html}
|
||||
#{statuses_html}
|
||||
"""
|
||||
|
||||
new()
|
||||
|> to({to.name, to.email})
|
||||
|> from({instance_name(), instance_email()})
|
||||
|> reply_to({reporter.name, reporter.email})
|
||||
|> subject("#{instance_name()} Report")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
end
|
|
@ -4,4 +4,10 @@
|
|||
|
||||
defmodule Pleroma.Mailer do
|
||||
use Swoosh.Mailer, otp_app: :pleroma
|
||||
|
||||
def deliver_async(email, config \\ []) do
|
||||
Pleroma.Jobs.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
||||
end
|
||||
|
||||
def perform(:deliver_async, email, config), do: deliver(email, config)
|
||||
end
|
||||
|
|
|
@ -273,7 +273,7 @@ def try_send_confirmation_email(%User{} = user) do
|
|||
Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
user
|
||||
|> Pleroma.UserEmail.account_confirmation_email()
|
||||
|> Pleroma.Mailer.deliver()
|
||||
|> Pleroma.Mailer.deliver_async()
|
||||
else
|
||||
{:ok, :noop}
|
||||
end
|
||||
|
@ -1284,4 +1284,13 @@ def error_user(ap_id) do
|
|||
inserted_at: NaiveDateTime.utc_now()
|
||||
}
|
||||
end
|
||||
|
||||
def all_superusers do
|
||||
from(
|
||||
u in User,
|
||||
where: u.local == true,
|
||||
where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
|
||||
)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -353,6 +353,31 @@ def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
|
|||
end
|
||||
end
|
||||
|
||||
def flag(
|
||||
%{
|
||||
actor: actor,
|
||||
context: context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content
|
||||
} = params
|
||||
) do
|
||||
additional = params[:additional] || %{}
|
||||
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
|
||||
%{
|
||||
actor: actor,
|
||||
context: context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content
|
||||
}
|
||||
|> make_flag_data(additional)
|
||||
|> insert(local)
|
||||
end
|
||||
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
|
|
|
@ -598,4 +598,20 @@ def make_create_data(params, additional) do
|
|||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
||||
#### Flag-related helpers
|
||||
|
||||
def make_flag_data(params, additional) do
|
||||
status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"])
|
||||
object = [params.account.ap_id] ++ status_ap_ids
|
||||
|
||||
%{
|
||||
"type" => "Flag",
|
||||
"actor" => params.actor.ap_id,
|
||||
"content" => params.content,
|
||||
"object" => object,
|
||||
"context" => params.context
|
||||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -243,4 +243,31 @@ def thread_muted?(user, activity) do
|
|||
_ -> true
|
||||
end
|
||||
end
|
||||
|
||||
def report(user, data) do
|
||||
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
||||
{:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
|
||||
{:ok, content_html} <- make_report_content_html(data["comment"]),
|
||||
{:ok, statuses} <- get_report_statuses(account, data),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.flag(%{
|
||||
context: Utils.generate_context_id(),
|
||||
actor: user,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content_html
|
||||
}) do
|
||||
Enum.each(User.all_superusers(), fn superuser ->
|
||||
superuser
|
||||
|> Pleroma.AdminEmail.report(user, account, statuses, content_html)
|
||||
|> Pleroma.Mailer.deliver_async()
|
||||
end)
|
||||
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, err} -> {:error, err}
|
||||
{:account_id, %{}} -> {:error, "Valid `account_id` required"}
|
||||
{:account, nil} -> {:error, "Account not found"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -322,4 +322,22 @@ def maybe_extract_mentions(%{"tag" => tag}) do
|
|||
end
|
||||
|
||||
def maybe_extract_mentions(_), do: []
|
||||
|
||||
def make_report_content_html(nil), do: {:ok, nil}
|
||||
|
||||
def make_report_content_html(comment) do
|
||||
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
|
||||
|
||||
if String.length(comment) <= max_size do
|
||||
{:ok, format_input(comment, [], [], "text/plain")}
|
||||
else
|
||||
{:error, "Comment must be up to #{max_size} characters"}
|
||||
end
|
||||
end
|
||||
|
||||
def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do
|
||||
{:ok, Activity.all_by_actor_and_id(actor, status_ids)}
|
||||
end
|
||||
|
||||
def get_report_statuses(_, _), do: {:ok, nil}
|
||||
end
|
||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MastodonAPI.ReportView
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
@ -1533,6 +1534,20 @@ def status_card(conn, %{"id" => status_id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def reports(%{assigns: %{user: user}} = conn, params) do
|
||||
case CommonAPI.report(user, params) do
|
||||
{:ok, activity} ->
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> try_render("report.json", %{activity: activity})
|
||||
|
||||
{:error, err} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: err})
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, target, params)
|
||||
when is_binary(target) do
|
||||
res = render(conn, target, params)
|
||||
|
|
14
lib/pleroma/web/mastodon_api/views/report_view.ex
Normal file
14
lib/pleroma/web/mastodon_api/views/report_view.ex
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.ReportView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("report.json", %{activity: activity}) do
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
action_taken: false
|
||||
}
|
||||
end
|
||||
end
|
|
@ -275,6 +275,8 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/filters/:id", MastodonAPIController, :delete_filter)
|
||||
|
||||
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
|
||||
|
||||
post("/reports", MastodonAPIController, :reports)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
|
|
|
@ -216,7 +216,7 @@ def password_reset(nickname_or_email) do
|
|||
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||
user
|
||||
|> UserEmail.password_reset_email(token_record.token)
|
||||
|> Mailer.deliver()
|
||||
|> Mailer.deliver_async()
|
||||
else
|
||||
false ->
|
||||
{:error, "bad user identifier"}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Pleroma.Repo.Migrations.UsersAddIsAdminIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create(index(:users, ["(info->'is_admin')"], name: :users_is_admin_index, using: :gin))
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||
|
@ -742,6 +742,37 @@ test "returned pinned statuses" do
|
|||
assert 3 = length(activities)
|
||||
end
|
||||
|
||||
test "it can create a Flag activity" do
|
||||
reporter = insert(:user)
|
||||
target_account = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
|
||||
context = Utils.generate_context_id()
|
||||
content = "foobar"
|
||||
|
||||
reporter_ap_id = reporter.ap_id
|
||||
target_ap_id = target_account.ap_id
|
||||
activity_ap_id = activity.data["id"]
|
||||
|
||||
assert {:ok, activity} =
|
||||
ActivityPub.flag(%{
|
||||
actor: reporter,
|
||||
context: context,
|
||||
account: target_account,
|
||||
statuses: [activity],
|
||||
content: content
|
||||
})
|
||||
|
||||
assert %Activity{
|
||||
actor: ^reporter_ap_id,
|
||||
data: %{
|
||||
"type" => "Flag",
|
||||
"content" => ^content,
|
||||
"context" => ^context,
|
||||
"object" => [^target_ap_id, ^activity_ap_id]
|
||||
}
|
||||
} = activity
|
||||
end
|
||||
|
||||
describe "publish_one/1" do
|
||||
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
|
||||
Instances,
|
||||
|
|
|
@ -190,4 +190,35 @@ test "check that mutes can't be duplicate", %{user: user, activity: activity} do
|
|||
{:error, _} = CommonAPI.add_mute(user, activity)
|
||||
end
|
||||
end
|
||||
|
||||
describe "reports" do
|
||||
test "creates a report" do
|
||||
reporter = insert(:user)
|
||||
target_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
|
||||
|
||||
reporter_ap_id = reporter.ap_id
|
||||
target_ap_id = target_user.ap_id
|
||||
activity_ap_id = activity.data["id"]
|
||||
comment = "foobar"
|
||||
|
||||
report_data = %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => comment,
|
||||
"status_ids" => [activity.id]
|
||||
}
|
||||
|
||||
assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
|
||||
|
||||
assert %Activity{
|
||||
actor: ^reporter_ap_id,
|
||||
data: %{
|
||||
"type" => "Flag",
|
||||
"content" => ^comment,
|
||||
"object" => [^target_ap_id, ^activity_ap_id]
|
||||
}
|
||||
} = flag_activity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1855,4 +1855,69 @@ test "flavours switching (Pleroma Extension)", %{conn: conn} do
|
|||
|
||||
assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
|
||||
end
|
||||
|
||||
describe "reports" do
|
||||
setup do
|
||||
reporter = insert(:user)
|
||||
target_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
|
||||
|
||||
[reporter: reporter, target_user: target_user, activity: activity]
|
||||
end
|
||||
|
||||
test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
|
||||
assert %{"action_taken" => false, "id" => _} =
|
||||
conn
|
||||
|> assign(:user, reporter)
|
||||
|> post("/api/v1/reports", %{"account_id" => target_user.id})
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
test "submit a report with statuses and comment", %{
|
||||
conn: conn,
|
||||
reporter: reporter,
|
||||
target_user: target_user,
|
||||
activity: activity
|
||||
} do
|
||||
assert %{"action_taken" => false, "id" => _} =
|
||||
conn
|
||||
|> assign(:user, reporter)
|
||||
|> post("/api/v1/reports", %{
|
||||
"account_id" => target_user.id,
|
||||
"status_ids" => [activity.id],
|
||||
"comment" => "bad status!"
|
||||
})
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
test "accound_id is required", %{
|
||||
conn: conn,
|
||||
reporter: reporter,
|
||||
activity: activity
|
||||
} do
|
||||
assert %{"error" => "Valid `account_id` required"} =
|
||||
conn
|
||||
|> assign(:user, reporter)
|
||||
|> post("/api/v1/reports", %{"status_ids" => [activity.id]})
|
||||
|> json_response(400)
|
||||
end
|
||||
|
||||
test "comment must be up to the size specified in the config", %{
|
||||
conn: conn,
|
||||
reporter: reporter,
|
||||
target_user: target_user
|
||||
} do
|
||||
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
|
||||
comment = String.pad_trailing("a", max_size + 1, "a")
|
||||
|
||||
error = %{"error" => "Comment must be up to #{max_size} characters"}
|
||||
|
||||
assert ^error =
|
||||
conn
|
||||
|> assign(:user, reporter)
|
||||
|> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
|
||||
|> json_response(400)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue