@@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- Fix follower/blocks import when nicknames starts with @ | |||
- Filtering of push notifications on activities from blocked domains | |||
- Resolving Peertube accounts with Webfinger | |||
- `blob:` urls not being allowed by connect-src CSP | |||
## [Unreleased (patch)] | |||
@@ -22,8 +22,21 @@ defmodule Pleroma.LoadTesting.Activities do | |||
@max_concurrency 10 | |||
@visibility ~w(public private direct unlisted) | |||
@types ~w(simple emoji mentions hell_thread attachment tag like reblog simple_thread remote) | |||
@groups ~w(user friends non_friends) | |||
@types [ | |||
:simple, | |||
:emoji, | |||
:mentions, | |||
:hell_thread, | |||
:attachment, | |||
:tag, | |||
:like, | |||
:reblog, | |||
:simple_thread | |||
] | |||
@groups [:friends_local, :friends_remote, :non_friends_local, :non_friends_local] | |||
@remote_groups [:friends_remote, :non_friends_remote] | |||
@friends_groups [:friends_local, :friends_remote] | |||
@non_friends_groups [:non_friends_local, :non_friends_remote] | |||
@spec generate(User.t(), keyword()) :: :ok | |||
def generate(user, opts \\ []) do | |||
@@ -34,33 +47,24 @@ defmodule Pleroma.LoadTesting.Activities do | |||
opts = Keyword.merge(@defaults, opts) | |||
friends = | |||
user | |||
|> Users.get_users(limit: opts[:friends_used], local: :local, friends?: true) | |||
|> Enum.shuffle() | |||
users = Users.prepare_users(user, opts) | |||
non_friends = | |||
user | |||
|> Users.get_users(limit: opts[:non_friends_used], local: :local, friends?: false) | |||
|> Enum.shuffle() | |||
{:ok, _} = Agent.start_link(fn -> users[:non_friends_remote] end, name: :non_friends_remote) | |||
task_data = | |||
for visibility <- @visibility, | |||
type <- @types, | |||
group <- @groups, | |||
group <- [:user | @groups], | |||
do: {visibility, type, group} | |||
IO.puts("Starting generating #{opts[:iterations]} iterations of activities...") | |||
friends_thread = Enum.take(friends, 5) | |||
non_friends_thread = Enum.take(friends, 5) | |||
public_long_thread = fn -> | |||
generate_long_thread("public", user, friends_thread, non_friends_thread, opts) | |||
generate_long_thread("public", users, opts) | |||
end | |||
private_long_thread = fn -> | |||
generate_long_thread("private", user, friends_thread, non_friends_thread, opts) | |||
generate_long_thread("private", users, opts) | |||
end | |||
iterations = opts[:iterations] | |||
@@ -73,10 +77,10 @@ defmodule Pleroma.LoadTesting.Activities do | |||
i when i == iterations - 2 -> | |||
spawn(public_long_thread) | |||
spawn(private_long_thread) | |||
generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts) | |||
generate_activities(users, Enum.shuffle(task_data), opts) | |||
_ -> | |||
generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts) | |||
generate_activities(users, Enum.shuffle(task_data), opts) | |||
end | |||
) | |||
end) | |||
@@ -127,16 +131,16 @@ defmodule Pleroma.LoadTesting.Activities do | |||
end) | |||
end | |||
defp generate_long_thread(visibility, user, friends, non_friends, _opts) do | |||
defp generate_long_thread(visibility, users, _opts) do | |||
group = | |||
if visibility == "public", | |||
do: "friends", | |||
else: "user" | |||
do: :friends_local, | |||
else: :user | |||
tasks = get_reply_tasks(visibility, group) |> Stream.cycle() |> Enum.take(50) | |||
{:ok, activity} = | |||
CommonAPI.post(user, %{ | |||
CommonAPI.post(users[:user], %{ | |||
status: "Start of #{visibility} long thread", | |||
visibility: visibility | |||
}) | |||
@@ -150,31 +154,28 @@ defmodule Pleroma.LoadTesting.Activities do | |||
Map.put(state, key, activity) | |||
end) | |||
acc = {activity.id, ["@" <> user.nickname, "reply to long thread"]} | |||
insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc) | |||
acc = {activity.id, ["@" <> users[:user].nickname, "reply to long thread"]} | |||
insert_replies_for_long_thread(tasks, visibility, users, acc) | |||
IO.puts("Generating #{visibility} long thread ended\n") | |||
end | |||
defp insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc) do | |||
defp insert_replies_for_long_thread(tasks, visibility, users, acc) do | |||
Enum.reduce(tasks, acc, fn | |||
"friend", {id, data} -> | |||
friend = Enum.random(friends) | |||
insert_reply(friend, List.delete(data, "@" <> friend.nickname), id, visibility) | |||
"non_friend", {id, data} -> | |||
non_friend = Enum.random(non_friends) | |||
insert_reply(non_friend, List.delete(data, "@" <> non_friend.nickname), id, visibility) | |||
"user", {id, data} -> | |||
:user, {id, data} -> | |||
user = users[:user] | |||
insert_reply(user, List.delete(data, "@" <> user.nickname), id, visibility) | |||
group, {id, data} -> | |||
replier = Enum.random(users[group]) | |||
insert_reply(replier, List.delete(data, "@" <> replier.nickname), id, visibility) | |||
end) | |||
end | |||
defp generate_activities(user, friends, non_friends, task_data, opts) do | |||
defp generate_activities(users, task_data, opts) do | |||
Task.async_stream( | |||
task_data, | |||
fn {visibility, type, group} -> | |||
insert_activity(type, visibility, group, user, friends, non_friends, opts) | |||
insert_activity(type, visibility, group, users, opts) | |||
end, | |||
max_concurrency: @max_concurrency, | |||
timeout: 30_000 | |||
@@ -182,67 +183,104 @@ defmodule Pleroma.LoadTesting.Activities do | |||
|> Stream.run() | |||
end | |||
defp insert_activity("simple", visibility, group, user, friends, non_friends, _opts) do | |||
{:ok, _activity} = | |||
defp insert_local_activity(visibility, group, users, status) do | |||
{:ok, _} = | |||
group | |||
|> get_actor(user, friends, non_friends) | |||
|> CommonAPI.post(%{status: "Simple status", visibility: visibility}) | |||
|> get_actor(users) | |||
|> CommonAPI.post(%{status: status, visibility: visibility}) | |||
end | |||
defp insert_activity("emoji", visibility, group, user, friends, non_friends, _opts) do | |||
{:ok, _activity} = | |||
group | |||
|> get_actor(user, friends, non_friends) | |||
|> CommonAPI.post(%{ | |||
status: "Simple status with emoji :firefox:", | |||
visibility: visibility | |||
}) | |||
defp insert_remote_activity(visibility, group, users, status) do | |||
actor = get_actor(group, users) | |||
{act_data, obj_data} = prepare_activity_data(actor, visibility, users[:user]) | |||
{activity_data, object_data} = other_data(actor, status) | |||
activity_data | |||
|> Map.merge(act_data) | |||
|> Map.put("object", Map.merge(object_data, obj_data)) | |||
|> Pleroma.Web.ActivityPub.ActivityPub.insert(false) | |||
end | |||
defp insert_activity("mentions", visibility, group, user, friends, non_friends, _opts) do | |||
defp user_mentions(users) do | |||
user_mentions = | |||
get_random_mentions(friends, Enum.random(0..3)) ++ | |||
get_random_mentions(non_friends, Enum.random(0..3)) | |||
Enum.reduce( | |||
@groups, | |||
[], | |||
fn group, acc -> | |||
acc ++ get_random_mentions(users[group], Enum.random(0..2)) | |||
end | |||
) | |||
user_mentions = | |||
if Enum.random([true, false]), | |||
do: ["@" <> user.nickname | user_mentions], | |||
else: user_mentions | |||
if Enum.random([true, false]), | |||
do: ["@" <> users[:user].nickname | user_mentions], | |||
else: user_mentions | |||
end | |||
{:ok, _activity} = | |||
group | |||
|> get_actor(user, friends, non_friends) | |||
|> CommonAPI.post(%{ | |||
status: Enum.join(user_mentions, ", ") <> " simple status with mentions", | |||
visibility: visibility | |||
}) | |||
defp hell_thread_mentions(users) do | |||
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do | |||
cached = | |||
@groups | |||
|> Enum.reduce([users[:user]], fn group, acc -> | |||
acc ++ Enum.take(users[group], 5) | |||
end) | |||
|> Enum.map(&"@#{&1.nickname}") | |||
|> Enum.join(", ") | |||
Cachex.put(:user_cache, "hell_thread_mentions", cached) | |||
cached | |||
else | |||
{:ok, cached} -> cached | |||
end | |||
end | |||
defp insert_activity("hell_thread", visibility, group, user, friends, non_friends, _opts) do | |||
mentions = | |||
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do | |||
cached = | |||
([user | Enum.take(friends, 10)] ++ Enum.take(non_friends, 10)) | |||
|> Enum.map(&"@#{&1.nickname}") | |||
|> Enum.join(", ") | |||
defp insert_activity(:simple, visibility, group, users, _opts) | |||
when group in @remote_groups do | |||
insert_remote_activity(visibility, group, users, "Remote status") | |||
end | |||
Cachex.put(:user_cache, "hell_thread_mentions", cached) | |||
cached | |||
else | |||
{:ok, cached} -> cached | |||
end | |||
defp insert_activity(:simple, visibility, group, users, _opts) do | |||
insert_local_activity(visibility, group, users, "Simple status") | |||
end | |||
{:ok, _activity} = | |||
group | |||
|> get_actor(user, friends, non_friends) | |||
|> CommonAPI.post(%{ | |||
status: mentions <> " hell thread status", | |||
visibility: visibility | |||
}) | |||
defp insert_activity(:emoji, visibility, group, users, _opts) | |||
when group in @remote_groups do | |||
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:") | |||
end | |||
defp insert_activity(:emoji, visibility, group, users, _opts) do | |||
insert_local_activity(visibility, group, users, "Simple status with emoji :firefox:") | |||
end | |||
defp insert_activity(:mentions, visibility, group, users, _opts) | |||
when group in @remote_groups do | |||
mentions = user_mentions(users) | |||
status = Enum.join(mentions, ", ") <> " remote status with mentions" | |||
insert_remote_activity(visibility, group, users, status) | |||
end | |||
defp insert_activity(:mentions, visibility, group, users, _opts) do | |||
mentions = user_mentions(users) | |||
status = Enum.join(mentions, ", ") <> " simple status with mentions" | |||
insert_remote_activity(visibility, group, users, status) | |||
end | |||
defp insert_activity(:hell_thread, visibility, group, users, _) | |||
when group in @remote_groups do | |||
mentions = hell_thread_mentions(users) | |||
insert_remote_activity(visibility, group, users, mentions <> " remote hell thread status") | |||
end | |||
defp insert_activity(:hell_thread, visibility, group, users, _opts) do | |||
mentions = hell_thread_mentions(users) | |||
insert_local_activity(visibility, group, users, mentions <> " hell thread status") | |||
end | |||
defp insert_activity("attachment", visibility, group, user, friends, non_friends, _opts) do | |||
actor = get_actor(group, user, friends, non_friends) | |||
defp insert_activity(:attachment, visibility, group, users, _opts) do | |||
actor = get_actor(group, users) | |||
obj_data = %{ | |||
"actor" => actor.ap_id, | |||
@@ -268,67 +306,54 @@ defmodule Pleroma.LoadTesting.Activities do | |||
}) | |||
end | |||
defp insert_activity("tag", visibility, group, user, friends, non_friends, _opts) do | |||
{:ok, _activity} = | |||
group | |||
|> get_actor(user, friends, non_friends) | |||
|> CommonAPI.post(%{status: "Status with #tag", visibility: visibility}) | |||
defp insert_activity(:tag, visibility, group, users, _opts) do | |||
insert_local_activity(visibility, group, users, "Status with #tag") | |||
end | |||
defp insert_activity("like", visibility, group, user, friends, non_friends, opts) do | |||
actor = get_actor(group, user, friends, non_friends) | |||
defp insert_activity(:like, visibility, group, users, opts) do | |||
actor = get_actor(group, users) | |||
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(), | |||
{:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do | |||
:ok | |||
else | |||
{:error, _} -> | |||
insert_activity("like", visibility, group, user, friends, non_friends, opts) | |||
insert_activity(:like, visibility, group, users, opts) | |||
nil -> | |||
Process.sleep(15) | |||
insert_activity("like", visibility, group, user, friends, non_friends, opts) | |||
insert_activity(:like, visibility, group, users, opts) | |||
end | |||
end | |||
defp insert_activity("reblog", visibility, group, user, friends, non_friends, opts) do | |||
actor = get_actor(group, user, friends, non_friends) | |||
defp insert_activity(:reblog, visibility, group, users, opts) do | |||
actor = get_actor(group, users) | |||
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(), | |||
{:ok, _activity, _object} <- CommonAPI.repeat(activity_id, actor) do | |||
{:ok, _activity} <- CommonAPI.repeat(activity_id, actor) do | |||
:ok | |||
else | |||
{:error, _} -> | |||
insert_activity("reblog", visibility, group, user, friends, non_friends, opts) | |||
insert_activity(:reblog, visibility, group, users, opts) | |||
nil -> | |||
Process.sleep(15) | |||
insert_activity("reblog", visibility, group, user, friends, non_friends, opts) | |||
insert_activity(:reblog, visibility, group, users, opts) | |||
end | |||
end | |||
defp insert_activity("simple_thread", visibility, group, user, friends, non_friends, _opts) | |||
when visibility in ["public", "unlisted", "private"] do | |||
actor = get_actor(group, user, friends, non_friends) | |||
tasks = get_reply_tasks(visibility, group) | |||
{:ok, activity} = CommonAPI.post(user, %{status: "Simple status", visibility: visibility}) | |||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]} | |||
insert_replies(tasks, visibility, user, friends, non_friends, acc) | |||
end | |||
defp insert_activity("simple_thread", "direct", group, user, friends, non_friends, _opts) do | |||
actor = get_actor(group, user, friends, non_friends) | |||
defp insert_activity(:simple_thread, "direct", group, users, _opts) do | |||
actor = get_actor(group, users) | |||
tasks = get_reply_tasks("direct", group) | |||
list = | |||
case group do | |||
"non_friends" -> | |||
Enum.take(non_friends, 3) | |||
:user -> | |||
group = Enum.random(@friends_groups) | |||
Enum.take(users[group], 3) | |||
_ -> | |||
Enum.take(friends, 3) | |||
Enum.take(users[group], 3) | |||
end | |||
data = Enum.map(list, &("@" <> &1.nickname)) | |||
@@ -339,40 +364,30 @@ defmodule Pleroma.LoadTesting.Activities do | |||
visibility: "direct" | |||
}) | |||
acc = {activity.id, ["@" <> user.nickname | data] ++ ["reply to status"]} | |||
insert_direct_replies(tasks, user, list, acc) | |||
acc = {activity.id, ["@" <> users[:user].nickname | data] ++ ["reply to status"]} | |||
insert_direct_replies(tasks, users[:user], list, acc) | |||
end | |||
defp insert_activity("remote", _, "user", _, _, _, _), do: :ok | |||
defp insert_activity("remote", visibility, group, user, _friends, _non_friends, opts) do | |||
remote_friends = | |||
Users.get_users(user, limit: opts[:friends_used], local: :external, friends?: true) | |||
remote_non_friends = | |||
Users.get_users(user, limit: opts[:non_friends_used], local: :external, friends?: false) | |||
actor = get_actor(group, user, remote_friends, remote_non_friends) | |||
defp insert_activity(:simple_thread, visibility, group, users, _opts) do | |||
actor = get_actor(group, users) | |||
tasks = get_reply_tasks(visibility, group) | |||
{act_data, obj_data} = prepare_activity_data(actor, visibility, user) | |||
{activity_data, object_data} = other_data(actor) | |||
{:ok, activity} = | |||
CommonAPI.post(users[:user], %{status: "Simple status", visibility: visibility}) | |||
activity_data | |||
|> Map.merge(act_data) | |||
|> Map.put("object", Map.merge(object_data, obj_data)) | |||
|> Pleroma.Web.ActivityPub.ActivityPub.insert(false) | |||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]} | |||
insert_replies(tasks, visibility, users, acc) | |||
end | |||
defp get_actor("user", user, _friends, _non_friends), do: user | |||
defp get_actor("friends", _user, friends, _non_friends), do: Enum.random(friends) | |||
defp get_actor("non_friends", _user, _friends, non_friends), do: Enum.random(non_friends) | |||
defp get_actor(:user, %{user: user}), do: user | |||
defp get_actor(group, users), do: Enum.random(users[group]) | |||
defp other_data(actor) do | |||
defp other_data(actor, content) do | |||
%{host: host} = URI.parse(actor.ap_id) | |||
datetime = DateTime.utc_now() | |||
context_id = "http://#{host}:4000/contexts/#{UUID.generate()}" | |||
activity_id = "http://#{host}:4000/activities/#{UUID.generate()}" | |||
object_id = "http://#{host}:4000/objects/#{UUID.generate()}" | |||
context_id = "https://#{host}/contexts/#{UUID.generate()}" | |||
activity_id = "https://#{host}/activities/#{UUID.generate()}" | |||
object_id = "https://#{host}/objects/#{UUID.generate()}" | |||
activity_data = %{ | |||
"actor" => actor.ap_id, | |||
@@ -389,7 +404,7 @@ defmodule Pleroma.LoadTesting.Activities do | |||
"attributedTo" => actor.ap_id, | |||
"bcc" => [], | |||
"bto" => [], | |||
"content" => "Remote post", | |||
"content" => content, | |||
"context" => context_id, | |||
"conversation" => context_id, | |||
"emoji" => %{}, | |||
@@ -475,51 +490,65 @@ defmodule Pleroma.LoadTesting.Activities do | |||
{act_data, obj_data} | |||
end | |||
defp get_reply_tasks("public", "user"), do: ~w(friend non_friend user) | |||
defp get_reply_tasks("public", "friends"), do: ~w(non_friend user friend) | |||
defp get_reply_tasks("public", "non_friends"), do: ~w(user friend non_friend) | |||
defp get_reply_tasks("public", :user) do | |||
[:friends_local, :friends_remote, :non_friends_local, :non_friends_remote, :user] | |||
end | |||
defp get_reply_tasks("public", group) when group in @friends_groups do | |||
[:non_friends_local, :non_friends_remote, :user, :friends_local, :friends_remote] | |||
end | |||
defp get_reply_tasks(visibility, "user") when visibility in ["unlisted", "private"], | |||
do: ~w(friend user friend) | |||
defp get_reply_tasks("public", group) when group in @non_friends_groups do | |||
[:user, :friends_local, :friends_remote, :non_friends_local, :non_friends_remote] | |||
end | |||
defp get_reply_tasks(visibility, "friends") when visibility in ["unlisted", "private"], | |||
do: ~w(user friend user) | |||
defp get_reply_tasks(visibility, :user) when visibility in ["unlisted", "private"] do | |||
[:friends_local, :friends_remote, :user, :friends_local, :friends_remote] | |||
end | |||
defp get_reply_tasks(visibility, "non_friends") when visibility in ["unlisted", "private"], | |||
do: [] | |||
defp get_reply_tasks(visibility, group) | |||
when visibility in ["unlisted", "private"] and group in @friends_groups do | |||
[:user, :friends_remote, :friends_local, :user] | |||
end | |||
defp get_reply_tasks("direct", "user"), do: ~w(friend user friend) | |||
defp get_reply_tasks("direct", "friends"), do: ~w(user friend user) | |||
defp get_reply_tasks("direct", "non_friends"), do: ~w(user non_friend user) | |||
defp get_reply_tasks(visibility, group) | |||
when visibility in ["unlisted", "private"] and | |||
group in @non_friends_groups, | |||
do: [] | |||
defp insert_replies(tasks, visibility, user, friends, non_friends, acc) do | |||
Enum.reduce(tasks, acc, fn | |||
"friend", {id, data} -> | |||
friend = Enum.random(friends) | |||
insert_reply(friend, data, id, visibility) | |||
defp get_reply_tasks("direct", :user), do: [:friends_local, :user, :friends_remote] | |||
"non_friend", {id, data} -> | |||
non_friend = Enum.random(non_friends) | |||
insert_reply(non_friend, data, id, visibility) | |||
defp get_reply_tasks("direct", group) when group in @friends_groups, | |||
do: [:user, group, :user] | |||
"user", {id, data} -> | |||
insert_reply(user, data, id, visibility) | |||
defp get_reply_tasks("direct", group) when group in @non_friends_groups do | |||
[:user, :non_friends_remote, :user, :non_friends_local] | |||
end | |||
defp insert_replies(tasks, visibility, users, acc) do | |||
Enum.reduce(tasks, acc, fn | |||
:user, {id, data} -> | |||
insert_reply(users[:user], data, id, visibility) | |||
group, {id, data} -> | |||
replier = Enum.random(users[group]) | |||
insert_reply(replier, data, id, visibility) | |||
end) | |||
end | |||
defp insert_direct_replies(tasks, user, list, acc) do | |||
Enum.reduce(tasks, acc, fn | |||
group, {id, data} when group in ["friend", "non_friend"] -> | |||
:user, {id, data} -> | |||
{reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct") | |||
{reply_id, data} | |||
_, {id, data} -> | |||
actor = Enum.random(list) | |||
{reply_id, _} = | |||
insert_reply(actor, List.delete(data, "@" <> actor.nickname), id, "direct") | |||
{reply_id, data} | |||
"user", {id, data} -> | |||
{reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct") | |||
{reply_id, data} | |||
end) | |||
end | |||
@@ -36,6 +36,7 @@ defmodule Pleroma.LoadTesting.Fetcher do | |||
fetch_home_timeline(user) | |||
fetch_direct_timeline(user) | |||
fetch_public_timeline(user) | |||
fetch_public_timeline(user, :with_blocks) | |||
fetch_public_timeline(user, :local) | |||
fetch_public_timeline(user, :tag) | |||
fetch_notifications(user) | |||
@@ -227,6 +228,58 @@ defmodule Pleroma.LoadTesting.Fetcher do | |||
fetch_public_timeline(opts, "public timeline only media") | |||
end | |||
defp fetch_public_timeline(user, :with_blocks) do | |||
opts = opts_for_public_timeline(user) | |||
remote_non_friends = Agent.get(:non_friends_remote, & &1) | |||
Benchee.run(%{ | |||
"public timeline without blocks" => fn -> | |||
ActivityPub.fetch_public_activities(opts) | |||
end | |||
}) | |||
Enum.each(remote_non_friends, fn non_friend -> | |||
{:ok, _} = User.block(user, non_friend) | |||
end) | |||
user = User.get_by_id(user.id) | |||
opts = Map.put(opts, "blocking_user", user) | |||
Benchee.run( | |||
%{ | |||
"public timeline with user block" => fn -> | |||
ActivityPub.fetch_public_activities(opts) | |||
end | |||
}, | |||
) | |||
domains = | |||
Enum.reduce(remote_non_friends, [], fn non_friend, domains -> | |||
{:ok, _user} = User.unblock(user, non_friend) | |||
%{host: host} = URI.parse(non_friend.ap_id) | |||
[host | domains] | |||
end) | |||
domains = Enum.uniq(domains) | |||
Enum.each(domains, fn domain -> | |||
{:ok, _} = User.block_domain(user, domain) | |||
end) | |||
user = User.get_by_id(user.id) | |||
opts = Map.put(opts, "blocking_user", user) | |||
Benchee.run( | |||
%{ | |||
"public timeline with domain block" => fn opts -> | |||
ActivityPub.fetch_public_activities(opts) | |||
end | |||
} | |||
) | |||
end | |||
defp fetch_public_timeline(opts, title) when is_binary(title) do | |||
first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last() | |||
@@ -27,7 +27,7 @@ defmodule Pleroma.LoadTesting.Users do | |||
make_friends(main_user, opts[:friends]) | |||
Repo.get(User, main_user.id) | |||
User.get_by_id(main_user.id) | |||
end | |||
def generate_users(max) do | |||
@@ -166,4 +166,24 @@ defmodule Pleroma.LoadTesting.Users do | |||
) | |||
|> Stream.run() | |||
end | |||
@spec prepare_users(User.t(), keyword()) :: map() | |||
def prepare_users(user, opts) do | |||
friends_limit = opts[:friends_used] | |||
non_friends_limit = opts[:non_friends_used] | |||
%{ | |||
user: user, | |||
friends_local: fetch_users(user, friends_limit, :local, true), | |||
friends_remote: fetch_users(user, friends_limit, :external, true), | |||
non_friends_local: fetch_users(user, non_friends_limit, :local, false), | |||
non_friends_remote: fetch_users(user, non_friends_limit, :external, false) | |||
} | |||
end | |||
defp fetch_users(user, limit, local, friends?) do | |||
user | |||
|> get_users(limit: limit, local: local, friends?: friends?) | |||
|> Enum.shuffle() | |||
end | |||
end |
@@ -5,7 +5,6 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do | |||
import Ecto.Query | |||
alias Pleroma.Repo | |||
alias Pleroma.Web.MastodonAPI.TimelineController | |||
def run(_args) do | |||
Mix.Pleroma.start_pleroma() | |||
@@ -37,7 +36,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do | |||
Benchee.run( | |||
%{ | |||
"Hashtag fetching, any" => fn tags -> | |||
TimelineController.hashtag_fetching( | |||
hashtag_fetching( | |||
%{ | |||
"any" => tags | |||
}, | |||
@@ -47,7 +46,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do | |||
end, | |||
# Will always return zero results because no overlapping hashtags are generated. | |||
"Hashtag fetching, all" => fn tags -> | |||
TimelineController.hashtag_fetching( | |||
hashtag_fetching( | |||
%{ | |||
"all" => tags | |||
}, | |||
@@ -67,7 +66,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do | |||
Benchee.run( | |||
%{ | |||
"Hashtag fetching" => fn tag -> | |||
TimelineController.hashtag_fetching( | |||
hashtag_fetching( | |||
%{ | |||
"tag" => tag | |||
}, | |||
@@ -80,4 +79,35 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do | |||
time: 5 | |||
) | |||
end | |||
defp hashtag_fetching(params, user, local_only) do | |||
tags = | |||
[params["tag"], params["any"]] | |||
|> List.flatten() | |||
|> Enum.uniq() | |||
|> Enum.filter(& &1) | |||
|> Enum.map(&String.downcase(&1)) | |||
tag_all = | |||
params | |||
|> Map.get("all", []) | |||
|> Enum.map(&String.downcase(&1)) | |||
tag_reject = | |||
params | |||
|> Map.get("none", []) | |||
|> Enum.map(&String.downcase(&1)) | |||
_activities = | |||
params | |||
|> Map.put("type", "Create") | |||
|> Map.put("local_only", local_only) | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("muting_user", user) | |||
|> Map.put("user", user) | |||
|> Map.put("tag", tags) | |||
|> Map.put("tag_all", tag_all) | |||
|> Map.put("tag_reject", tag_reject) | |||
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() | |||
end | |||
end |
@@ -171,7 +171,8 @@ config :mime, :types, %{ | |||
"application/ld+json" => ["activity+json"] | |||
} | |||
config :tesla, adapter: Tesla.Adapter.Gun | |||
config :tesla, adapter: Tesla.Adapter.Hackney | |||
# Configures http settings, upstream proxy etc. | |||
config :pleroma, :http, | |||
proxy_url: nil, | |||
@@ -183,7 +184,7 @@ config :pleroma, :instance, | |||
name: "Pleroma", | |||
email: "example@example.com", | |||
notify_email: "noreply@example.com", | |||
description: "A Pleroma instance, an alternative fediverse server", | |||
description: "Pleroma: An efficient and flexible fediverse server", | |||
background_image: "/images/city.jpg", | |||
limit: 5_000, | |||
chat_limit: 5_000, | |||
@@ -547,7 +547,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret | |||
```json | |||
{ | |||
"totalReports" : 1, | |||
"total" : 1, | |||
"reports": [ | |||
{ | |||
"account": { | |||
@@ -768,7 +768,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret | |||
- 400 Bad Request `"Invalid parameters"` when `status` is missing | |||
- On success: `204`, empty response | |||
## `POST /api/pleroma/admin/reports/:report_id/notes/:id` | |||
## `DELETE /api/pleroma/admin/reports/:report_id/notes/:id` | |||
### Delete report note | |||
@@ -6,10 +6,6 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma | |||
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings | |||
## Attachment cap | |||
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting. | |||
## Timelines | |||
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users. | |||
@@ -32,12 +28,20 @@ Has these additional fields under the `pleroma` object: | |||
- `thread_muted`: true if the thread the post belongs to is muted | |||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. | |||
## Attachments | |||
## Media Attachments | |||
Has these additional fields under the `pleroma` object: | |||
- `mime_type`: mime type of the attachment. | |||
### Attachment cap | |||
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting. | |||
### Limitations | |||
Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it. | |||
## Accounts | |||
The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc. | |||
@@ -69,3 +69,32 @@ mix pleroma.database update_users_following_followers_counts | |||
```sh tab="From Source" | |||
mix pleroma.database fix_likes_collections | |||
``` | |||
## Vacuum the database | |||
### Analyze | |||
Running an `analyze` vacuum job can improve performance by updating statistics used by the query planner. **It is safe to cancel this.** | |||
```sh tab="OTP" | |||
./bin/pleroma_ctl database vacuum analyze | |||
``` | |||
```sh tab="From Source" | |||
mix pleroma.database vacuum analyze | |||
``` | |||
### Full | |||
Running a `full` vacuum job rebuilds your entire database by reading all of the data and rewriting it into smaller | |||
and more compact files with an optimized layout. This process will take a long time and use additional disk space as | |||
it builds the files side-by-side the existing database files. It can make your database faster and use less disk space, | |||
but should only be run if necessary. **It is safe to cancel this.** | |||
```sh tab="OTP" | |||
./bin/pleroma_ctl database vacuum full | |||
``` | |||
```sh tab="From Source" | |||
mix pleroma.database vacuum full | |||
``` |
@@ -42,6 +42,12 @@ Feel free to contact us to be added to this list! | |||
- Platforms: SailfishOS | |||
- Features: No Streaming | |||
### Husky | |||
- Source code: <https://git.mentality.rip/FWGS/Husky> | |||
- Contact: [@Husky@enigmatic.observer](https://enigmatic.observer/users/Husky) | |||
- Platforms: Android | |||
- Features: No Streaming, Emoji Reactions, Text Formatting, FE Stickers | |||
### Nekonium | |||
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/) | |||
- Source: <https://gogs.gdgd.jp.net/lin/nekonium> | |||
@@ -4,6 +4,7 @@ | |||
defmodule Mix.Tasks.Pleroma.Database do | |||
alias Pleroma.Conversation | |||
alias Pleroma.Maintenance | |||
alias Pleroma.Object | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
@@ -34,13 +35,7 @@ defmodule Mix.Tasks.Pleroma.Database do | |||
) | |||
if Keyword.get(options, :vacuum) do | |||
Logger.info("Runnning VACUUM FULL") | |||
Repo.query!( | |||
"vacuum full;", | |||
[], | |||
timeout: :infinity | |||
) | |||
Maintenance.vacuum("full") | |||
end | |||
end | |||
@@ -94,13 +89,7 @@ defmodule Mix.Tasks.Pleroma.Database do | |||
|> Repo.delete_all(timeout: :infinity) | |||
if Keyword.get(options, :vacuum) do | |||
Logger.info("Runnning VACUUM FULL") | |||
Repo.query!( | |||
"vacuum full;", | |||
[], | |||
timeout: :infinity | |||
) | |||
Maintenance.vacuum("full") | |||
end | |||
end | |||
@@ -135,4 +124,10 @@ defmodule Mix.Tasks.Pleroma.Database do | |||
end) | |||
|> Stream.run() | |||
end | |||
def run(["vacuum", args]) do | |||
start_pleroma() | |||
Maintenance.vacuum(args) | |||
end | |||
end |
@@ -24,6 +24,6 @@ defmodule Pleroma.Constants do | |||
const(static_only_files, | |||
do: | |||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) | |||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css) | |||
) | |||
end |
@@ -22,22 +22,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do | |||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) | |||
end | |||
defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts | |||
defp add_scheme_opts(opts, %URI{scheme: "https", host: host}) do | |||
ssl_opts = [ | |||
ssl_options: [ | |||
# Workaround for remote server certificate chain issues | |||
partial_chain: &:hackney_connect.partial_chain/1, | |||
# We don't support TLS v1.3 yet | |||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], | |||
server_name_indication: to_charlist(host) | |||
] | |||
] | |||
Keyword.merge(opts, ssl_opts) | |||
end | |||
defp add_scheme_opts(opts, _), do: opts | |||
def after_request(_), do: :ok | |||
end |
@@ -0,0 +1,37 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Maintenance do | |||
alias Pleroma.Repo | |||
require Logger | |||
def vacuum(args) do | |||
case args do | |||
"analyze" -> | |||
Logger.info("Runnning VACUUM ANALYZE.") | |||
Repo.query!( | |||
"vacuum analyze;", | |||
[], | |||
timeout: :infinity | |||
) | |||
"full" -> | |||
Logger.info("Runnning VACUUM FULL.") | |||
Logger.warn( | |||
"Re-packing your entire database may take a while and will consume extra disk space during the process." | |||
) | |||
Repo.query!( | |||
"vacuum full;", | |||
[], | |||
timeout: :infinity | |||
) | |||
_ -> | |||
Logger.error("Error: invalid vacuum argument.") | |||
end | |||
end | |||
end |
@@ -31,7 +31,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
{"x-content-type-options", "nosniff"}, | |||
{"referrer-policy", referrer_policy}, | |||
{"x-download-options", "noopen"}, | |||
{"content-security-policy", csp_string() <> ";"} | |||
{"content-security-policy", csp_string()} | |||
] | |||
if report_uri do | |||
@@ -43,23 +43,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
] | |||
} | |||
headers ++ [{"reply-to", Jason.encode!(report_group)}] | |||
[{"reply-to", Jason.encode!(report_group)} | headers] | |||
else | |||
headers | |||
end | |||
end | |||
static_csp_rules = [ | |||
"default-src 'none'", | |||
"base-uri 'self'", | |||
"frame-ancestors 'none'", | |||
"style-src 'self' 'unsafe-inline'", | |||
"font-src 'self'", | |||
"manifest-src 'self'" | |||
] | |||
@csp_start [Enum.join(static_csp_rules, ";") <> ";"] | |||
defp csp_string do | |||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] | |||
static_url = Pleroma.Web.Endpoint.static_url() | |||
websocket_url = Pleroma.Web.Endpoint.websocket_url() | |||
report_uri = Config.get([:http_security, :report_uri]) | |||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}" | |||
img_src = "img-src 'self' data: blob:" | |||
media_src = "media-src 'self'" | |||
{img_src, media_src} = | |||
if Config.get([:media_proxy, :enabled]) && | |||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do | |||
sources = get_proxy_and_attachment_sources() | |||
{[img_src, sources], [media_src, sources]} | |||
else | |||
{[img_src, " https:"], [media_src, " https:"]} | |||
end | |||
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] | |||
connect_src = | |||
if Pleroma.Config.get(:env) == :dev do | |||
connect_src <> " http://localhost:3035/" | |||
[connect_src, " http://localhost:3035/"] | |||
else | |||
connect_src | |||
end | |||
@@ -71,27 +94,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
"script-src 'self'" | |||
end | |||
main_part = [ | |||
"default-src 'none'", | |||
"base-uri 'self'", | |||
"frame-ancestors 'none'", | |||
"img-src 'self' data: blob: https:", | |||
"media-src 'self' https:", | |||
"style-src 'self' 'unsafe-inline'", | |||
"font-src 'self'", | |||
"manifest-src 'self'", | |||
connect_src, | |||
script_src | |||
] | |||
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] | |||
insecure = if scheme == "https", do: "upgrade-insecure-requests" | |||
@csp_start | |||
|> add_csp_param(img_src) | |||
|> add_csp_param(media_src) | |||
|> add_csp_param(connect_src) | |||
|> add_csp_param(script_src) | |||
|> add_csp_param(insecure) | |||
|> add_csp_param(report) | |||
|> :erlang.iolist_to_binary() | |||
end | |||
defp get_proxy_and_attachment_sources do | |||
media_proxy_whitelist = | |||
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc -> | |||
add_source(acc, host) | |||
end) | |||
report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: [] | |||
upload_base_url = | |||
if Config.get([Pleroma.Upload, :base_url]), | |||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host | |||
insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: [] | |||
s3_endpoint = | |||
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3, | |||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host | |||
(main_part ++ report ++ insecure) | |||
|> Enum.join("; ") | |||
[] | |||
|> add_source(upload_base_url) | |||
|> add_source(s3_endpoint) | |||
|> add_source(media_proxy_whitelist) | |||
end | |||
defp add_source(iodata, nil), do: iodata | |||
defp add_source(iodata, source), do: [[?\s, source] | iodata] | |||
defp add_csp_param(csp_iodata, nil), do: csp_iodata | |||
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata] | |||
def warn_if_disabled do | |||
unless Config.get([:http_security, :enabled]) do | |||
Logger.warn(" | |||
@@ -45,7 +45,7 @@ defmodule Pleroma.User.Query do | |||
is_admin: boolean(), | |||
is_moderator: boolean(), | |||
super_users: boolean(), | |||
exclude_service_users: boolean(), | |||
invisible: boolean(), | |||
followers: User.t(), | |||
friends: User.t(), | |||
recipients_from_activity: [String.t()], | |||
@@ -89,8 +89,8 @@ defmodule Pleroma.User.Query do | |||
where(query, [u], ilike(field(u, ^key), ^"%#{value}%")) | |||
end | |||
defp compose_query({:exclude_service_users, _}, query) do | |||
where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch")) | |||
defp compose_query({:invisible, bool}, query) when is_boolean(bool) do | |||
where(query, [u], u.invisible == ^bool) | |||
end | |||
defp compose_query({key, value}, query) | |||
@@ -938,6 +938,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), | |||
where: | |||
fragment( | |||
"recipients_contain_blocked_domains(?, ?) = false", | |||
activity.recipients, | |||
^domain_blocks | |||
), | |||
where: | |||
fragment( | |||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", | |||
activity.data, | |||
activity.data, | |||
@@ -1030,6 +1036,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
end | |||
defp exclude_invisible_actors(query, %{"invisible_actors" => true}), do: query | |||
defp exclude_invisible_actors(query, _opts) do | |||
invisible_ap_ids = | |||
User.Query.build(%{invisible: true, select: [:ap_id]}) | |||
|> Repo.all() | |||
|> Enum.map(fn %{ap_id: ap_id} -> ap_id end) | |||
from([activity] in query, where: activity.actor not in ^invisible_ap_ids) | |||
end | |||
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do | |||
from(activity in query, where: activity.id != ^id) | |||
end | |||
@@ -1135,6 +1152,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
|> restrict_instance(opts) | |||
|> Activity.restrict_deactivated_users() | |||
|> exclude_poll_votes(opts) | |||
|> exclude_invisible_actors(opts) | |||
|> exclude_visibility(opts) | |||
end | |||
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do | |||
alias Pleroma.Object | |||
alias Pleroma.User | |||
alias Pleroma.Web.ActivityPub.Relay | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.ActivityPub.Visibility | |||
@@ -85,15 +86,20 @@ defmodule Pleroma.Web.ActivityPub.Builder do | |||
end | |||
end | |||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} | |||
def announce(actor, object, options \\ []) do | |||
public? = Keyword.get(options, :public, false) | |||
to = [actor.follower_address, object.data["actor"]] | |||
to = | |||
if public? do | |||
[Pleroma.Constants.as_public() | to] | |||
else | |||
to | |||
cond do | |||
actor.ap_id == Relay.relay_ap_id() -> | |||
[actor.follower_address] | |||
public? -> | |||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] | |||
true -> | |||
[actor.follower_address, object.data["actor"]] | |||
end | |||
{:ok, | |||
@@ -33,11 +33,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do | |||
# - Stream out the announce | |||
def handle(%{data: %{"type" => "Announce"}} = object, meta) do | |||
announced_object = Object.get_by_ap_id(object.data["object"]) | |||
user = User.get_cached_by_ap_id(object.data["actor"]) | |||
Utils.add_announce_to_object(object, announced_object) | |||
Notification.create_notifications(object) | |||
ActivityPub.stream_out(object) | |||
if !User.is_internal_user?(user) do | |||
Notification.create_notifications(object) | |||
ActivityPub.stream_out(object) | |||
end | |||
{:ok, object, meta} | |||
end | |||
@@ -740,6 +740,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
def get_reports(params, page, page_size) do | |||
params = | |||
params | |||
|> Map.new(fn {key, value} -> {to_string(key), value} end) | |||
|> Map.put("type", "Flag") | |||
|> Map.put("skip_preload", true) | |||
|> Map.put("preload_report_notes", true) | |||
@@ -7,31 +7,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||
alias Pleroma.Activity | |||
alias Pleroma.Config | |||
alias Pleroma.MFA | |||
alias Pleroma.ModerationLog | |||
alias Pleroma.Plugs.OAuthScopesPlug | |||
alias Pleroma.ReportNote | |||
alias Pleroma.Stats | |||
alias Pleroma.User | |||
alias Pleroma.UserInviteToken | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Builder | |||
alias Pleroma.Web.ActivityPub.Pipeline | |||
alias Pleroma.Web.ActivityPub.Relay | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.AdminAPI | |||
alias Pleroma.Web.AdminAPI.AccountView | |||
alias Pleroma.Web.AdminAPI.ModerationLogView | |||
alias Pleroma.Web.AdminAPI.Report | |||
alias Pleroma.Web.AdminAPI.ReportView | |||
alias Pleroma.Web.AdminAPI.Search | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.Endpoint | |||
alias Pleroma.Web.MastodonAPI | |||
alias Pleroma.Web.MastodonAPI.AppView | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.Router | |||
require Logger | |||
@@ -66,14 +56,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
] | |||
) | |||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:invites"], admin: true} | |||
when action in [:create_invite_token, :revoke_invite, :email_invite] | |||
) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:follows"], admin: true} | |||
@@ -82,18 +64,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["read:reports"], admin: true} | |||
when action in [:list_reports, :report_show] | |||
) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:reports"], admin: true} | |||
when action in [:reports_update, :report_notes_create, :report_notes_delete] | |||
) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["read:statuses"], admin: true} | |||
when action in [:list_user_statuses, :list_instance_statuses] | |||
) | |||
@@ -116,10 +86,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
:restart, | |||
:resend_confirmation_email, | |||
:confirm_email, | |||
:oauth_app_create, | |||
:oauth_app_list, | |||
:oauth_app_update, | |||
:oauth_app_delete, | |||
:reload_emoji | |||
] | |||
) | |||
@@ -288,7 +254,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
}) | |||
conn | |||
|> put_view(MastodonAPI.StatusView) | |||
|> put_view(AdminAPI.StatusView) | |||
|> render("index.json", %{activities: activities, as: :activity}) | |||
else | |||
_ -> {:error, :not_found} | |||
@@ -569,69 +535,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
end | |||
end | |||
@doc "Sends registration invite via email" | |||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do | |||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, | |||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, | |||
{:ok, invite_token} <- UserInviteToken.create_invite(), | |||
email <- | |||
Pleroma.Emails.UserEmail.user_invitation_email( | |||
user, | |||
invite_token, | |||
email, | |||
params["name"] | |||
), | |||
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do | |||
json_response(conn, :no_content, "") | |||
else | |||
{:registrations_open, _} -> | |||
{:error, "To send invites you need to set the `registrations_open` option to false."} | |||
{:invites_enabled, _} -> | |||
{:error, "To send invites you need to set the `invites_enabled` option to true."} | |||
end | |||
end | |||
@doc "Create an account registration invite token" | |||
def create_invite_token(conn, params) do | |||
opts = %{} | |||
opts = | |||
if params["max_use"], | |||
do: Map.put(opts, :max_use, params["max_use"]), | |||
else: opts | |||
opts = | |||
if params["expires_at"], | |||
do: Map.put(opts, :expires_at, params["expires_at"]), | |||
else: opts | |||
{:ok, invite} = UserInviteToken.create_invite(opts) | |||
json(conn, AccountView.render("invite.json", %{invite: invite})) | |||
end | |||
@doc "Get list of created invites" | |||
def invites(conn, _params) do | |||
invites = UserInviteToken.list_invites() | |||
conn | |||
|> put_view(AccountView) | |||
|> render("invites.json", %{invites: invites}) | |||
end | |||
@doc "Revokes invite by token" | |||
def revoke_invite(conn, %{"token" => token}) do | |||
with {:ok, invite} <- UserInviteToken.find_by_token(token), | |||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do | |||
conn | |||
|> put_view(AccountView) | |||
|> render("invite.json", %{invite: updated_invite}) | |||
else | |||
nil -> {:error, :not_found} | |||
end | |||
end | |||
@doc "Get a password reset token (base64 string) for given nickname" | |||
def get_password_reset(conn, %{"nickname" => nickname}) do | |||
(%User{local: true} = user) = User.get_cached_by_nickname(nickname) | |||
@@ -718,85 +621,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
end | |||
end | |||
def list_reports(conn, params) do | |||
{page, page_size} = page_params(params) | |||
reports = Utils.get_reports(params, page, page_size) | |||
conn | |||
|> put_view(ReportView) | |||
|> render("index.json", %{reports: reports}) | |||
end | |||
def report_show(conn, %{"id" => id}) do | |||
with %Activity{} = report <- Activity.get_by_id(id) do | |||
conn | |||
|> put_view(ReportView) | |||
|> render("show.json", Report.extract_report_info(report)) | |||
else | |||
_ -> {:error, :not_found} | |||
end | |||
end | |||
def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do | |||
result = | |||
reports | |||
|> Enum.map(fn report -> | |||
with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do | |||
ModerationLog.insert_log(%{ | |||
action: "report_update", | |||
actor: admin, | |||
subject: activity | |||
}) | |||
activity | |||
else | |||
{:error, message} -> %{id: report["id"], error: message} | |||
end | |||
end) | |||
case Enum.any?(result, &Map.has_key?(&1, :error)) do | |||
true -> json_response(conn, :bad_request, result) | |||
false -> json_response(conn, :no_content, "") | |||
end | |||
end | |||
def report_notes_create(%{assigns: %{user: user}} = conn, %{ | |||
"id" => report_id, | |||
"content" => content | |||
}) do | |||
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do | |||
ModerationLog.insert_log(%{ | |||
action: "report_note", | |||
actor: user, | |||
subject: Activity.get_by_id(report_id), | |||
text: content | |||
}) | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def report_notes_delete(%{assigns: %{user: user}} = conn, %{ | |||
"id" => note_id, | |||
"report_id" => report_id | |||
}) do | |||
with {:ok, note} <- ReportNote.destroy(note_id) do | |||
ModerationLog.insert_log(%{ | |||
action: "report_note_delete", | |||
actor: user, | |||
subject: Activity.get_by_id(report_id), | |||
text: note.content | |||
}) | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def list_log(conn, params) do | |||
{page, page_size} = page_params(params) | |||
@@ -869,83 +693,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
conn |> json("") | |||
end | |||
def oauth_app_create(conn, params) do | |||
params = | |||
if params["name"] do | |||
Map.put(params, "client_name", params["name"]) | |||
else | |||
params | |||
end | |||
result = | |||
case App.create(params) do | |||
{:ok, app} -> | |||
AppView.render("show.json", %{app: app, admin: true}) | |||
{:error, changeset} -> | |||
App.errors(changeset) | |||
end | |||
json(conn, result) | |||
end | |||
def oauth_app_update(conn, params) do | |||
params = | |||
if params["name"] do | |||
Map.put(params, "client_name", params["name"]) | |||
else | |||
params | |||
end | |||
with {:ok, app} <- App.update(params) do | |||
json(conn, AppView.render("show.json", %{app: app, admin: true})) | |||
else | |||
{:error, changeset} -> | |||
json(conn, App.errors(changeset)) | |||
nil -> | |||
json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def oauth_app_list(conn, params) do | |||
{page, page_size} = page_params(params) | |||
search_params = %{ | |||
client_name: params["name"], | |||
client_id: params["client_id"], | |||
page: page, | |||
page_size: page_size | |||
} | |||
search_params = | |||
if Map.has_key?(params, "trusted") do | |||
Map.put(search_params, :trusted, params["trusted"]) | |||
else | |||
search_params | |||
end | |||
with {:ok, apps, count} <- App.search(search_params) do | |||
json( | |||
conn, | |||
AppView.render("index.json", | |||
apps: apps, | |||
count: count, | |||
page_size: page_size, | |||
admin: true | |||
) | |||
) | |||
end | |||
end | |||
def oauth_app_delete(conn, params) do | |||
with {:ok, _app} <- App.destroy(params["id"]) do | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def stats(conn, _) do | |||
count = Stats.get_status_visibility_count() | |||
@@ -0,0 +1,78 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.InviteController do | |||
use Pleroma.Web, :controller | |||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||
alias Pleroma.Config | |||
alias Pleroma.Plugs.OAuthScopesPlug | |||
alias Pleroma.UserInviteToken | |||
require Logger | |||
plug(Pleroma.Web.ApiSpec.CastAndValidate) | |||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :index) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:invites"], admin: true} when action in [:create, :revoke, :email] | |||
) | |||
action_fallback(Pleroma.Web.AdminAPI.FallbackController) | |||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InviteOperation | |||
@doc "Get list of created invites" | |||
def index(conn, _params) do | |||
invites = UserInviteToken.list_invites() | |||
render(conn, "index.json", invites: invites) | |||
end | |||
@doc "Create an account registration invite token" | |||
def create(%{body_params: params} = conn, _) do | |||
{:ok, invite} = UserInviteToken.create_invite(params) | |||
render(conn, "show.json", invite: invite) | |||
end | |||
@doc "Revokes invite by token" | |||
def revoke(%{body_params: %{token: token}} = conn, _) do | |||
with {:ok, invite} <- UserInviteToken.find_by_token(token), | |||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do | |||
render(conn, "show.json", invite: updated_invite) | |||
else | |||
nil -> {:error, :not_found} | |||
error -> error | |||
end | |||
end | |||
@doc "Sends registration invite via email" | |||
def email(%{assigns: %{user: user}, body_params: %{email: email} = params} = conn, _) do | |||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, | |||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, | |||
{:ok, invite_token} <- UserInviteToken.create_invite(), | |||
{:ok, _} <- | |||
user | |||
|> Pleroma.Emails.UserEmail.user_invitation_email( | |||
invite_token, | |||
email, | |||
params[:name] | |||
) | |||
|> Pleroma.Emails.Mailer.deliver() do | |||
json_response(conn, :no_content, "") | |||
else | |||
{:registrations_open, _} -> | |||
{:error, "To send invites you need to set the `registrations_open` option to false."} | |||
{:invites_enabled, _} -> | |||
{:error, "To send invites you need to set the `invites_enabled` option to true."} | |||
{:error, error} -> | |||
{:error, error} | |||
end | |||
end | |||
end |
@@ -0,0 +1,87 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.OAuthAppController do | |||
use Pleroma.Web, :controller | |||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||
alias Pleroma.Plugs.OAuthScopesPlug | |||
alias Pleroma.Web.OAuth.App | |||
require Logger | |||
plug(Pleroma.Web.ApiSpec.CastAndValidate) | |||
plug(:put_view, Pleroma.Web.MastodonAPI.AppView) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write"], admin: true} | |||
when action in [:create, :index, :update, :delete] | |||
) | |||
action_fallback(Pleroma.Web.AdminAPI.FallbackController) | |||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation | |||
def index(conn, params) do | |||
search_params = | |||
params | |||
|> Map.take([:client_id, :page, :page_size, :trusted]) | |||
|> Map.put(:client_name, params[:name]) | |||
with {:ok, apps, count} <- App.search(search_params) do | |||
render(conn, "index.json", | |||
apps: apps, | |||
count: count, | |||
page_size: params.page_size, | |||
admin: true | |||
) | |||
end | |||
end | |||
def create(%{body_params: params} = conn, _) do | |||
params = | |||
if params[:name] do | |||
Map.put(params, :client_name, params[:name]) | |||
else | |||
params | |||
end | |||
case App.create(params) do | |||
{:ok, app} -> | |||
render(conn, "show.json", app: app, admin: true) | |||
{:error, changeset} -> | |||
json(conn, App.errors(changeset)) | |||
end | |||
end | |||
def update(%{body_params: params} = conn, %{id: id}) do | |||
params = | |||
if params[:name] do | |||
Map.put(params, :client_name, params.name) | |||
else | |||
params | |||
end | |||
with {:ok, app} <- App.update(id, params) do | |||
render(conn, "show.json", app: app, admin: true) | |||
else | |||
{:error, changeset} -> | |||
json(conn, App.errors(changeset)) | |||
nil -> | |||
json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def delete(conn, params) do | |||
with {:ok, _app} <- App.destroy(params.id) do | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
end |
@@ -0,0 +1,107 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.ReportController do | |||
use Pleroma.Web, :controller | |||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||
alias Pleroma.Activity | |||
alias Pleroma.ModerationLog | |||
alias Pleroma.Plugs.OAuthScopesPlug | |||
alias Pleroma.ReportNote | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.AdminAPI | |||
alias Pleroma.Web.AdminAPI.Report | |||
alias Pleroma.Web.CommonAPI | |||
require Logger | |||
plug(Pleroma.Web.ApiSpec.CastAndValidate) | |||
plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show]) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:reports"], admin: true} | |||
when action in [:update, :notes_create, :notes_delete] | |||
) | |||
action_fallback(AdminAPI.FallbackController) | |||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation | |||
def index(conn, params) do | |||
reports = Utils.get_reports(params, params.page, params.page_size) | |||
render(conn, "index.json", reports: reports) | |||
end | |||
def show(conn, %{id: id}) do | |||
with %Activity{} = report <- Activity.get_by_id(id) do | |||
render(conn, "show.json", Report.extract_report_info(report)) | |||
else | |||
_ -> {:error, :not_found} | |||
end | |||
end | |||
def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn, _) do | |||
result = | |||
Enum.map(reports, fn report -> | |||
case CommonAPI.update_report_state(report.id, report.state) do | |||
{:ok, activity} -> | |||
ModerationLog.insert_log(%{ | |||
action: "report_update", | |||
actor: admin, | |||
subject: activity | |||
}) | |||
activity | |||
{:error, message} -> | |||
%{id: report.id, error: message} | |||
end | |||
end) | |||
if Enum.any?(result, &Map.has_key?(&1, :error)) do | |||
json_response(conn, :bad_request, result) | |||
else | |||
json_response(conn, :no_content, "") | |||
end | |||
end | |||
def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{ | |||
id: report_id | |||
}) do | |||
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do | |||
ModerationLog.insert_log(%{ | |||
action: "report_note", | |||
actor: user, | |||
subject: Activity.get_by_id(report_id), | |||
text: content | |||
}) | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
def notes_delete(%{assigns: %{user: user}} = conn, %{ | |||
id: note_id, | |||
report_id: report_id | |||
}) do | |||
with {:ok, note} <- ReportNote.destroy(note_id) do | |||
ModerationLog.insert_log(%{ | |||
action: "report_note_delete", | |||
actor: user, | |||
subject: Activity.get_by_id(report_id), | |||
text: note.content | |||
}) | |||
json_response(conn, :no_content, "") | |||
else | |||
_ -> json_response(conn, :bad_request, "") | |||
end | |||
end | |||
end |
@@ -41,9 +41,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do | |||
def show(conn, %{id: id}) do | |||
with %Activity{} = activity <- Activity.get_by_id(id) do | |||
conn | |||
|> put_view(MastodonAPI.StatusView) | |||
|> render("show.json", %{activity: activity}) | |||
render(conn, "show.json", %{activity: activity}) | |||
else | |||
nil -> {:error, :not_found} | |||
end | |||
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.AdminAPI.Search do | |||
query = | |||
params | |||
|> Map.drop([:page, :page_size]) | |||
|> Map.put(:exclude_service_users, true) | |||
|> Map.put(:invisible, false) | |||
|> User.Query.build() | |||
|> order_by([u], u.nickname) | |||
@@ -31,7 +31,6 @@ defmodule Pleroma.Web.AdminAPI.Search do | |||
count = Repo.aggregate(query, :count, :id) | |||
results = Repo.all(paginated_query) | |||
{:ok, results, count} | |||
end | |||
end |
@@ -80,24 +80,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do | |||
} | |||
end | |||
def render("invite.json", %{invite: invite}) do | |||
%{ | |||
"id" => invite.id, | |||
"token" => invite.token, | |||
"used" => invite.used, | |||
"expires_at" => invite.expires_at, | |||
"uses" => invite.uses, | |||
"max_use" => invite.max_use, | |||
"invite_type" => invite.invite_type | |||
} | |||
end | |||
def render("invites.json", %{invites: invites}) do | |||
%{ | |||
invites: render_many(invites, AccountView, "invite.json", as: :invite) | |||
} | |||
end | |||
def render("created.json", %{user: user}) do | |||
%{ | |||
type: "success", | |||
@@ -0,0 +1,25 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.InviteView do | |||
use Pleroma.Web, :view | |||
def render("index.json", %{invites: invites}) do | |||
%{ | |||
invites: render_many(invites, __MODULE__, "show.json", as: :invite) | |||
} | |||
end | |||
def render("show.json", %{invite: invite}) do | |||
%{ | |||
"id" => invite.id, | |||
"token" => invite.token, | |||
"used" => invite.used, | |||
"expires_at" => invite.expires_at, | |||
"uses" => invite.uses, | |||
"max_use" => invite.max_use, | |||
"invite_type" => invite.invite_type | |||
} | |||
end | |||
end |
@@ -0,0 +1,148 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do | |||
alias OpenApiSpex.Operation | |||
alias OpenApiSpex.Schema | |||
alias Pleroma.Web.ApiSpec.Schemas.ApiError | |||
import Pleroma.Web.ApiSpec.Helpers | |||
def open_api_operation(action) do | |||
operation = String.to_existing_atom("#{action}_operation") | |||
apply(__MODULE__, operation, []) | |||
end | |||
def index_operation do | |||
%Operation{ | |||
tags: ["Admin", "Invites"], | |||
summary: "Get a list of generated invites", | |||
operationId: "AdminAPI.InviteController.index", | |||
security: [%{"oAuth" => ["read:invites"]}], | |||
responses: %{ | |||
200 => | |||
Operation.response("Invites", "application/json", %Schema{ | |||
type: :object, | |||
properties: %{ | |||
invites: %Schema{type: :array, items: invite()} | |||
}, | |||
example: %{ | |||
"invites" => [ | |||
%{ | |||
"id" => 123, | |||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=", | |||
"used" => true, | |||
"expires_at" => nil, | |||
"uses" => 0, | |||
"max_use" => nil, | |||
"invite_type" => "one_time" | |||
} | |||
] | |||
} | |||
}) | |||
} | |||
} | |||
end | |||
def create_operation do | |||
%Operation{ | |||
tags: ["Admin", "Invites"], | |||
summary: "Create an account registration invite token", | |||
operationId: "AdminAPI.InviteController.create", | |||
security: [%{"oAuth" => ["write:invites"]}], | |||
requestBody: | |||
request_body("Parameters", %Schema{ | |||
type: :object, | |||
properties: %{ | |||
max_use: %Schema{type: :integer}, | |||
expires_at: %Schema{type: :string, format: :date, example: "2020-04-20"} | |||
} | |||
}), | |||
responses: %{ | |||
200 => Operation.response("Invite", "application/json", invite()) | |||
} | |||
} | |||
end | |||
def revoke_operation do | |||
%Operation{ | |||
tags: ["Admin", "Invites"], | |||
summary: "Revoke invite by token", | |||
operationId: "AdminAPI.InviteController.revoke", | |||
security: [%{"oAuth" => ["write:invites"]}], | |||
requestBody: | |||
request_body( | |||
"Parameters", | |||
%Schema{ | |||
type: :object, | |||
required: [:token], | |||
properties: %{ | |||
token: %Schema{type: :string} | |||
} | |||
}, | |||
required: true | |||
), | |||
responses: %{ | |||
200 => Operation.response("Invite", "application/json", invite()), | |||
400 => Operation.response("Bad Request", "application/json", ApiError), | |||
404 => Operation.response("Not Found", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def email_operation do | |||
%Operation{ | |||
tags: ["Admin", "Invites"], | |||
summary: "Sends registration invite via email", | |||
operationId: "AdminAPI.InviteController.email", | |||
security: [%{"oAuth" => ["write:invites"]}], | |||
requestBody: | |||
request_body( | |||
"Parameters", | |||
%Schema{ | |||
type: :object, | |||
required: [:email], | |||
properties: %{ | |||
email: %Schema{type: :string, format: :email}, | |||
name: %Schema{type: :string} | |||
} | |||
}, | |||
required: true | |||
), | |||
responses: %{ | |||
204 => no_content_response(), | |||
400 => Operation.response("Bad Request", "application/json", ApiError), | |||
403 => Operation.response("Forbidden", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
defp invite do | |||
%Schema{ | |||
title: "Invite", | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{type: :integer}, | |||
token: %Schema{type: :string}, | |||
used: %Schema{type: :boolean}, | |||
expires_at: %Schema{type: :string, format: :date, nullable: true}, | |||
uses: %Schema{type: :integer}, | |||
max_use: %Schema{type: :integer, nullable: true}, | |||
invite_type: %Schema{ | |||
type: :string, | |||
enum: ["one_time", "reusable", "date_limited", "reusable_date_limited"] | |||
} | |||
}, | |||
example: %{ | |||
"id" => 123, | |||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=", | |||
"used" => true, | |||
"expires_at" => nil, | |||
"uses" => 0, | |||
"max_use" => nil, | |||
"invite_type" => "one_time" | |||
} | |||
} | |||
end | |||
end |
@@ -0,0 +1,215 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do | |||
alias OpenApiSpex.Operation | |||
alias OpenApiSpex.Schema | |||
alias Pleroma.Web.ApiSpec.Schemas.ApiError | |||
import Pleroma.Web.ApiSpec.Helpers | |||
def open_api_operation(action) do | |||
operation = String.to_existing_atom("#{action}_operation") | |||
apply(__MODULE__, operation, []) | |||
end | |||
def index_operation do | |||
%Operation{ | |||
summary: "List OAuth apps", | |||
tags: ["Admin", "oAuth Apps"], | |||
operationId: "AdminAPI.OAuthAppController.index", | |||
security: [%{"oAuth" => ["write"]}], | |||
parameters: [ | |||
Operation.parameter(:name, :query, %Schema{type: :string}, "App name"), | |||
Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"), | |||
Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"), | |||
Operation.parameter( | |||
:trusted, | |||
:query, | |||
%Schema{type: :boolean, default: false}, | |||
"Trusted apps" | |||
), | |||
Operation.parameter( | |||
:page_size, | |||
:query, | |||
%Schema{type: :integer, default: 50}, | |||
"Number of apps to return" | |||
) | |||
], | |||
responses: %{ | |||
200 => | |||
Operation.response("List of apps", "application/json", %Schema{ | |||
type: :object, | |||
properties: %{ | |||
apps: %Schema{type: :array, items: oauth_app()}, | |||
count: %Schema{type: :integer}, | |||
page_size: %Schema{type: :integer} | |||
}, | |||
example: %{ | |||
"apps" => [ | |||
%{ | |||
"id" => 1, | |||
"name" => "App name", | |||
"client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", | |||
"client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", | |||
"redirect_uri" => "https://example.com/oauth-callback", | |||
"website" => "https://example.com", | |||
"trusted" => true | |||
} | |||
], | |||
"count" => 1, | |||
"page_size" => 50 | |||
} | |||
}) | |||
} | |||
} | |||
end | |||
def create_operation do | |||
%Operation{ | |||
tags: ["Admin", "oAuth Apps"], | |||
summary: "Create OAuth App", | |||
operationId: "AdminAPI.OAuthAppController.create", | |||
requestBody: request_body("Parameters", create_request()), | |||
security: [%{"oAuth" => ["write"]}], | |||
responses: %{ | |||
200 => Operation.response("App", "application/json", oauth_app()), | |||
400 => Operation.response("Bad Request", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def update_operation do | |||
%Operation{ | |||
tags: ["Admin", "oAuth Apps"], | |||
summary: "Update OAuth App", | |||
operationId: "AdminAPI.OAuthAppController.update", | |||
parameters: [id_param()], | |||
security: [%{"oAuth" => ["write"]}], | |||
requestBody: request_body("Parameters", update_request()), | |||
responses: %{ | |||
200 => Operation.response("App", "application/json", oauth_app()), | |||
400 => | |||
Operation.response("Bad Request", "application/json", %Schema{ | |||
oneOf: [ApiError, %Schema{type: :string}] | |||
}) | |||
} | |||
} | |||
end | |||
def delete_operation do | |||
%Operation{ | |||
tags: ["Admin", "oAuth Apps"], | |||
summary: "Delete OAuth App", | |||
operationId: "AdminAPI.OAuthAppController.delete", | |||
parameters: [id_param()], | |||
security: [%{"oAuth" => ["write"]}], | |||
responses: %{ | |||
204 => no_content_response(), | |||
400 => no_content_response() | |||
} | |||
} | |||
end | |||
defp create_request do | |||
%Schema{ | |||
title: "oAuthAppCreateRequest", | |||
type: :object, | |||
required: [:name, :redirect_uris], | |||
properties: %{ | |||
name: %Schema{type: :string, description: "Application Name"}, | |||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, | |||
redirect_uris: %Schema{ | |||
type: :string, | |||
description: | |||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." | |||
}, | |||
website: %Schema{ | |||
type: :string, | |||
nullable: true, | |||
description: "A URL to the homepage of the app" | |||
}, | |||
trusted: %Schema{ | |||
type: :boolean, | |||
nullable: true, | |||
default: false, | |||
description: "Is the app trusted?" | |||
} | |||
}, | |||
example: %{ | |||
"name" => "My App", | |||
"redirect_uris" => "https://myapp.com/auth/callback", | |||
"website" => "https://myapp.com/", | |||
"scopes" => ["read", "write"], | |||
"trusted" => true | |||
} | |||
} | |||
end | |||
defp update_request do | |||
%Schema{ | |||
title: "oAuthAppUpdateRequest", | |||
type: :object, | |||
properties: %{ | |||
name: %Schema{type: :string, description: "Application Name"}, | |||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, | |||
redirect_uris: %Schema{ | |||
type: :string, | |||
description: | |||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." | |||
}, | |||
website: %Schema{ | |||
type: :string, | |||
nullable: true, | |||
description: "A URL to the homepage of the app" | |||
}, | |||
trusted: %Schema{ | |||
type: :boolean, | |||
nullable: true, | |||
default: false, | |||
description: "Is the app trusted?" | |||
} | |||
}, | |||
example: %{ | |||
"name" => "My App", | |||
"redirect_uris" => "https://myapp.com/auth/callback", | |||
"website" => "https://myapp.com/", | |||
"scopes" => ["read", "write"], | |||
"trusted" => true | |||
} | |||
} | |||
end | |||
defp oauth_app do | |||
%Schema{ | |||
title: "oAuthApp", | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{type: :integer}, | |||
name: %Schema{type: :string}, | |||
client_id: %Schema{type: :string}, | |||
client_secret: %Schema{type: :string}, | |||
redirect_uri: %Schema{type: :string}, | |||
website: %Schema{type: :string, nullable: true}, | |||
trusted: %Schema{type: :boolean} | |||
}, | |||
example: %{ | |||
"id" => 123, | |||
"name" => "My App", | |||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM", | |||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw", | |||
"redirect_uri" => "https://myapp.com/oauth-callback", | |||
"website" => "https://myapp.com/", | |||
"trusted" => false | |||
} | |||
} | |||
end | |||
def id_param do | |||
Operation.parameter(:id, :path, :integer, "App ID", | |||
example: 1337, | |||
required: true | |||
) | |||
end | |||
end |
@@ -0,0 +1,237 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do | |||
alias OpenApiSpex.Operation | |||
alias OpenApiSpex.Schema | |||
alias Pleroma.Web.ApiSpec.Schemas.Account | |||
alias Pleroma.Web.ApiSpec.Schemas.ApiError | |||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID | |||
alias Pleroma.Web.ApiSpec.Schemas.Status | |||
import Pleroma.Web.ApiSpec.Helpers | |||
def open_api_operation(action) do | |||
operation = String.to_existing_atom("#{action}_operation") | |||
apply(__MODULE__, operation, []) | |||
end | |||
def index_operation do | |||
%Operation{ | |||
tags: ["Admin", "Reports"], | |||
summary: "Get a list of reports", | |||
operationId: "AdminAPI.ReportController.index", | |||
security: [%{"oAuth" => ["read:reports"]}], | |||
parameters: [ | |||
Operation.parameter( | |||
:state, | |||
:query, | |||
report_state(), | |||
"Filter by report state" | |||
), | |||
Operation.parameter( | |||
:limit, | |||
:query, | |||
%Schema{type: :integer}, | |||
"The number of records to retrieve" | |||
), | |||
Operation.parameter( | |||
:page, | |||
:query, | |||
%Schema{type: :integer, default: 1}, | |||
"Page number" | |||
), | |||
Operation.parameter( | |||
:page_size, | |||
:query, | |||
%Schema{type: :integer, default: 50}, | |||
"Number number of log entries per page" | |||
) | |||
], | |||
responses: %{ | |||
200 => | |||
Operation.response("Response", "application/json", %Schema{ | |||
type: :object, | |||
properties: %{ | |||
total: %Schema{type: :integer}, | |||
reports: %Schema{ | |||
type: :array, | |||
items: report() | |||
} | |||
} | |||
}), | |||
403 => Operation.response("Forbidden", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def show_operation do | |||
%Operation{ | |||
tags: ["Admin", "Reports"], | |||
summary: "Get an individual report", | |||
operationId: "AdminAPI.ReportController.show", | |||
parameters: [id_param()], | |||
security: [%{"oAuth" => ["read:reports"]}], | |||
responses: %{ | |||
200 => Operation.response("Report", "application/json", report()), | |||
404 => Operation.response("Not Found", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def update_operation do | |||
%Operation{ | |||
tags: ["Admin", "Reports"], | |||
summary: "Change the state of one or multiple reports", | |||
operationId: "AdminAPI.ReportController.update", | |||
security: [%{"oAuth" => ["write:reports"]}], | |||
requestBody: request_body("Parameters", update_request(), required: true), | |||
responses: %{ | |||
204 => no_content_response(), | |||
400 => Operation.response("Bad Request", "application/json", update_400_response()), | |||
403 => Operation.response("Forbidden", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def notes_create_operation do | |||
%Operation{ | |||
tags: ["Admin", "Reports"], | |||
summary: "Create report note", | |||
operationId: "AdminAPI.ReportController.notes_create", | |||
parameters: [id_param()], | |||
requestBody: | |||
request_body("Parameters", %Schema{ | |||
type: :object, | |||
properties: %{ | |||
content: %Schema{type: :string, description: "The message"} | |||
} | |||
}), | |||
security: [%{"oAuth" => ["write:reports"]}], | |||
responses: %{ | |||
204 => no_content_response(), | |||
404 => Operation.response("Not Found", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
def notes_delete_operation do | |||
%Operation{ | |||
tags: ["Admin", "Reports"], | |||
summary: "Delete report note", | |||
operationId: "AdminAPI.ReportController.notes_delete", | |||
parameters: [ | |||
Operation.parameter(:report_id, :path, :string, "Report ID"), | |||
Operation.parameter(:id, :path, :string, "Note ID") | |||
], | |||
security: [%{"oAuth" => ["write:reports"]}], | |||
responses: %{ | |||
204 => no_content_response(), | |||
404 => Operation.response("Not Found", "application/json", ApiError) | |||
} | |||
} | |||
end | |||
defp report_state do | |||
%Schema{type: :string, enum: ["open", "closed", "resolved"]} | |||
end | |||
defp id_param do | |||
Operation.parameter(:id, :path, FlakeID, "Report ID", | |||
example: "9umDrYheeY451cQnEe", | |||
required: true | |||
) | |||
end | |||
defp report do | |||
%Schema{ | |||
type: :object, | |||
properties: %{ | |||
id: FlakeID, | |||
state: report_state(), | |||
account: account_admin(), | |||
actor: account_admin(), | |||
content: %Schema{type: :string}, | |||
created_at: %Schema{type: :string, format: :"date-time"}, | |||
statuses: %Schema{type: :array, items: Status}, | |||
notes: %Schema{ | |||
type: :array, | |||
items: %Schema{ | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{type: :integer}, | |||
user_id: FlakeID, | |||
content: %Schema{type: :string}, | |||
inserted_at: %Schema{type: :string, format: :"date-time"} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
end | |||
defp account_admin do | |||
%Schema{ | |||
title: "Account", | |||
description: "Account view for admins", | |||
type: :object, | |||
properties: | |||
Map.merge(Account.schema().properties, %{ | |||
nickname: %Schema{type: :string}, | |||
deactivated: %Schema{type: :boolean}, | |||
local: %Schema{type: :boolean}, | |||
roles: %Schema{ | |||
type: :object, | |||
properties: %{ | |||
admin: %Schema{type: :boolean}, | |||
moderator: %Schema{type: :boolean} | |||
} | |||
}, | |||
confirmation_pending: %Schema{type: :boolean} | |||
}) | |||
} | |||
end | |||
defp update_request do | |||
%Schema{ | |||
type: :object, | |||
required: [:reports], | |||
properties: %{ | |||
reports: %Schema{ | |||
type: :array, | |||
items: %Schema{ | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{allOf: [FlakeID], description: "Required, report ID"}, | |||
state: %Schema{ | |||
type: :string, | |||
description: | |||
"Required, the new state. Valid values are `open`, `closed` and `resolved`" | |||
} | |||
} | |||
}, | |||
example: %{ | |||
"reports" => [ | |||
%{"id" => "123", "state" => "closed"}, | |||
%{"id" => "1337", "state" => "resolved"} | |||
] | |||
} | |||
} | |||
} | |||
} | |||
end | |||
defp update_400_response do | |||
%Schema{ | |||
type: :array, | |||
items: %Schema{ | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{allOf: [FlakeID], description: "Report ID"}, | |||
error: %Schema{type: :string, description: "Error message"} | |||
} | |||
} | |||
} | |||
end | |||
end |
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do | |||
parameters: [id_param()], | |||
security: [%{"oAuth" => ["read:statuses"]}], | |||
responses: %{ | |||
200 => Operation.response("Status", "application/json", Status), | |||
200 => Operation.response("Status", "application/json", status()), | |||
404 => Operation.response("Not Found", "application/json", ApiError) | |||
} | |||
} | |||
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do | |||
} | |||
end | |||
defp admin_account do | |||
def admin_account do | |||
%Schema{ | |||
type: :object, | |||
properties: %{ | |||
@@ -137,7 +137,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do | |||
"background_upload_limit" => 4_000_000, | |||
"background_image" => "/static/image.png", | |||
"banner_upload_limit" => 4_000_000, | |||
"description" => "A Pleroma instance, an alternative fediverse server", | |||
"description" => "Pleroma: An efficient and flexible fediverse server", | |||
"email" => "lain@lain.com", | |||
"languages" => ["en"], | |||
"max_toot_chars" => 5000, | |||
@@ -0,0 +1,42 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.EmbedController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Activity | |||
alias Pleroma.Object | |||
alias Pleroma.User | |||
alias Pleroma.Web.ActivityPub.Visibility | |||
plug(:put_layout, :embed) | |||
def show(conn, %{"id" => id}) do | |||
with %Activity{local: true} = activity <- | |||
Activity.get_by_id_with_object(id), | |||
true <- Visibility.is_public?(activity.object) do | |||
{:ok, author} = User.get_or_fetch(activity.object.data["actor"]) | |||
conn | |||
|> delete_resp_header("x-frame-options") | |||
|> delete_resp_header("content-security-policy") | |||
|> render("show.html", | |||
activity: activity, | |||
author: User.sanitize_html(author), | |||
counts: get_counts(activity) | |||
) | |||
end | |||
end | |||
defp get_counts(%Activity{} = activity) do | |||
%Object{data: data} = Object.normalize(activity) | |||
%{ | |||
likes: Map.get(data, "like_count", 0), | |||
replies: Map.get(data, "repliesCount", 0), | |||
announces: Map.get(data, "announcement_count", 0) | |||
} | |||
end | |||
end |
@@ -139,9 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do | |||
end | |||
@doc "PATCH /api/v1/accounts/update_credentials" | |||
def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do | |||
user = original_user | |||
def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do | |||
params = | |||
params | |||
|> Enum.filter(fn {_, value} -> not is_nil(value) end) | |||
@@ -183,12 +181,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do | |||
changeset = User.update_changeset(user, user_params) | |||
with {:ok, user} <- User.update_and_set_cache(changeset) do | |||
user | |||
|> build_update_activity_params() | |||
|> ActivityPub.update() | |||
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true) | |||
else | |||
_e -> render_error(conn, :forbidden, "Invalid request") | |||
end | |||
end | |||
# Hotfix, handling will be redone with the pipeline | |||
defp build_update_activity_params(user) do | |||
object = | |||
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user) | |||
|> Map.delete("@context") | |||
%{ | |||
local: true, | |||
to: [user.follower_address], | |||
cc: [], | |||
object: object, | |||
actor: user.ap_id | |||
} | |||
end | |||
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do | |||
with true <- is_map(params), | |||
true <- Map.has_key?(params, params_field), | |||
@@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do | |||
@doc "GET /api/v1/conversations" | |||
def index(%{assigns: %{user: user}} = conn, params) do | |||
params = stringify_pagination_params(params) | |||
participations = Participation.for_user_with_last_activity_id(user, params) | |||
conn | |||
@@ -36,4 +37,20 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do | |||
render(conn, "participation.json", participation: participation, for: user) | |||
end | |||
end | |||
defp stringify_pagination_params(params) do | |||
atom_keys = | |||
Pleroma.Pagination.page_keys() | |||
|> Enum.map(&String.to_atom(&1)) | |||
str_keys = | |||
params | |||
|> Map.take(atom_keys) | |||
|> Enum.map(fn {key, value} -> {to_string(key), value} end) | |||
|> Enum.into(%{}) | |||
params | |||
|> Map.delete(atom_keys) | |||
|> Map.merge(str_keys) | |||
end | |||
end |
@@ -113,22 +113,44 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
query | |||
|> prepare_tags() | |||
|> Enum.map(fn tag -> | |||
tag = String.trim_leading(tag, "#") | |||
%{name: tag, url: tags_path <> tag} | |||
end) | |||
end | |||
defp resource_search(:v1, "hashtags", query, _options) do | |||
query | |||
|> prepare_tags() | |||
|> Enum.map(fn tag -> String.trim_leading(tag, "#") end) | |||
prepare_tags(query) | |||
end | |||
defp prepare_tags(query) do | |||
query | |||
|> String.split() | |||
|> Enum.uniq() | |||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) | |||
defp prepare_tags(query, add_joined_tag \\ true) do | |||
tags = | |||
query | |||
|> String.split(~r/[^#\w]+/u, trim: true) | |||
|> Enum.uniq_by(&String.downcase/1) | |||
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end) | |||
tags = | |||
if Enum.any?(explicit_tags) do | |||
explicit_tags | |||
else | |||
tags | |||
end | |||
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end) | |||
if Enum.empty?(explicit_tags) && add_joined_tag do | |||
tags | |||
|> Kernel.++([joined_tag(tags)]) | |||
|> Enum.uniq_by(&String.downcase/1) | |||
else | |||
tags | |||
end | |||
end | |||
defp joined_tag(tags) do | |||
tags | |||
|> Enum.map(fn tag -> String.capitalize(tag) end) | |||
|> Enum.join() | |||
end | |||
defp with_fallback(f, fallback \\ []) do | |||
@@ -111,7 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do | |||
else | |||
activities = | |||
params | |||
|> Map.put("type", ["Create", "Announce"]) | |||
|> Map.put("type", ["Create"]) | |||
|> Map.put("local_only", local_only) | |||
|> Map.put("blocking_user", user) | |||
|> Map.put("muting_user", user) | |||
@@ -182,12 +182,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
bot = user.actor_type in ["Application", "Service"] | |||
emojis = | |||
Enum.map(user.emoji, fn {shortcode, url} -> | |||
Enum.map(user.emoji, fn {shortcode, raw_url} -> | |||
url = MediaProxy.url(raw_url) | |||
%{ | |||
"shortcode" => shortcode, | |||
"url" => url, | |||
"static_url" => url, | |||
"visible_in_picker" => false | |||
shortcode: shortcode, | |||
url: url, | |||
static_url: url, | |||
visible_in_picker: false | |||
} | |||
end) | |||
@@ -25,12 +25,12 @@ defmodule Pleroma.Web.OAuth.App do | |||
timestamps() | |||
end | |||
@spec changeset(App.t(), map()) :: Ecto.Changeset.t() | |||
@spec changeset(t(), map()) :: Ecto.Changeset.t() | |||
def changeset(struct, params) do | |||
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted]) | |||
end | |||
@spec register_changeset(App.t(), map()) :: Ecto.Changeset.t() | |||
@spec register_changeset(t(), map()) :: Ecto.Changeset.t() | |||
def register_changeset(struct, params \\ %{}) do | |||
changeset = | |||
struct | |||
@@ -52,18 +52,19 @@ defmodule Pleroma.Web.OAuth.App do | |||
end | |||
end | |||
@spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
@spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} | |||
def create(params) do | |||
with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do | |||
Repo.insert(changeset) | |||
end | |||
%__MODULE__{} | |||
|> register_changeset(params) | |||
|> Repo.insert() | |||
end | |||
@spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
def update(params) do | |||
with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]), | |||
changeset <- changeset(app, params) do | |||
Repo.update(changeset) | |||
@spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} | |||
def update(id, params) do | |||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do | |||
app | |||
|> changeset(params) | |||
|> Repo.update() | |||
end | |||
end | |||
@@ -71,7 +72,7 @@ defmodule Pleroma.Web.OAuth.App do | |||
Gets app by attrs or create new with attrs. | |||
And updates the scopes if need. | |||
""" | |||
@spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
@spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()} | |||
def get_or_make(attrs, scopes) do | |||
with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do | |||
update_scopes(app, scopes) | |||
@@ -92,7 +93,7 @@ defmodule Pleroma.Web.OAuth.App do | |||
|> Repo.update() | |||
end | |||
@spec search(map()) :: {:ok, [App.t()], non_neg_integer()} | |||
@spec search(map()) :: {:ok, [t()], non_neg_integer()} | |||
def search(params) do | |||
query = from(a in __MODULE__) | |||
@@ -128,7 +129,7 @@ defmodule Pleroma.Web.OAuth.App do | |||
{:ok, Repo.all(query), count} | |||
end | |||
@spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
@spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} | |||
def destroy(id) do | |||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do | |||
Repo.delete(app) | |||
@@ -164,10 +164,10 @@ defmodule Pleroma.Web.Router do | |||
post("/relay", AdminAPIController, :relay_follow) | |||
delete("/relay", AdminAPIController, :relay_unfollow) | |||
post("/users/invite_token", AdminAPIController, :create_invite_token) | |||
get("/users/invites", AdminAPIController, :invites) | |||
post("/users/revoke_invite", AdminAPIController, :revoke_invite) | |||
post("/users/email_invite", AdminAPIController, :email_invite) | |||
post("/users/invite_token", InviteController, :create) | |||
get("/users/invites", InviteController, :index) | |||
post("/users/revoke_invite", InviteController, :revoke) | |||
post("/users/email_invite", InviteController, :email) | |||
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) | |||
patch("/users/force_password_reset", AdminAPIController, :force_password_reset) | |||
@@ -183,11 +183,11 @@ defmodule Pleroma.Web.Router do | |||
patch("/users/confirm_email", AdminAPIController, :confirm_email) | |||
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) | |||
get("/reports", AdminAPIController, :list_reports) | |||
get("/reports/:id", AdminAPIController, :report_show) | |||
patch("/reports", AdminAPIController, :reports_update) | |||
post("/reports/:id/notes", AdminAPIController, :report_notes_create) | |||
delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) | |||
get("/reports", ReportController, :index) | |||
get("/reports/:id", ReportController, :show) | |||
patch("/reports", ReportController, :update) | |||
post("/reports/:id/notes", ReportController, :notes_create) | |||
delete("/reports/:report_id/notes/:id", ReportController, :notes_delete) | |||
get("/statuses/:id", StatusController, :show) | |||
put("/statuses/:id", StatusController, :update) | |||
@@ -205,10 +205,10 @@ defmodule Pleroma.Web.Router do | |||
post("/reload_emoji", AdminAPIController, :reload_emoji) | |||
get("/stats", AdminAPIController, :stats) | |||
get("/oauth_app", AdminAPIController, :oauth_app_list) | |||
post("/oauth_app", AdminAPIController, :oauth_app_create) | |||
patch("/oauth_app/:id", AdminAPIController, :oauth_app_update) | |||
delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete) | |||
get("/oauth_app", OAuthAppController, :index) | |||
post("/oauth_app", OAuthAppController, :create) | |||
patch("/oauth_app/:id", OAuthAppController, :update) | |||
delete("/oauth_app/:id", OAuthAppController, :delete) | |||
end | |||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do | |||
@@ -664,6 +664,8 @@ defmodule Pleroma.Web.Router do | |||
post("/auth/password", MastodonAPI.AuthController, :password_reset) | |||
get("/web/*path", MastoFEController, :index) | |||
get("/embed/:id", EmbedController, :show) | |||
end | |||
scope "/proxy/", Pleroma.Web.MediaProxy do | |||
@@ -136,7 +136,7 @@ defmodule Pleroma.Web.Streamer do | |||
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), | |||
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), | |||
true <- thread_containment(item, user), | |||
false <- CommonAPI.thread_muted?(user, item) do | |||
false <- CommonAPI.thread_muted?(user, parent) do | |||
false | |||
else | |||
_ -> true | |||
@@ -0,0 +1,8 @@ | |||
<%= case @mediaType do %> | |||
<% "audio" -> %> | |||
<audio src="<%= @url %>" controls="controls"></audio> | |||
<% "video" -> %> | |||
<video src="<%= @url %>" controls="controls"></video> | |||
<% _ -> %> | |||
<img src="<%= @url %>" alt="<%= @name %>" title="<%= @name %>"> | |||
<% end %> |
@@ -0,0 +1,76 @@ | |||
<div> | |||
<div class="p-author h-card"> | |||
<a class="u-url" rel="author noopener" href="<%= @author.ap_id %>"> | |||
<div class="avatar"> | |||
<img src="<%= User.avatar_url(@author) |> MediaProxy.url %>" width="48" height="48" alt=""> | |||
</div> | |||
<span class="display-name" style="padding-left: 0.5em;"> | |||
<bdi><%= raw (@author.name |> Formatter.emojify(@author.emoji)) %></bdi> | |||
<span class="nickname"><%= full_nickname(@author) %></span> | |||
</span> | |||
</a> | |||
</div> | |||
<div class="activity-content" > | |||
<%= if status_title(@activity) != "" do %> | |||
<details <%= if open_content?() do %>open<% end %>> | |||
<summary><%= raw status_title(@activity) %></summary> | |||
<div><%= activity_content(@activity) %></div> | |||
</details> | |||
<% else %> | |||
<div><%= activity_content(@activity) %></div> | |||
<% end %> | |||
<%= for %{"name" => name, "url" => [url | _]} <- attachments(@activity) do %> | |||
<div class="attachment"> | |||
<%= if sensitive?(@activity) do %> | |||
<details class="nsfw"> | |||
<summary onClick="updateHeight()"><%= Gettext.gettext("sensitive media") %></summary> | |||
<div class="nsfw-content"> | |||
<%= render("_attachment.html", %{name: name, url: url["href"], | |||
mediaType: fetch_media_type(url)}) %> | |||
</div> | |||
</details> | |||
<% else %> | |||
<%= render("_attachment.html", %{name: name, url: url["href"], | |||
mediaType: fetch_media_type(url)}) %> | |||
<% end %> | |||
</div> | |||
<% end %> | |||
</div> | |||
<dl class="counts pull-right"> | |||
<dt><%= Gettext.gettext("replies") %></dt><dd><%= @counts.replies %></dd> | |||
<dt><%= Gettext.gettext("announces") %></dt><dd><%= @counts.announces %></dd> | |||
<dt><%= Gettext.gettext("likes") %></dt><dd><%= @counts.likes %></dd> | |||
</dl> | |||
<p class="date pull-left"> | |||
<%= link published(@activity), to: activity_url(@author, @activity) %> | |||
</p> | |||
</div> | |||
<script> | |||
function updateHeight() { | |||
window.requestAnimationFrame(function(){ | |||
var height = document.getElementsByTagName('html')[0].scrollHeight; | |||
window.parent.postMessage({ | |||
type: 'setHeightPleromaEmbed', | |||
id: window.parentId, | |||
height: height, | |||
}, '*'); | |||
}) | |||
} | |||
window.addEventListener('message', function(e){ | |||
var data = e.data || {}; | |||
if (!window.parent || data.type !== 'setHeightPleromaEmbed') { | |||
return; | |||
} | |||
window.parentId = data.id | |||
updateHeight() | |||
}); | |||
</script> |
@@ -0,0 +1,15 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" /> | |||
<title><%= Pleroma.Config.get([:instance, :name]) %></title> | |||
<meta content='noindex' name='robots'> | |||
<%= Phoenix.HTML.raw(assigns[:meta] || "") %> | |||
<link rel="stylesheet" href="/embed.css"> | |||
<base target="_parent"> | |||
</head> | |||
<body> | |||
<%= render @view_module, @view_template, assigns %> | |||
</body> | |||
</html> |
@@ -0,0 +1,74 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.EmbedView do | |||
use Pleroma.Web, :view | |||
alias Calendar.Strftime | |||
alias Pleroma.Activity | |||
alias Pleroma.Emoji.Formatter | |||
alias Pleroma.Object | |||
alias Pleroma.User | |||
alias Pleroma.Web.Gettext | |||
alias Pleroma.Web.MediaProxy | |||
alias Pleroma.Web.Metadata.Utils | |||
alias Pleroma.Web.Router.Helpers | |||
use Phoenix.HTML | |||
@media_types ["image", "audio", "video"] | |||
defp fetch_media_type(%{"mediaType" => mediaType}) do | |||
Utils.fetch_media_type(@media_types, mediaType) | |||
end | |||
defp open_content? do | |||
Pleroma.Config.get( | |||
[:frontend_configurations, :collapse_message_with_subjects], | |||
true | |||
) | |||
end | |||
defp full_nickname(user) do | |||
%{host: host} = URI.parse(user.ap_id) | |||
"@" <> user.nickname <> "@" <> host | |||
end | |||
defp status_title(%Activity{object: %Object{data: %{"name" => name}}}) when is_binary(name), | |||
do: name | |||
defp status_title(%Activity{object: %Object{data: %{"summary" => summary}}}) | |||
when is_binary(summary), | |||
do: summary | |||
defp status_title(_), do: nil | |||
defp activity_content(%Activity{object: %Object{data: %{"content" => content}}}) do | |||
content |> Pleroma.HTML.filter_tags() |> raw() | |||
end | |||
defp activity_content(_), do: nil | |||
defp activity_url(%User{local: true}, activity) do | |||
Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) | |||
end | |||
defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do | |||
data["url"] || data["external_url"] || data["id"] | |||
end | |||
defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}}) do | |||
attachments | |||
end | |||
defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do | |||
sensitive | |||
end | |||
defp published(%Activity{object: %Object{data: %{"published" => published}}}) do | |||
published | |||
|> NaiveDateTime.from_iso8601!() | |||
|> Strftime.strftime!("%B %d, %Y, %l:%M %p") | |||
end | |||
end |
@@ -12,7 +12,7 @@ | |||
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"}, | |||
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, | |||
"castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"}, | |||
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, | |||
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, | |||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, | |||
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, | |||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, | |||
@@ -50,12 +50,12 @@ | |||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, | |||
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, | |||
"gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, | |||
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, | |||
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, | |||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, | |||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, | |||
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, | |||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, | |||
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, | |||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, | |||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, | |||
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, | |||
@@ -102,7 +102,7 @@ | |||
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"}, | |||
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, | |||
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, | |||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, | |||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, | |||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, | |||
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, | |||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, | |||
@@ -112,7 +112,7 @@ | |||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, | |||
"tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"}, | |||
"ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"}, | |||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, | |||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, | |||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, | |||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"}, | |||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, | |||
@@ -3,14 +3,16 @@ msgstr "" | |||
"Project-Id-Version: PACKAGE VERSION\n" | |||
"Report-Msgid-Bugs-To: \n" | |||
"POT-Creation-Date: 2020-05-15 09:37+0000\n" | |||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | |||
"Last-Translator: Automatically generated\n" | |||
"Language-Team: none\n" | |||
"PO-Revision-Date: 2020-06-02 07:36+0000\n" | |||
"Last-Translator: Fristi <fristi@subcon.town>\n" | |||
"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/" | |||
"pleroma/nl/>\n" | |||
"Language: nl\n" | |||
"MIME-Version: 1.0\n" | |||
"Content-Type: text/plain; charset=UTF-8\n" | |||
"Content-Transfer-Encoding: 8bit\n" | |||
"X-Generator: Translate Toolkit 2.5.1\n" | |||
"Plural-Forms: nplurals=2; plural=n != 1;\n" | |||
"X-Generator: Weblate 4.0.4\n" | |||
## This file is a PO Template file. | |||
## | |||
@@ -23,142 +25,142 @@ msgstr "" | |||
## effect: edit them in PO (`.po`) files instead. | |||
## From Ecto.Changeset.cast/4 | |||
msgid "can't be blank" | |||
msgstr "" | |||
msgstr "kan niet leeg zijn" | |||
## From Ecto.Changeset.unique_constraint/3 | |||
msgid "has already been taken" | |||
msgstr "" | |||
msgstr "is al bezet" | |||
## From Ecto.Changeset.put_change/3 | |||
msgid "is invalid" | |||
msgstr "" | |||
msgstr "is ongeldig" | |||
## From Ecto.Changeset.validate_format/3 | |||
msgid "has invalid format" | |||
msgstr "" | |||
msgstr "heeft een ongeldig formaat" | |||
## From Ecto.Changeset.validate_subset/3 | |||
msgid "has an invalid entry" | |||
msgstr "" | |||
msgstr "heeft een ongeldige entry" | |||
## From Ecto.Changeset.validate_exclusion/3 | |||
msgid "is reserved" | |||
msgstr "" | |||
msgstr "is gereserveerd" | |||
## From Ecto.Changeset.validate_confirmation/3 | |||
msgid "does not match confirmation" | |||
msgstr "" | |||
msgstr "komt niet overeen met bevestiging" | |||
## From Ecto.Changeset.no_assoc_constraint/3 | |||
msgid "is still associated with this entry" | |||
msgstr "" | |||
msgstr "is nog geassocieerd met deze entry" | |||
msgid "are still associated with this entry" | |||
msgstr "" | |||
msgstr "zijn nog geassocieerd met deze entry" | |||
## From Ecto.Changeset.validate_length/3 | |||
msgid "should be %{count} character(s)" | |||
msgid_plural "should be %{count} character(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient %{count} karakter te bevatten" | |||
msgstr[1] "dient %{count} karakters te bevatten" | |||
msgid "should have %{count} item(s)" | |||
msgid_plural "should have %{count} item(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient %{count} item te bevatten" | |||
msgstr[1] "dient %{count} items te bevatten" | |||
msgid "should be at least %{count} character(s)" | |||
msgid_plural "should be at least %{count} character(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient ten minste %{count} karakter te bevatten" | |||
msgstr[1] "dient ten minste %{count} karakters te bevatten" | |||
msgid "should have at least %{count} item(s)" | |||
msgid_plural "should have at least %{count} item(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient ten minste %{count} item te bevatten" | |||
msgstr[1] "dient ten minste %{count} items te bevatten" | |||
msgid "should be at most %{count} character(s)" | |||
msgid_plural "should be at most %{count} character(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient niet meer dan %{count} karakter te bevatten" | |||
msgstr[1] "dient niet meer dan %{count} karakters te bevatten" | |||
msgid "should have at most %{count} item(s)" | |||
msgid_plural "should have at most %{count} item(s)" | |||
msgstr[0] "" | |||
msgstr[1] "" | |||
msgstr[0] "dient niet meer dan %{count} item te bevatten" | |||
msgstr[1] "dient niet meer dan %{count} items te bevatten" | |||
## From Ecto.Changeset.validate_number/3 | |||
msgid "must be less than %{number}" | |||
msgstr "" | |||
msgstr "dient kleiner te zijn dan %{number}" | |||
msgid "must be greater than %{number}" | |||
msgstr "" | |||
msgstr "dient groter te zijn dan %{number}" | |||
msgid "must be less than or equal to %{number}" | |||
msgstr "" | |||
msgstr "dient kleiner dan of gelijk te zijn aan %{number}" | |||
msgid "must be greater than or equal to %{number}" | |||
msgstr "" | |||
msgstr "dient groter dan of gelijk te zijn aan %{number}" | |||
msgid "must be equal to %{number}" | |||
msgstr "" | |||
msgstr "dient gelijk te zijn aan %{number}" | |||
#: lib/pleroma/web/common_api/common_api.ex:421 | |||
#, elixir-format | |||
msgid "Account not found" | |||
msgstr "" | |||
msgstr "Account niet gevonden" | |||
#: lib/pleroma/web/common_api/common_api.ex:249 | |||
#, elixir-format | |||
msgid "Already voted" | |||
msgstr "" | |||
msgstr "Al gestemd" | |||
#: lib/pleroma/web/oauth/oauth_controller.ex:360 | |||
#, elixir-format | |||
msgid "Bad request" | |||
msgstr "" | |||
msgstr "Bad request" | |||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 | |||
#, elixir-format | |||
msgid "Can't delete object" | |||
msgstr "" | |||
msgstr "Object kan niet verwijderd worden" | |||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196 | |||
#, elixir-format | |||
msgid "Can't delete this post" | |||
msgstr "" | |||
msgstr "Bericht kan niet verwijderd worden" | |||
#: lib/pleroma/web/controller_helper.ex:95 | |||
#: lib/pleroma/web/controller_helper.ex:101 | |||
#, elixir-format | |||
msgid "Can't display this activity" | |||
msgstr "" | |||
msgstr "Activiteit kan niet worden getoond" | |||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227 | |||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254 | |||
#, elixir-format | |||
msgid "Can't find user" | |||
msgstr "" | |||
msgstr "Gebruiker kan niet gevonden worden" | |||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 | |||
#, elixir-format | |||
msgid "Can't get favorites" | |||
msgstr "" | |||
msgstr "Favorieten konden niet opgehaald worden" | |||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 | |||
#, elixir-format | |||
msgid "Can't like object" | |||
msgstr "" | |||
msgstr "Object kan niet geliked worden" | |||
#: lib/pleroma/web/common_api/utils.ex:556 | |||
#, elixir-format | |||
msgid "Cannot post an empty status without attachments" | |||
msgstr "" | |||
msgstr "Status kan niet geplaatst worden zonder tekst of bijlagen" | |||
#: lib/pleroma/web/common_api/utils.ex:504 | |||
#, elixir-format | |||
msgid "Comment must be up to %{max_size} characters" | |||
msgstr "" | |||
msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten" | |||
#: lib/pleroma/config/config_db.ex:222 | |||
#, elixir-format | |||
@@ -0,0 +1,33 @@ | |||
defmodule Pleroma.Repo.Migrations.AddRecipientsContainBlockedDomainsFunction do | |||
use Ecto.Migration | |||
@disable_ddl_transaction true | |||
def up do | |||
statement = """ | |||
CREATE OR REPLACE FUNCTION recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[]) RETURNS boolean AS $$ | |||
DECLARE | |||
recipient_domain varchar; | |||
recipient varchar; | |||
BEGIN | |||
FOREACH recipient IN ARRAY recipients LOOP | |||
recipient_domain = split_part(recipient, '/', 3)::varchar; | |||
IF recipient_domain = ANY(blocked_domains) THEN | |||
RETURN TRUE; | |||
END IF; | |||
END LOOP; | |||
RETURN FALSE; | |||
END; | |||
$$ LANGUAGE plpgsql; | |||
""" | |||
execute(statement) | |||
end | |||
def down do | |||
execute( | |||
"drop function if exists recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[])" | |||
) | |||
end | |||
end |
@@ -0,0 +1,115 @@ | |||
body { | |||
background-color: #282c37; | |||
font-family: sans-serif; | |||
color: white; | |||
margin: 0; | |||
padding: 1em; | |||
padding-bottom: 0; | |||
} | |||
.avatar { | |||
cursor: pointer; | |||
} | |||
.avatar img { | |||
float: left; | |||
border-radius: 4px; | |||
margin-right: 4px; | |||
} | |||
.activity-content { | |||
padding-top: 1em; | |||
} | |||
.attachment { | |||
margin-top: 1em; | |||
} | |||
.attachment img { | |||
max-width: 100%; | |||
} | |||
.date a { | |||
text-decoration: none; | |||
} | |||
.date a:hover { | |||
text-decoration: underline; | |||
} | |||
.date a, | |||
.counts { | |||
color: #666; | |||
font-size: 0.9em; | |||
} | |||
.counts dt, | |||
.counts dd { | |||
float: left; | |||
margin-left: 1em; | |||
} | |||
a { | |||
color: white; | |||
} | |||
.h-card { | |||
min-height: 48px; | |||
margin-bottom: 8px; | |||
} | |||
.h-card a { | |||
text-decoration: none; | |||
} | |||
.h-card a:hover { | |||
text-decoration: underline; | |||
} | |||
.display-name { | |||
padding-top: 4px; | |||
display: block; | |||
text-overflow: ellipsis; | |||
overflow: hidden; | |||
color: white; | |||
} | |||
/* keep emoji from being hilariously huge */ | |||
.display-name img { | |||
max-height: 1em; | |||
} | |||
.display-name .nickname { | |||
padding-top: 4px; | |||
display: block; | |||
} | |||
.nickname:hover { | |||
text-decoration: none; | |||
} | |||
.pull-right { | |||
float: right; | |||
} | |||
.collapse { | |||
margin: 0; | |||
width: auto; | |||
} | |||
a.button { | |||
box-sizing: border-box; | |||
display: inline-block; | |||
color: white; | |||
background-color: #419bdd; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
font-weight: 500; | |||
font-size: 0.9em; | |||
} | |||
a.button:hover { | |||
text-decoration: none; | |||
background-color: #61a6d9; | |||
} |
@@ -0,0 +1,43 @@ | |||
(function () { | |||
'use strict' | |||
var ready = function (loaded) { | |||
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { | |||
loaded() | |||
} else { | |||
document.addEventListener('DOMContentLoaded', loaded) | |||
} | |||
} | |||
ready(function () { | |||
var iframes = [] | |||
window.addEventListener('message', function (e) { | |||
var data = e.data || {} | |||
if (data.type !== 'setHeightPleromaEmbed' || !iframes[data.id]) { | |||
return | |||
} | |||
iframes[data.id].height = data.height | |||
}); | |||
[].forEach.call(document.querySelectorAll('iframe.pleroma-embed'), function (iframe) { | |||
iframe.scrolling = 'no' | |||
iframe.style.overflow = 'hidden' | |||
iframes.push(iframe) | |||
var id = iframes.length - 1 | |||
iframe.onload = function () { | |||
iframe.contentWindow.postMessage({ | |||
type: 'setHeightPleromaEmbed', | |||
id: id | |||
}, '*') | |||
} | |||
iframe.onload() | |||
}) | |||
}) | |||
})() |
@@ -31,17 +31,5 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do | |||
assert opts[:b] == 1 | |||
refute Keyword.has_key?(opts, :proxy) | |||
end | |||
test "add opts for https" do | |||
uri = URI.parse("https://domain.com") | |||
opts = Hackney.options(uri) | |||
assert opts[:ssl_options] == [ | |||
partial_chain: &:hackney_connect.partial_chain/1, | |||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], | |||
server_name_indication: 'domain.com' | |||
] | |||
end | |||
end | |||
end |
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do | |||
[csp] = Conn.get_resp_header(conn, "content-security-policy") | |||
assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;| | |||
assert csp =~ ~r|report-uri https://endpoint.com;report-to csp-endpoint;| | |||
[reply_to] = Conn.get_resp_header(conn, "reply-to") | |||
@@ -65,7 +65,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do | |||
"type" => "Undo", | |||
"actor_id" => follower_id, | |||
"limit" => 1, | |||
"skip_preload" => true | |||
"skip_preload" => true, | |||
"invisible_actors" => true | |||
}) | |||
assert undo_activity.data["type"] == "Undo" | |||
@@ -1802,7 +1802,7 @@ defmodule Pleroma.UserTest do | |||
user = insert(:user) | |||
assert User.avatar_url(user) =~ "/images/avi.png" | |||
Pleroma.Config.put([:assets, :default_user_avatar], "avatar.png") | |||
clear_config([:assets, :default_user_avatar], "avatar.png") | |||
user = User.get_cached_by_nickname_or_id(user.nickname) | |||
assert User.avatar_url(user) =~ "avatar.png" | |||
@@ -108,6 +108,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do | |||
assert {:ok, %Activity{} = activity} = Relay.publish(note) | |||
assert activity.data["type"] == "Announce" | |||
assert activity.data["actor"] == service_actor.ap_id | |||
assert activity.data["to"] == [service_actor.follower_address] | |||
assert called(Pleroma.Web.Federator.publish(activity)) | |||
end | |||
@@ -16,10 +16,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
alias Pleroma.MFA | |||
alias Pleroma.ModerationLog | |||
alias Pleroma.Repo | |||
alias Pleroma.ReportNote | |||
alias Pleroma.Tests.ObanHelpers | |||
alias Pleroma.User | |||
alias Pleroma.UserInviteToken | |||
alias Pleroma.Web | |||
alias Pleroma.Web.ActivityPub.Relay | |||
alias Pleroma.Web.CommonAPI | |||
@@ -587,122 +585,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/email_invite, with valid config" do | |||
setup do: clear_config([:instance, :registrations_open], false) | |||
setup do: clear_config([:instance, :invites_enabled], true) | |||
test "sends invitation and returns 204", %{admin: admin, conn: conn} do | |||
recipient_email = "foo@bar.com" | |||
recipient_name = "J. D." | |||
conn = | |||
post( | |||
conn, | |||
"/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}" | |||
) | |||
assert json_response(conn, :no_content) | |||
token_record = List.last(Repo.all(Pleroma.UserInviteToken)) | |||
assert token_record | |||
refute token_record.used | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
email = | |||
Pleroma.Emails.UserEmail.user_invitation_email( | |||
admin, | |||
token_record, | |||
recipient_email, | |||
recipient_name | |||
) | |||
Swoosh.TestAssertions.assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: {recipient_name, recipient_email}, | |||
html_body: email.html_body | |||
) | |||
end | |||
test "it returns 403 if requested by a non-admin" do | |||
non_admin_user = insert(:user) | |||
token = insert(:oauth_token, user: non_admin_user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, non_admin_user) | |||
|> assign(:token, token) | |||
|> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") | |||
assert json_response(conn, :forbidden) | |||
end | |||
test "email with +", %{conn: conn, admin: admin} do | |||
recipient_email = "foo+bar@baz.com" | |||
conn | |||
|> put_req_header("content-type", "application/json;charset=utf-8") | |||
|> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email}) | |||
|> json_response(:no_content) | |||
token_record = | |||
Pleroma.UserInviteToken | |||
|> Repo.all() | |||
|> List.last() | |||
assert token_record | |||
refute token_record.used | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
email = | |||
Pleroma.Emails.UserEmail.user_invitation_email( | |||
admin, | |||
token_record, | |||
recipient_email | |||
) | |||
Swoosh.TestAssertions.assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: recipient_email, | |||
html_body: email.html_body | |||
) | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do | |||
setup do: clear_config([:instance, :registrations_open]) | |||
setup do: clear_config([:instance, :invites_enabled]) | |||
test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do | |||
Config.put([:instance, :registrations_open], false) | |||
Config.put([:instance, :invites_enabled], false) | |||
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") | |||
assert json_response(conn, :bad_request) == | |||
%{ | |||
"error" => | |||
"To send invites you need to set the `invites_enabled` option to true." | |||
} | |||
end | |||
test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do | |||
Config.put([:instance, :registrations_open], true) | |||
Config.put([:instance, :invites_enabled], true) | |||
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") | |||
assert json_response(conn, :bad_request) == | |||
%{ | |||
"error" => | |||
"To send invites you need to set the `registrations_open` option to false." | |||
} | |||
end | |||
end | |||
test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do | |||
user = insert(:user) | |||
@@ -756,8 +638,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
end | |||
test "pagination works correctly with service users", %{conn: conn} do | |||
service1 = insert(:user, ap_id: Web.base_url() <> "/relay") | |||
service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch") | |||
service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido") | |||
insert_list(25, :user) | |||
assert %{"count" => 26, "page_size" => 10, "users" => users1} = | |||
@@ -766,8 +648,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
|> json_response(200) | |||
assert Enum.count(users1) == 10 | |||
assert service1 not in [users1] | |||
assert service2 not in [users1] | |||
assert service1 not in users1 | |||
assert %{"count" => 26, "page_size" => 10, "users" => users2} = | |||
conn | |||
@@ -775,8 +656,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
|> json_response(200) | |||
assert Enum.count(users2) == 10 | |||
assert service1 not in [users2] | |||
assert service2 not in [users2] | |||
assert service1 not in users2 | |||
assert %{"count" => 26, "page_size" => 10, "users" => users3} = | |||
conn | |||
@@ -784,8 +664,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
|> json_response(200) | |||
assert Enum.count(users3) == 6 | |||
assert service1 not in [users3] | |||
assert service2 not in [users3] | |||
assert service1 not in users3 | |||
end | |||
test "renders empty array for the second page", %{conn: conn} do | |||
@@ -1317,392 +1196,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/invite_token" do | |||
test "without options", %{conn: conn} do | |||
conn = post(conn, "/api/pleroma/admin/users/invite_token") | |||
invite_json = json_response(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
refute invite.expires_at | |||
refute invite.max_use | |||
assert invite.invite_type == "one_time" | |||
end | |||
test "with expires_at", %{conn: conn} do | |||
conn = | |||
post(conn, "/api/pleroma/admin/users/invite_token", %{ | |||
"expires_at" => Date.to_string(Date.utc_today()) | |||
}) | |||
invite_json = json_response(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
assert invite.expires_at == Date.utc_today() | |||
refute invite.max_use | |||
assert invite.invite_type == "date_limited" | |||
end | |||
test "with max_use", %{conn: conn} do | |||
conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150}) | |||
invite_json = json_response(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
refute invite.expires_at | |||
assert invite.max_use == 150 | |||
assert invite.invite_type == "reusable" | |||
end | |||
test "with max use and expires_at", %{conn: conn} do | |||
conn = | |||
post(conn, "/api/pleroma/admin/users/invite_token", %{ | |||
"max_use" => 150, | |||
"expires_at" => Date.to_string(Date.utc_today()) | |||
}) | |||
invite_json = json_response(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
assert invite.expires_at == Date.utc_today() | |||
assert invite.max_use == 150 | |||
assert invite.invite_type == "reusable_date_limited" | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/users/invites" do | |||
test "no invites", %{conn: conn} do | |||
conn = get(conn, "/api/pleroma/admin/users/invites") | |||
assert json_response(conn, 200) == %{"invites" => []} | |||
end | |||
test "with invite", %{conn: conn} do | |||
{:ok, invite} = UserInviteToken.create_invite() | |||
conn = get(conn, "/api/pleroma/admin/users/invites") | |||
assert json_response(conn, 200) == %{ | |||
"invites" => [ | |||
%{ | |||
"expires_at" => nil, | |||
"id" => invite.id, | |||
"invite_type" => "one_time", | |||
"max_use" => nil, | |||
"token" => invite.token, | |||
"used" => false, | |||
"uses" => 0 | |||
} | |||
] | |||
} | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/revoke_invite" do | |||
test "with token", %{conn: conn} do | |||
{:ok, invite} = UserInviteToken.create_invite() | |||
conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) | |||
assert json_response(conn, 200) == %{ | |||
"expires_at" => nil, | |||
"id" => invite.id, | |||
"invite_type" => "one_time", | |||
"max_use" => nil, | |||
"token" => invite.token, | |||
"used" => true, | |||
"uses" => 0 | |||
} | |||
end | |||
test "with invalid token", %{conn: conn} do | |||
conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) | |||
assert json_response(conn, :not_found) == %{"error" => "Not found"} | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/reports/:id" do | |||
test "returns report by its id", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports/#{report_id}") | |||
|> json_response(:ok) | |||
assert response["id"] == report_id | |||
end | |||
test "returns 404 when report id is invalid", %{conn: conn} do | |||
conn = get(conn, "/api/pleroma/admin/reports/test") | |||
assert json_response(conn, :not_found) == %{"error" => "Not found"} | |||
end | |||
end | |||
describe "PATCH /api/pleroma/admin/reports" do | |||
setup do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
{:ok, %{id: second_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel very offended", | |||
status_ids: [activity.id] | |||
}) | |||
%{ | |||
id: report_id, | |||
second_report_id: second_report_id | |||
} | |||
end | |||
test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do | |||
read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"]) | |||
write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]) | |||
response = | |||
conn | |||
|> assign(:token, read_token) | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [%{"state" => "resolved", "id" => id}] | |||
}) | |||
|> json_response(403) | |||
assert response == %{ | |||
"error" => "Insufficient permissions: admin:write:reports." | |||
} | |||
conn | |||
|> assign(:token, write_token) | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [%{"state" => "resolved", "id" => id}] | |||
}) | |||
|> json_response(:no_content) | |||
end | |||
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do | |||
conn | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "resolved", "id" => id} | |||
] | |||
}) | |||
|> json_response(:no_content) | |||
activity = Activity.get_by_id(id) | |||
assert activity.data["state"] == "resolved" | |||
log_entry = Repo.one(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'resolved' state" | |||
end | |||
test "closes report", %{conn: conn, id: id, admin: admin} do | |||
conn | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "closed", "id" => id} | |||
] | |||
}) | |||
|> json_response(:no_content) | |||
activity = Activity.get_by_id(id) | |||
assert activity.data["state"] == "closed" | |||
log_entry = Repo.one(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'closed' state" | |||
end | |||
test "returns 400 when state is unknown", %{conn: conn, id: id} do | |||
conn = | |||
conn | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "test", "id" => id} | |||
] | |||
}) | |||
assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state" | |||
end | |||
test "returns 404 when report is not exist", %{conn: conn} do | |||
conn = | |||
conn | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "closed", "id" => "test"} | |||
] | |||
}) | |||
assert hd(json_response(conn, :bad_request))["error"] == "not_found" | |||
end | |||
test "updates state of multiple reports", %{ | |||
conn: conn, | |||
id: id, | |||
admin: admin, | |||
second_report_id: second_report_id | |||
} do | |||
conn | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "resolved", "id" => id}, | |||
%{"state" => "closed", "id" => second_report_id} | |||
] | |||
}) | |||
|> json_response(:no_content) | |||
activity = Activity.get_by_id(id) | |||
second_activity = Activity.get_by_id(second_report_id) | |||
assert activity.data["state"] == "resolved" | |||
assert second_activity.data["state"] == "closed" | |||
[first_log_entry, second_log_entry] = Repo.all(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(first_log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'resolved' state" | |||
assert ModerationLog.get_log_entry_message(second_log_entry) == | |||
"@#{admin.nickname} updated report ##{second_report_id} with 'closed' state" | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/reports" do | |||
test "returns empty response when no reports created", %{conn: conn} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports") | |||
|> json_response(:ok) | |||
assert Enum.empty?(response["reports"]) | |||
assert response["total"] == 0 | |||
end | |||
test "returns reports", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports") | |||
|> json_response(:ok) | |||
[report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert report["id"] == report_id | |||
assert response["total"] == 1 | |||
end | |||
test "returns reports with specified state", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: first_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
{:ok, %{id: second_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I don't like this user" | |||
}) | |||
CommonAPI.update_report_state(second_report_id, "closed") | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports", %{ | |||
"state" => "open" | |||
}) | |||
|> json_response(:ok) | |||
[open_report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert open_report["id"] == first_report_id | |||
assert response["total"] == 1 | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports", %{ | |||
"state" => "closed" | |||
}) | |||
|> json_response(:ok) | |||
[closed_report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert closed_report["id"] == second_report_id | |||
assert response["total"] == 1 | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports", %{ | |||
"state" => "resolved" | |||
}) | |||
|> json_response(:ok) | |||
assert Enum.empty?(response["reports"]) | |||
assert response["total"] == 0 | |||
end | |||
test "returns 403 when requested by a non-admin" do | |||
user = insert(:user) | |||
token = insert(:oauth_token, user: user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, user) | |||
|> assign(:token, token) | |||
|> get("/api/pleroma/admin/reports") | |||
assert json_response(conn, :forbidden) == | |||
%{"error" => "User is not an admin or OAuth admin scope is not granted."} | |||
end | |||
test "returns 403 when requested by anonymous" do | |||
conn = get(build_conn(), "/api/pleroma/admin/reports") | |||
assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."} | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/restart" do | |||
setup do: clear_config(:configurable_from_database, true) | |||
@@ -2251,65 +1744,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
end | |||
end | |||
describe "POST /reports/:id/notes" do | |||
setup %{conn: conn, admin: admin} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ | |||
content: "this is disgusting!" | |||
}) | |||
post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ | |||
content: "this is disgusting2!" | |||
}) | |||
%{ | |||
admin_id: admin.id, | |||
report_id: report_id | |||
} | |||
end | |||
test "it creates report note", %{admin_id: admin_id, report_id: report_id} do | |||
[note, _] = Repo.all(ReportNote) | |||
assert %{ | |||
activity_id: ^report_id, | |||
content: "this is disgusting!", | |||
user_id: ^admin_id | |||
} = note | |||
end | |||
test "it returns reports with notes", %{conn: conn, admin: admin} do | |||
conn = get(conn, "/api/pleroma/admin/reports") | |||
response = json_response(conn, 200) | |||
notes = hd(response["reports"])["notes"] | |||
[note, _] = notes | |||
assert note["user"]["nickname"] == admin.nickname | |||
assert note["content"] == "this is disgusting!" | |||
assert note["created_at"] | |||
assert response["total"] == 1 | |||
end | |||
test "it deletes the note", %{conn: conn, report_id: report_id} do | |||
assert ReportNote |> Repo.all() |> length() == 2 | |||
[note, _] = Repo.all(ReportNote) | |||
delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") | |||
assert ReportNote |> Repo.all() |> length() == 1 | |||
end | |||
end | |||
describe "/api/pleroma/admin/stats" do | |||
test "status visibility count", %{conn: conn} do | |||
@@ -2329,191 +1763,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
response["status_visibility"] | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/oauth_app" do | |||
test "errors", %{conn: conn} do | |||
response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200) | |||
assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"} | |||
end | |||
test "success", %{conn: conn} do | |||
base_url = Web.base_url() | |||
app_name = "Trusted app" | |||
response = | |||
conn | |||
|> post("/api/pleroma/admin/oauth_app", %{ | |||
name: app_name, | |||
redirect_uris: base_url | |||
}) | |||
|> json_response(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"name" => ^app_name, | |||
"redirect_uri" => ^base_url, | |||
"trusted" => false | |||
} = response | |||
end | |||
test "with trusted", %{conn: conn} do | |||
base_url = Web.base_url() | |||
app_name = "Trusted app" | |||
response = | |||
conn | |||
|> post("/api/pleroma/admin/oauth_app", %{ | |||
name: app_name, | |||
redirect_uris: base_url, | |||
trusted: true | |||
}) | |||
|> json_response(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"name" => ^app_name, | |||
"redirect_uri" => ^base_url, | |||
"trusted" => true | |||
} = response | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/oauth_app" do | |||
setup do | |||
app = insert(:oauth_app) | |||
{:ok, app: app} | |||
end | |||
test "list", %{conn: conn} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app") | |||
|> json_response(200) | |||
assert %{"apps" => apps, "count" => count, "page_size" => _} = response | |||
assert length(apps) == count | |||
end | |||
test "with page size", %{conn: conn} do | |||
insert(:oauth_app) | |||
page_size = 1 | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)}) | |||
|> json_response(200) | |||
assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response | |||
assert length(apps) == page_size | |||
end | |||
test "search by client name", %{conn: conn, app: app} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app", %{name: app.client_name}) | |||
|> json_response(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
test "search by client id", %{conn: conn, app: app} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id}) | |||
|> json_response(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
test "only trusted", %{conn: conn} do | |||
app = insert(:oauth_app, trusted: true) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app", %{trusted: true}) | |||
|> json_response(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
end | |||
describe "DELETE /api/pleroma/admin/oauth_app/:id" do | |||
test "with id", %{conn: conn} do | |||
app = insert(:oauth_app) | |||
response = | |||
conn | |||
|> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id)) | |||
|> json_response(:no_content) | |||
assert response == "" | |||
end | |||
test "with non existance id", %{conn: conn} do | |||
response = | |||
conn | |||
|> delete("/api/pleroma/admin/oauth_app/0") | |||
|> json_response(:bad_request) | |||
assert response == "" | |||
end | |||
end | |||
describe "PATCH /api/pleroma/admin/oauth_app/:id" do | |||
test "with id", %{conn: conn} do | |||
app = insert(:oauth_app) | |||
name = "another name" | |||
url = "https://example.com" | |||
scopes = ["admin"] | |||
id = app.id | |||
website = "http://website.com" | |||
response = | |||
conn | |||
|> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{ | |||
name: name, | |||
trusted: true, | |||
redirect_uris: url, | |||
scopes: scopes, | |||
website: website | |||
}) | |||
|> json_response(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"id" => ^id, | |||
"name" => ^name, | |||
"redirect_uri" => ^url, | |||
"trusted" => true, | |||
"website" => ^website | |||
} = response | |||
end | |||
test "without id", %{conn: conn} do | |||
response = | |||
conn | |||
|> patch("/api/pleroma/admin/oauth_app/0") | |||
|> json_response(:bad_request) | |||
assert response == "" | |||
end | |||
end | |||
end | |||
# Needed for testing | |||
@@ -0,0 +1,281 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.InviteControllerTest do | |||
use Pleroma.Web.ConnCase, async: true | |||
import Pleroma.Factory | |||
alias Pleroma.Config | |||
alias Pleroma.Repo | |||
alias Pleroma.UserInviteToken | |||
setup do | |||
admin = insert(:user, is_admin: true) | |||
token = insert(:oauth_admin_token, user: admin) | |||
conn = | |||
build_conn() | |||
|> assign(:user, admin) | |||
|> assign(:token, token) | |||
{:ok, %{admin: admin, token: token, conn: conn}} | |||
end | |||
describe "POST /api/pleroma/admin/users/email_invite, with valid config" do | |||
setup do: clear_config([:instance, :registrations_open], false) | |||
setup do: clear_config([:instance, :invites_enabled], true) | |||
test "sends invitation and returns 204", %{admin: admin, conn: conn} do | |||
recipient_email = "foo@bar.com" | |||
recipient_name = "J. D." | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json;charset=utf-8") | |||
|> post("/api/pleroma/admin/users/email_invite", %{ | |||
email: recipient_email, | |||
name: recipient_name | |||
}) | |||
assert json_response_and_validate_schema(conn, :no_content) | |||
token_record = List.last(Repo.all(Pleroma.UserInviteToken)) | |||
assert token_record | |||
refute token_record.used | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
email = | |||
Pleroma.Emails.UserEmail.user_invitation_email( | |||
admin, | |||
token_record, | |||
recipient_email, | |||
recipient_name | |||
) | |||
Swoosh.TestAssertions.assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: {recipient_name, recipient_email}, | |||
html_body: email.html_body | |||
) | |||
end | |||
test "it returns 403 if requested by a non-admin" do | |||
non_admin_user = insert(:user) | |||
token = insert(:oauth_token, user: non_admin_user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, non_admin_user) | |||
|> assign(:token, token) | |||
|> put_req_header("content-type", "application/json;charset=utf-8") | |||
|> post("/api/pleroma/admin/users/email_invite", %{ | |||
email: "foo@bar.com", | |||
name: "JD" | |||
}) | |||
assert json_response(conn, :forbidden) | |||
end | |||
test "email with +", %{conn: conn, admin: admin} do | |||
recipient_email = "foo+bar@baz.com" | |||
conn | |||
|> put_req_header("content-type", "application/json;charset=utf-8") | |||
|> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email}) | |||
|> json_response_and_validate_schema(:no_content) | |||
token_record = | |||
Pleroma.UserInviteToken | |||
|> Repo.all() | |||
|> List.last() | |||
assert token_record | |||
refute token_record.used | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
email = | |||
Pleroma.Emails.UserEmail.user_invitation_email( | |||
admin, | |||
token_record, | |||
recipient_email | |||
) | |||
Swoosh.TestAssertions.assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: recipient_email, | |||
html_body: email.html_body | |||
) | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do | |||
setup do: clear_config([:instance, :registrations_open]) | |||
setup do: clear_config([:instance, :invites_enabled]) | |||
test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do | |||
Config.put([:instance, :registrations_open], false) | |||
Config.put([:instance, :invites_enabled], false) | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/email_invite", %{ | |||
email: "foo@bar.com", | |||
name: "JD" | |||
}) | |||
assert json_response_and_validate_schema(conn, :bad_request) == | |||
%{ | |||
"error" => | |||
"To send invites you need to set the `invites_enabled` option to true." | |||
} | |||
end | |||
test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do | |||
Config.put([:instance, :registrations_open], true) | |||
Config.put([:instance, :invites_enabled], true) | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/email_invite", %{ | |||
email: "foo@bar.com", | |||
name: "JD" | |||
}) | |||
assert json_response_and_validate_schema(conn, :bad_request) == | |||
%{ | |||
"error" => | |||
"To send invites you need to set the `registrations_open` option to false." | |||
} | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/invite_token" do | |||
test "without options", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/invite_token") | |||
invite_json = json_response_and_validate_schema(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
refute invite.expires_at | |||
refute invite.max_use | |||
assert invite.invite_type == "one_time" | |||
end | |||
test "with expires_at", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/invite_token", %{ | |||
"expires_at" => Date.to_string(Date.utc_today()) | |||
}) | |||
invite_json = json_response_and_validate_schema(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
assert invite.expires_at == Date.utc_today() | |||
refute invite.max_use | |||
assert invite.invite_type == "date_limited" | |||
end | |||
test "with max_use", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/invite_token", %{"max_use" => 150}) | |||
invite_json = json_response_and_validate_schema(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
refute invite.expires_at | |||
assert invite.max_use == 150 | |||
assert invite.invite_type == "reusable" | |||
end | |||
test "with max use and expires_at", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/invite_token", %{ | |||
"max_use" => 150, | |||
"expires_at" => Date.to_string(Date.utc_today()) | |||
}) | |||
invite_json = json_response_and_validate_schema(conn, 200) | |||
invite = UserInviteToken.find_by_token!(invite_json["token"]) | |||
refute invite.used | |||
assert invite.expires_at == Date.utc_today() | |||
assert invite.max_use == 150 | |||
assert invite.invite_type == "reusable_date_limited" | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/users/invites" do | |||
test "no invites", %{conn: conn} do | |||
conn = get(conn, "/api/pleroma/admin/users/invites") | |||
assert json_response_and_validate_schema(conn, 200) == %{"invites" => []} | |||
end | |||
test "with invite", %{conn: conn} do | |||
{:ok, invite} = UserInviteToken.create_invite() | |||
conn = get(conn, "/api/pleroma/admin/users/invites") | |||
assert json_response_and_validate_schema(conn, 200) == %{ | |||
"invites" => [ | |||
%{ | |||
"expires_at" => nil, | |||
"id" => invite.id, | |||
"invite_type" => "one_time", | |||
"max_use" => nil, | |||
"token" => invite.token, | |||
"used" => false, | |||
"uses" => 0 | |||
} | |||
] | |||
} | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/users/revoke_invite" do | |||
test "with token", %{conn: conn} do | |||
{:ok, invite} = UserInviteToken.create_invite() | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) | |||
assert json_response_and_validate_schema(conn, 200) == %{ | |||
"expires_at" => nil, | |||
"id" => invite.id, | |||
"invite_type" => "one_time", | |||
"max_use" => nil, | |||
"token" => invite.token, | |||
"used" => true, | |||
"uses" => 0 | |||
} | |||
end | |||
test "with invalid token", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) | |||
assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} | |||
end | |||
end | |||
end |
@@ -0,0 +1,220 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do | |||
use Pleroma.Web.ConnCase, async: true | |||
use Oban.Testing, repo: Pleroma.Repo | |||
import Pleroma.Factory | |||
alias Pleroma.Config | |||
alias Pleroma.Web | |||
setup do | |||
admin = insert(:user, is_admin: true) | |||
token = insert(:oauth_admin_token, user: admin) | |||
conn = | |||
build_conn() | |||
|> assign(:user, admin) | |||
|> assign(:token, token) | |||
{:ok, %{admin: admin, token: token, conn: conn}} | |||
end | |||
describe "POST /api/pleroma/admin/oauth_app" do | |||
test "errors", %{conn: conn} do | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/oauth_app", %{}) | |||
|> json_response_and_validate_schema(400) | |||
assert %{ | |||
"error" => "Missing field: name. Missing field: redirect_uris." | |||
} = response | |||
end | |||
test "success", %{conn: conn} do | |||
base_url = Web.base_url() | |||
app_name = "Trusted app" | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/oauth_app", %{ | |||
name: app_name, | |||
redirect_uris: base_url | |||
}) | |||
|> json_response_and_validate_schema(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"name" => ^app_name, | |||
"redirect_uri" => ^base_url, | |||
"trusted" => false | |||
} = response | |||
end | |||
test "with trusted", %{conn: conn} do | |||
base_url = Web.base_url() | |||
app_name = "Trusted app" | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/oauth_app", %{ | |||
name: app_name, | |||
redirect_uris: base_url, | |||
trusted: true | |||
}) | |||
|> json_response_and_validate_schema(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"name" => ^app_name, | |||
"redirect_uri" => ^base_url, | |||
"trusted" => true | |||
} = response | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/oauth_app" do | |||
setup do | |||
app = insert(:oauth_app) | |||
{:ok, app: app} | |||
end | |||
test "list", %{conn: conn} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app") | |||
|> json_response_and_validate_schema(200) | |||
assert %{"apps" => apps, "count" => count, "page_size" => _} = response | |||
assert length(apps) == count | |||
end | |||
test "with page size", %{conn: conn} do | |||
insert(:oauth_app) | |||
page_size = 1 | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app?page_size=#{page_size}") | |||
|> json_response_and_validate_schema(200) | |||
assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response | |||
assert length(apps) == page_size | |||
end | |||
test "search by client name", %{conn: conn, app: app} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app?name=#{app.client_name}") | |||
|> json_response_and_validate_schema(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
test "search by client id", %{conn: conn, app: app} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app?client_id=#{app.client_id}") | |||
|> json_response_and_validate_schema(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
test "only trusted", %{conn: conn} do | |||
app = insert(:oauth_app, trusted: true) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/oauth_app?trusted=true") | |||
|> json_response_and_validate_schema(200) | |||
assert %{"apps" => [returned], "count" => _, "page_size" => _} = response | |||
assert returned["client_id"] == app.client_id | |||
assert returned["name"] == app.client_name | |||
end | |||
end | |||
describe "DELETE /api/pleroma/admin/oauth_app/:id" do | |||
test "with id", %{conn: conn} do | |||
app = insert(:oauth_app) | |||
response = | |||
conn | |||
|> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id)) | |||
|> json_response_and_validate_schema(:no_content) | |||
assert response == "" | |||
end | |||
test "with non existance id", %{conn: conn} do | |||
response = | |||
conn | |||
|> delete("/api/pleroma/admin/oauth_app/0") | |||
|> json_response_and_validate_schema(:bad_request) | |||
assert response == "" | |||
end | |||
end | |||
describe "PATCH /api/pleroma/admin/oauth_app/:id" do | |||
test "with id", %{conn: conn} do | |||
app = insert(:oauth_app) | |||
name = "another name" | |||
url = "https://example.com" | |||
scopes = ["admin"] | |||
id = app.id | |||
website = "http://website.com" | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/oauth_app/#{id}", %{ | |||
name: name, | |||
trusted: true, | |||
redirect_uris: url, | |||
scopes: scopes, | |||
website: website | |||
}) | |||
|> json_response_and_validate_schema(200) | |||
assert %{ | |||
"client_id" => _, | |||
"client_secret" => _, | |||
"id" => ^id, | |||
"name" => ^name, | |||
"redirect_uri" => ^url, | |||
"trusted" => true, | |||
"website" => ^website | |||
} = response | |||
end | |||
test "without id", %{conn: conn} do | |||
response = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/oauth_app/0") | |||
|> json_response_and_validate_schema(:bad_request) | |||
assert response == "" | |||
end | |||
end | |||
end |
@@ -0,0 +1,374 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.AdminAPI.ReportControllerTest do | |||
use Pleroma.Web.ConnCase | |||
import Pleroma.Factory | |||
alias Pleroma.Activity | |||
alias Pleroma.Config | |||
alias Pleroma.ModerationLog | |||
alias Pleroma.Repo | |||
alias Pleroma.ReportNote | |||
alias Pleroma.Web.CommonAPI | |||
setup do | |||
admin = insert(:user, is_admin: true) | |||
token = insert(:oauth_admin_token, user: admin) | |||
conn = | |||
build_conn() | |||
|> assign(:user, admin) | |||
|> assign(:token, token) | |||
{:ok, %{admin: admin, token: token, conn: conn}} | |||
end | |||
describe "GET /api/pleroma/admin/reports/:id" do | |||
test "returns report by its id", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports/#{report_id}") | |||
|> json_response_and_validate_schema(:ok) | |||
assert response["id"] == report_id | |||
end | |||
test "returns 404 when report id is invalid", %{conn: conn} do | |||
conn = get(conn, "/api/pleroma/admin/reports/test") | |||
assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} | |||
end | |||
end | |||
describe "PATCH /api/pleroma/admin/reports" do | |||
setup do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
{:ok, %{id: second_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel very offended", | |||
status_ids: [activity.id] | |||
}) | |||
%{ | |||
id: report_id, | |||
second_report_id: second_report_id | |||
} | |||
end | |||
test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do | |||
read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"]) | |||
write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]) | |||
response = | |||
conn | |||
|> assign(:token, read_token) | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [%{"state" => "resolved", "id" => id}] | |||
}) | |||
|> json_response_and_validate_schema(403) | |||
assert response == %{ | |||
"error" => "Insufficient permissions: admin:write:reports." | |||
} | |||
conn | |||
|> assign(:token, write_token) | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [%{"state" => "resolved", "id" => id}] | |||
}) | |||
|> json_response_and_validate_schema(:no_content) | |||
end | |||
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "resolved", "id" => id} | |||
] | |||
}) | |||
|> json_response_and_validate_schema(:no_content) | |||
activity = Activity.get_by_id(id) | |||
assert activity.data["state"] == "resolved" | |||
log_entry = Repo.one(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'resolved' state" | |||
end | |||
test "closes report", %{conn: conn, id: id, admin: admin} do | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "closed", "id" => id} | |||
] | |||
}) | |||
|> json_response_and_validate_schema(:no_content) | |||
activity = Activity.get_by_id(id) | |||
assert activity.data["state"] == "closed" | |||
log_entry = Repo.one(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'closed' state" | |||
end | |||
test "returns 400 when state is unknown", %{conn: conn, id: id} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "test", "id" => id} | |||
] | |||
}) | |||
assert "Unsupported state" = | |||
hd(json_response_and_validate_schema(conn, :bad_request))["error"] | |||
end | |||
test "returns 404 when report is not exist", %{conn: conn} do | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "closed", "id" => "test"} | |||
] | |||
}) | |||
assert hd(json_response_and_validate_schema(conn, :bad_request))["error"] == "not_found" | |||
end | |||
test "updates state of multiple reports", %{ | |||
conn: conn, | |||
id: id, | |||
admin: admin, | |||
second_report_id: second_report_id | |||
} do | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> patch("/api/pleroma/admin/reports", %{ | |||
"reports" => [ | |||
%{"state" => "resolved", "id" => id}, | |||
%{"state" => "closed", "id" => second_report_id} | |||
] | |||
}) | |||
|> json_response_and_validate_schema(:no_content) | |||
activity = Activity.get_by_id(id) | |||
second_activity = Activity.get_by_id(second_report_id) | |||
assert activity.data["state"] == "resolved" | |||
assert second_activity.data["state"] == "closed" | |||
[first_log_entry, second_log_entry] = Repo.all(ModerationLog) | |||
assert ModerationLog.get_log_entry_message(first_log_entry) == | |||
"@#{admin.nickname} updated report ##{id} with 'resolved' state" | |||
assert ModerationLog.get_log_entry_message(second_log_entry) == | |||
"@#{admin.nickname} updated report ##{second_report_id} with 'closed' state" | |||
end | |||
end | |||
describe "GET /api/pleroma/admin/reports" do | |||
test "returns empty response when no reports created", %{conn: conn} do | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports") | |||
|> json_response_and_validate_schema(:ok) | |||
assert Enum.empty?(response["reports"]) | |||
assert response["total"] == 0 | |||
end | |||
test "returns reports", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports") | |||
|> json_response_and_validate_schema(:ok) | |||
[report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert report["id"] == report_id | |||
assert response["total"] == 1 | |||
end | |||
test "returns reports with specified state", %{conn: conn} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: first_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
{:ok, %{id: second_report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I don't like this user" | |||
}) | |||
CommonAPI.update_report_state(second_report_id, "closed") | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports?state=open") | |||
|> json_response_and_validate_schema(:ok) | |||
assert [open_report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert open_report["id"] == first_report_id | |||
assert response["total"] == 1 | |||
response = | |||
conn | |||
|> get("/api/pleroma/admin/reports?state=closed") | |||
|> json_response_and_validate_schema(:ok) | |||
assert [closed_report] = response["reports"] | |||
assert length(response["reports"]) == 1 | |||
assert closed_report["id"] == second_report_id | |||
assert response["total"] == 1 | |||
assert %{"total" => 0, "reports" => []} == | |||
conn | |||
|> get("/api/pleroma/admin/reports?state=resolved", %{ | |||
"" => "" | |||
}) | |||
|> json_response_and_validate_schema(:ok) | |||
end | |||
test "returns 403 when requested by a non-admin" do | |||
user = insert(:user) | |||
token = insert(:oauth_token, user: user) | |||
conn = | |||
build_conn() | |||
|> assign(:user, user) | |||
|> assign(:token, token) | |||
|> get("/api/pleroma/admin/reports") | |||
assert json_response(conn, :forbidden) == | |||
%{"error" => "User is not an admin or OAuth admin scope is not granted."} | |||
end | |||
test "returns 403 when requested by anonymous" do | |||
conn = get(build_conn(), "/api/pleroma/admin/reports") | |||
assert json_response(conn, :forbidden) == %{ | |||
"error" => "Invalid credentials." | |||
} | |||
end | |||
end | |||
describe "POST /api/pleroma/admin/reports/:id/notes" do | |||
setup %{conn: conn, admin: admin} do | |||
[reporter, target_user] = insert_pair(:user) | |||
activity = insert(:note_activity, user: target_user) | |||
{:ok, %{id: report_id}} = | |||
CommonAPI.report(reporter, %{ | |||
account_id: target_user.id, | |||
comment: "I feel offended", | |||
status_ids: [activity.id] | |||
}) | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ | |||
content: "this is disgusting!" | |||
}) | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ | |||
content: "this is disgusting2!" | |||
}) | |||
%{ | |||
admin_id: admin.id, | |||
report_id: report_id | |||
} | |||
end | |||
test "it creates report note", %{admin_id: admin_id, report_id: report_id} do | |||
assert [note, _] = Repo.all(ReportNote) | |||
assert %{ | |||
activity_id: ^report_id, | |||
content: "this is disgusting!", | |||
user_id: ^admin_id | |||
} = note | |||
end | |||
test "it returns reports with notes", %{conn: conn, admin: admin} do | |||
conn = get(conn, "/api/pleroma/admin/reports") | |||
response = json_response_and_validate_schema(conn, 200) | |||
notes = hd(response["reports"])["notes"] | |||
[note, _] = notes | |||
assert note["user"]["nickname"] == admin.nickname | |||
assert note["content"] == "this is disgusting!" | |||
assert note["created_at"] | |||
assert response["total"] == 1 | |||
end | |||
test "it deletes the note", %{conn: conn, report_id: report_id} do | |||
assert ReportNote |> Repo.all() |> length() == 2 | |||
assert [note, _] = Repo.all(ReportNote) | |||
delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") | |||
assert ReportNote |> Repo.all() |> length() == 1 | |||
end | |||
end | |||
end |
@@ -42,6 +42,14 @@ defmodule Pleroma.Web.AdminAPI.StatusControllerTest do | |||
|> json_response_and_validate_schema(200) | |||
assert response["id"] == activity.id | |||
account = response["account"] | |||
actor = User.get_by_ap_id(activity.actor) | |||
assert account["id"] == actor.id | |||
assert account["nickname"] == actor.nickname | |||
assert account["deactivated"] == actor.deactivated | |||
assert account["confirmation_pending"] == actor.confirmation_pending | |||
end | |||
end | |||
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do | |||
use Pleroma.Web.ConnCase | |||
import Mock | |||
import Pleroma.Factory | |||
setup do: clear_config([:instance, :max_account_fields]) | |||
@@ -52,24 +53,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do | |||
user = Repo.get(User, user_data["id"]) | |||
res_conn = | |||
conn | |||
|> assign(:user, user) | |||
|> patch("/api/v1/accounts/update_credentials", %{ | |||
"pleroma_settings_store" => %{ | |||
masto_fe: %{ | |||
theme: "blub" | |||
clear_config([:instance, :federating], true) | |||
with_mock Pleroma.Web.Federator, | |||
publish: fn _activity -> :ok end do | |||
res_conn = | |||
conn | |||
|> assign(:user, user) | |||
|> patch("/api/v1/accounts/update_credentials", %{ | |||
"pleroma_settings_store" => %{ | |||
masto_fe: %{ | |||
theme: "blub" | |||
} | |||
} | |||
} | |||
}) | |||
}) | |||
assert user_data = json_response_and_validate_schema(res_conn, 200) | |||
assert user_data = json_response_and_validate_schema(res_conn, 200) | |||
assert user_data["pleroma"]["settings_store"] == | |||
%{ | |||
"pleroma_fe" => %{"theme" => "bla"}, | |||
"masto_fe" => %{"theme" => "blub"} | |||
} | |||
assert user_data["pleroma"]["settings_store"] == | |||
%{ | |||
"pleroma_fe" => %{"theme" => "bla"}, | |||
"masto_fe" => %{"theme" => "blub"} | |||
} | |||
assert_called(Pleroma.Web.Federator.publish(:_)) | |||
end | |||
end | |||
test "updates the user's bio", %{conn: conn} do | |||
@@ -12,84 +12,88 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
setup do: oauth_access(["read:statuses"]) | |||
test "returns a list of conversations", %{user: user_one, conn: conn} do | |||
user_two = insert(:user) | |||
user_three = insert(:user) | |||
{:ok, user_two} = User.follow(user_two, user_one) | |||
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 | |||
{:ok, direct} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}, @#{user_three.nickname}!", | |||
visibility: "direct" | |||
}) | |||
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 | |||
{:ok, _follower_only} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}!", | |||
visibility: "private" | |||
}) | |||
res_conn = get(conn, "/api/v1/conversations") | |||
assert response = json_response_and_validate_schema(res_conn, 200) | |||
assert [ | |||
%{ | |||
"id" => res_id, | |||
"accounts" => res_accounts, | |||
"last_status" => res_last_status, | |||
"unread" => unread | |||
} | |||
] = response | |||
account_ids = Enum.map(res_accounts, & &1["id"]) | |||
assert length(res_accounts) == 2 | |||
assert user_two.id in account_ids | |||
assert user_three.id in account_ids | |||
assert is_binary(res_id) | |||
assert unread == false | |||
assert res_last_status["id"] == direct.id | |||
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 | |||
describe "returns a list of conversations" do | |||
setup(%{user: user_one, conn: conn}) do | |||
user_two = insert(:user) | |||
user_three = insert(:user) | |||
{:ok, user_two} = User.follow(user_two, user_one) | |||
{:ok, %{user: user_one, user_two: user_two, user_three: user_three, conn: conn}} | |||
end | |||
test "returns correct conversations", %{ | |||
user: user_one, | |||
user_two: user_two, | |||
user_three: user_three, | |||
conn: conn | |||
} do | |||
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 | |||
{:ok, direct} = create_direct_message(user_one, [user_two, user_three]) | |||
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 | |||
{:ok, _follower_only} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}!", | |||
visibility: "private" | |||
}) | |||
res_conn = get(conn, "/api/v1/conversations") | |||
assert response = json_response_and_validate_schema(res_conn, 200) | |||
assert [ | |||
%{ | |||
"id" => res_id, | |||
"accounts" => res_accounts, | |||
"last_status" => res_last_status, | |||
"unread" => unread | |||
} | |||
] = response | |||
account_ids = Enum.map(res_accounts, & &1["id"]) | |||
assert length(res_accounts) == 2 | |||
assert user_two.id in account_ids | |||
assert user_three.id in account_ids | |||
assert is_binary(res_id) | |||
assert unread == false | |||
assert res_last_status["id"] == direct.id | |||
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 | |||
end | |||
test "observes limit params", %{ | |||
user: user_one, | |||
user_two: user_two, | |||
user_three: user_three, | |||
conn: conn | |||
} do | |||
{:ok, _} = create_direct_message(user_one, [user_two, user_three]) | |||
{:ok, _} = create_direct_message(user_two, [user_one, user_three]) | |||
{:ok, _} = create_direct_message(user_three, [user_two, user_one]) | |||
res_conn = get(conn, "/api/v1/conversations?limit=1") | |||
assert response = json_response_and_validate_schema(res_conn, 200) | |||
assert Enum.count(response) == 1 | |||
res_conn = get(conn, "/api/v1/conversations?limit=2") | |||
assert response = json_response_and_validate_schema(res_conn, 200) | |||
assert Enum.count(response) == 2 | |||
end | |||
end | |||
test "filters conversations by recipients", %{user: user_one, conn: conn} do | |||
user_two = insert(:user) | |||
user_three = insert(:user) | |||
{:ok, direct1} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, _direct2} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_three.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct3} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}, @#{user_three.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, _direct4} = | |||
CommonAPI.post(user_two, %{ | |||
status: "Hi @#{user_three.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct5} = | |||
CommonAPI.post(user_two, %{ | |||
status: "Hi @#{user_one.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct1} = create_direct_message(user_one, [user_two]) | |||
{:ok, _direct2} = create_direct_message(user_one, [user_three]) | |||
{:ok, direct3} = create_direct_message(user_one, [user_two, user_three]) | |||
{:ok, _direct4} = create_direct_message(user_two, [user_three]) | |||
{:ok, direct5} = create_direct_message(user_two, [user_one]) | |||
assert [conversation1, conversation2] = | |||
conn | |||
@@ -109,12 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
test "updates the last_status on reply", %{user: user_one, conn: conn} do | |||
user_two = insert(:user) | |||
{:ok, direct} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct} = create_direct_message(user_one, [user_two]) | |||
{:ok, direct_reply} = | |||
CommonAPI.post(user_two, %{ | |||
@@ -133,12 +132,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
test "the user marks a conversation as read", %{user: user_one, conn: conn} do | |||
user_two = insert(:user) | |||
{:ok, direct} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct} = create_direct_message(user_one, [user_two]) | |||
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 | |||
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 | |||
@@ -194,15 +188,22 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do | |||
user_two = insert(:user) | |||
{:ok, direct} = | |||
CommonAPI.post(user_one, %{ | |||
status: "Hi @#{user_two.nickname}!", | |||
visibility: "direct" | |||
}) | |||
{:ok, direct} = create_direct_message(user_one, [user_two]) | |||
res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context") | |||
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) | |||
end | |||
defp create_direct_message(sender, recips) do | |||
hellos = | |||
recips | |||
|> Enum.map(fn s -> "@#{s.nickname}" end) | |||
|> Enum.join(", ") | |||
CommonAPI.post(sender, %{ | |||
status: "Hi #{hellos}!", | |||
visibility: "direct" | |||
}) | |||
end | |||
end |
@@ -71,10 +71,48 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
get(conn, "/api/v2/search?q=天子") | |||
|> json_response_and_validate_schema(200) | |||
assert results["hashtags"] == [ | |||
%{"name" => "天子", "url" => "#{Web.base_url()}/tag/天子"} | |||
] | |||
[status] = results["statuses"] | |||
assert status["id"] == to_string(activity.id) | |||
end | |||
test "constructs hashtags from search query", %{conn: conn} do | |||
results = | |||
conn | |||
|> get("/api/v2/search?#{URI.encode_query(%{q: "some text with #explicit #hashtags"})}") | |||
|> json_response_and_validate_schema(200) | |||
assert results["hashtags"] == [ | |||
%{"name" => "explicit", "url" => "#{Web.base_url()}/tag/explicit"}, | |||
%{"name" => "hashtags", "url" => "#{Web.base_url()}/tag/hashtags"} | |||
] | |||
results = | |||
conn | |||
|> get("/api/v2/search?#{URI.encode_query(%{q: "john doe JOHN DOE"})}") | |||
|> json_response_and_validate_schema(200) | |||
assert results["hashtags"] == [ | |||
%{"name" => "john", "url" => "#{Web.base_url()}/tag/john"}, | |||
%{"name" => "doe", "url" => "#{Web.base_url()}/tag/doe"}, | |||
%{"name" => "JohnDoe", "url" => "#{Web.base_url()}/tag/JohnDoe"} | |||
] | |||
results = | |||
conn | |||
|> get("/api/v2/search?#{URI.encode_query(%{q: "accident-prone"})}") | |||
|> json_response_and_validate_schema(200) | |||
assert results["hashtags"] == [ | |||
%{"name" => "accident", "url" => "#{Web.base_url()}/tag/accident"}, | |||
%{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"}, | |||
%{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"} | |||
] | |||
end | |||
test "excludes a blocked users from search results", %{conn: conn} do | |||
user = insert(:user) | |||
user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"}) | |||
@@ -179,7 +217,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
[account | _] = results["accounts"] | |||
assert account["id"] == to_string(user_three.id) | |||
assert results["hashtags"] == [] | |||
assert results["hashtags"] == ["2hu"] | |||
[status] = results["statuses"] | |||
assert status["id"] == to_string(activity.id) | |||
@@ -60,9 +60,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do | |||
describe "public" do | |||
@tag capture_log: true | |||
test "the public timeline", %{conn: conn} do | |||
following = insert(:user) | |||
user = insert(:user) | |||
{:ok, _activity} = CommonAPI.post(following, %{status: "test"}) | |||
{:ok, activity} = CommonAPI.post(user, %{status: "test"}) | |||
_activity = insert(:note_activity, local: false) | |||
@@ -77,6 +77,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do | |||
conn = get(build_conn(), "/api/v1/timelines/public?local=1") | |||
assert [%{"content" => "test"}] = json_response_and_validate_schema(conn, :ok) | |||
# does not contain repeats | |||
{:ok, _} = CommonAPI.repeat(activity.id, user) | |||
conn = get(build_conn(), "/api/v1/timelines/public?local=true") | |||
assert [_] = json_response_and_validate_schema(conn, :ok) | |||
end | |||
test "the public timeline includes only public statuses for an authenticated user" do | |||
@@ -90,6 +97,49 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do | |||
res_conn = get(conn, "/api/v1/timelines/public") | |||
assert length(json_response_and_validate_schema(res_conn, 200)) == 1 | |||
end | |||
test "doesn't return replies if follower is posting with blocked user" do | |||
%{conn: conn, user: blocker} = oauth_access(["read:statuses"]) | |||
[blockee, friend] = insert_list(2, :user) | |||
{:ok, blocker} = User.follow(blocker, friend) | |||
{:ok, _} = User.block(blocker, blockee) | |||
conn = assign(conn, :user, blocker) | |||
{:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"}) | |||
{:ok, reply_from_blockee} = | |||
CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity}) | |||
{:ok, _reply_from_friend} = | |||
CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee}) | |||
res_conn = get(conn, "/api/v1/timelines/public") | |||
[%{"id" => ^activity_id}] = json_response_and_validate_schema(res_conn, 200) | |||
end | |||
test "doesn't return replies if follow is posting with users from blocked domain" do | |||
%{conn: conn, user: blocker} = oauth_access(["read:statuses"]) | |||
friend = insert(:user) | |||
blockee = insert(:user, ap_id: "https://example.com/users/blocked") | |||
{:ok, blocker} = User.follow(blocker, friend) | |||
{:ok, blocker} = User.block_domain(blocker, "example.com") | |||
conn = assign(conn, :user, blocker) | |||
{:ok, %{id: activity_id} = activity} = CommonAPI.post(friend, %{status: "hey!"}) | |||
{:ok, reply_from_blockee} = | |||
CommonAPI.post(blockee, %{status: "heya", in_reply_to_status_id: activity}) | |||
{:ok, _reply_from_friend} = | |||
CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee}) | |||
res_conn = get(conn, "/api/v1/timelines/public") | |||
activities = json_response_and_validate_schema(res_conn, 200) | |||
[%{"id" => ^activity_id}] = activities | |||
end | |||
end | |||
defp local_and_remote_activities do | |||
@@ -54,10 +54,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||
header_static: "http://localhost:4001/images/banner.png", | |||
emojis: [ | |||
%{ | |||
"static_url" => "/file.png", | |||
"url" => "/file.png", | |||
"shortcode" => "karjalanpiirakka", | |||
"visible_in_picker" => false | |||
static_url: "/file.png", | |||
url: "/file.png", | |||
shortcode: "karjalanpiirakka", | |||
visible_in_picker: false | |||
} | |||
], | |||
fields: [], | |||
@@ -491,4 +491,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||
AccountView.render("show.json", %{user: user, for: user}) | |||
end | |||
end | |||
test "uses mediaproxy urls when it's enabled" do | |||
clear_config([:media_proxy, :enabled], true) | |||
user = | |||
insert(:user, | |||
avatar: %{"url" => [%{"href" => "https://evil.website/avatar.png"}]}, | |||
banner: %{"url" => [%{"href" => "https://evil.website/banner.png"}]}, | |||
emoji: %{"joker_smile" => "https://evil.website/society.png"} | |||
) | |||
AccountView.render("show.json", %{user: user}) | |||
|> Enum.all?(fn | |||
{key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> | |||
String.starts_with?(url, Pleroma.Web.base_url()) | |||
{:emojis, emojis} -> | |||
Enum.all?(emojis, fn %{url: url, static_url: static_url} -> | |||
String.starts_with?(url, Pleroma.Web.base_url()) && | |||
String.starts_with?(static_url, Pleroma.Web.base_url()) | |||
end) | |||
_ -> | |||
true | |||
end) | |||
|> assert() | |||
end | |||
end |
@@ -124,15 +124,7 @@ defmodule Pleroma.Web.MediaProxyTest do | |||
end | |||
test "uses the configured base_url" do | |||
base_url = Pleroma.Config.get([:media_proxy, :base_url]) | |||
if base_url do | |||
on_exit(fn -> | |||
Pleroma.Config.put([:media_proxy, :base_url], base_url) | |||
end) | |||
end | |||
Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") | |||
clear_config([:media_proxy, :base_url], "https://cache.pleroma.social") | |||
url = "https://pleroma.soykaf.com/static/logo.png" | |||
encoded = url(url) | |||
@@ -213,8 +205,8 @@ defmodule Pleroma.Web.MediaProxyTest do | |||
end | |||
test "does not change whitelisted urls" do | |||
Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"]) | |||
Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") | |||
clear_config([:media_proxy, :whitelist], ["mycdn.akamai.com"]) | |||
clear_config([:media_proxy, :base_url], "https://cache.pleroma.social") | |||
media_url = "https://mycdn.akamai.com" | |||
@@ -112,6 +112,25 @@ defmodule Pleroma.Web.StreamerTest do | |||
refute Streamer.filtered_by_user?(user, announce) | |||
end | |||
test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do | |||
Streamer.get_topic_and_add_socket("user", user) | |||
other_user = insert(:user) | |||
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) | |||
data = | |||
File.read!("test/fixtures/mastodon-announce.json") | |||
|> Poison.decode!() | |||
|> Map.put("object", activity.data["object"]) | |||
|> Map.put("actor", user.ap_id) | |||
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} = | |||
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data) | |||
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} | |||
refute Streamer.filtered_by_user?(user, announce) | |||
end | |||
test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do | |||
Streamer.get_topic_and_add_socket("user", user) | |||
Streamer.stream("user", notify) | |||