Merge remote-tracking branch 'remotes/origin/develop' into 1559-follow-request-notifications
# Conflicts: # CHANGELOG.md
This commit is contained in:
commit
6e6f95c6ae
128 changed files with 2622 additions and 880 deletions
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Mix.Pleroma do
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Mix.Task.run("app.start")
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
if Pleroma.Config.get(:env) != :test do
|
||||
|
|
|
|||
49
lib/mix/tasks/pleroma/app.ex
Normal file
49
lib/mix/tasks/pleroma/app.ex
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.App do
|
||||
@moduledoc File.read!("docs/administration/CLI_tasks/oauth_app.md")
|
||||
use Mix.Task
|
||||
|
||||
import Mix.Pleroma
|
||||
|
||||
@shortdoc "Creates trusted OAuth App"
|
||||
|
||||
def run(["create" | options]) do
|
||||
start_pleroma()
|
||||
|
||||
{opts, _} =
|
||||
OptionParser.parse!(options,
|
||||
strict: [name: :string, redirect_uri: :string, scopes: :string],
|
||||
aliases: [n: :name, r: :redirect_uri, s: :scopes]
|
||||
)
|
||||
|
||||
scopes =
|
||||
if opts[:scopes] do
|
||||
String.split(opts[:scopes], ",")
|
||||
else
|
||||
["read", "write", "follow", "push"]
|
||||
end
|
||||
|
||||
params = %{
|
||||
client_name: opts[:name],
|
||||
redirect_uris: opts[:redirect_uri],
|
||||
trusted: true,
|
||||
scopes: scopes
|
||||
}
|
||||
|
||||
with {:ok, app} <- Pleroma.Web.OAuth.App.create(params) do
|
||||
shell_info("#{app.client_name} successfully created:")
|
||||
shell_info("App client_id: " <> app.client_id)
|
||||
shell_info("App client_secret: " <> app.client_secret)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
shell_error("Creating failed:")
|
||||
|
||||
Enum.each(Pleroma.Web.OAuth.App.errors(changeset), fn {key, error} ->
|
||||
shell_error("#{key}: #{error}")
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
|
|||
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity
|
||||
as: :activity,
|
||||
skip_relationships: true
|
||||
})
|
||||
end
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,10 +54,19 @@ defmodule Pleroma.Config.TransferTask do
|
|||
[:pleroma, nil, :prometheus]
|
||||
end
|
||||
|
||||
{logger, other} =
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.map(&transform_and_merge/1)
|
||||
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
|
||||
|
||||
logger
|
||||
|> Enum.sort()
|
||||
|> Enum.each(&configure/1)
|
||||
|
||||
started_applications = Application.started_applications()
|
||||
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.map(&merge_and_update/1)
|
||||
other
|
||||
|> Enum.map(&update/1)
|
||||
|> Enum.uniq()
|
||||
|> Enum.reject(&(&1 in reject_restart))
|
||||
|> maybe_set_pleroma_last()
|
||||
|
|
@ -81,51 +90,71 @@ defmodule Pleroma.Config.TransferTask do
|
|||
end
|
||||
end
|
||||
|
||||
defp group_for_restart(:logger, key, _, merged_value) do
|
||||
# change logger configuration in runtime, without restart
|
||||
if Keyword.keyword?(merged_value) and
|
||||
key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
|
||||
Logger.configure_backend(key, merged_value)
|
||||
else
|
||||
Logger.configure([{key, merged_value}])
|
||||
end
|
||||
defp transform_and_merge(%{group: group, key: key, value: value} = setting) do
|
||||
group = ConfigDB.from_string(group)
|
||||
key = ConfigDB.from_string(key)
|
||||
value = ConfigDB.from_binary(value)
|
||||
|
||||
nil
|
||||
default = Config.Holder.default_config(group, key)
|
||||
|
||||
merged =
|
||||
cond do
|
||||
Ecto.get_meta(setting, :state) == :deleted -> default
|
||||
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
||||
true -> value
|
||||
end
|
||||
|
||||
{group, key, value, merged}
|
||||
end
|
||||
|
||||
defp group_for_restart(group, _, _, _) when group != :pleroma, do: group
|
||||
|
||||
defp group_for_restart(group, key, value, _) do
|
||||
if pleroma_need_restart?(group, key, value), do: group
|
||||
# change logger configuration in runtime, without restart
|
||||
defp configure({:quack, key, _, merged}) do
|
||||
Logger.configure_backend(Quack.Logger, [{key, merged}])
|
||||
:ok = update_env(:quack, key, merged)
|
||||
end
|
||||
|
||||
defp merge_and_update(setting) do
|
||||
defp configure({_, :backends, _, merged}) do
|
||||
# removing current backends
|
||||
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||
|
||||
Enum.each(merged, &Logger.add_backend/1)
|
||||
|
||||
:ok = update_env(:logger, :backends, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) when key in [:console, :ex_syslogger] do
|
||||
merged =
|
||||
if key == :console do
|
||||
put_in(merged[:format], merged[:format] <> "\n")
|
||||
else
|
||||
merged
|
||||
end
|
||||
|
||||
backend =
|
||||
if key == :ex_syslogger,
|
||||
do: {ExSyslogger, :ex_syslogger},
|
||||
else: key
|
||||
|
||||
Logger.configure_backend(backend, merged)
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) do
|
||||
Logger.configure([{key, merged}])
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp update({group, key, value, merged}) do
|
||||
try do
|
||||
key = ConfigDB.from_string(setting.key)
|
||||
group = ConfigDB.from_string(setting.group)
|
||||
:ok = update_env(group, key, merged)
|
||||
|
||||
default = Config.Holder.default_config(group, key)
|
||||
value = ConfigDB.from_binary(setting.value)
|
||||
|
||||
merged_value =
|
||||
cond do
|
||||
Ecto.get_meta(setting, :state) == :deleted -> default
|
||||
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
||||
true -> value
|
||||
end
|
||||
|
||||
:ok = update_env(group, key, merged_value)
|
||||
|
||||
group_for_restart(group, key, value, merged_value)
|
||||
if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
|
||||
rescue
|
||||
error ->
|
||||
error_msg =
|
||||
"updating env causes error, group: " <>
|
||||
inspect(setting.group) <>
|
||||
" key: " <>
|
||||
inspect(setting.key) <>
|
||||
" value: " <>
|
||||
inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error)
|
||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
||||
inspect(value)
|
||||
} error: #{inspect(error)}"
|
||||
|
||||
Logger.warn(error_msg)
|
||||
|
||||
|
|
@ -133,6 +162,9 @@ defmodule Pleroma.Config.TransferTask do
|
|||
end
|
||||
end
|
||||
|
||||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||
|
||||
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
||||
def pleroma_need_restart?(group, key, value) do
|
||||
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
||||
|
|
@ -150,9 +182,6 @@ defmodule Pleroma.Config.TransferTask do
|
|||
end)
|
||||
end
|
||||
|
||||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||
|
||||
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
||||
|
||||
defp restart(started_applications, app, _) do
|
||||
|
|
|
|||
|
|
@ -4,10 +4,16 @@
|
|||
|
||||
import EctoEnum
|
||||
|
||||
defenum(UserRelationshipTypeEnum,
|
||||
defenum(Pleroma.UserRelationship.Type,
|
||||
block: 1,
|
||||
mute: 2,
|
||||
reblog_mute: 3,
|
||||
notification_mute: 4,
|
||||
inverse_subscription: 5
|
||||
)
|
||||
|
||||
defenum(Pleroma.FollowingRelationship.State,
|
||||
follow_pending: 1,
|
||||
follow_accept: 2,
|
||||
follow_reject: 3
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,22 +38,14 @@ defmodule Pleroma.Emoji.Formatter do
|
|||
|
||||
def demojify(text, nil), do: text
|
||||
|
||||
@doc "Outputs a list of the emoji-shortcodes in a text"
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, %Emoji{}} ->
|
||||
String.contains?(text, ":#{emoji}:")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
@doc "Outputs a list of the emoji-Maps in a text"
|
||||
def get_emoji_map(text) when is_binary(text) do
|
||||
get_emoji(text)
|
||||
Emoji.get_all()
|
||||
|> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)
|
||||
|> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji_map(_), do: []
|
||||
def get_emoji_map(_), do: %{}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias FlakeId.Ecto.CompatType
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
schema "following_relationships" do
|
||||
field(:state, :string, default: "accept")
|
||||
field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
|
||||
|
||||
belongs_to(:follower, User, type: CompatType)
|
||||
belongs_to(:following, User, type: CompatType)
|
||||
|
|
@ -27,6 +28,18 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|> put_assoc(:follower, attrs.follower)
|
||||
|> put_assoc(:following, attrs.following)
|
||||
|> validate_required([:state, :follower, :following])
|
||||
|> unique_constraint(:follower_id,
|
||||
name: :following_relationships_follower_id_following_id_index
|
||||
)
|
||||
|> validate_not_self_relationship()
|
||||
end
|
||||
|
||||
def state_to_enum(state) when state in ["pending", "accept", "reject"] do
|
||||
String.to_existing_atom("follow_#{state}")
|
||||
end
|
||||
|
||||
def state_to_enum(state) do
|
||||
raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
|
||||
end
|
||||
|
||||
def get(%User{} = follower, %User{} = following) do
|
||||
|
|
@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
def update(follower, following, "reject"), do: unfollow(follower, following)
|
||||
def update(follower, following, :follow_reject), do: unfollow(follower, following)
|
||||
|
||||
def update(%User{} = follower, %User{} = following, state) do
|
||||
case get(follower, following) do
|
||||
|
|
@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
end
|
||||
end
|
||||
|
||||
def follow(%User{} = follower, %User{} = following, state \\ "accept") do
|
||||
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{follower: follower, following: following, state: state})
|
||||
|> Repo.insert(on_conflict: :nothing)
|
||||
|
|
@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
def get_follow_requests(%User{id: id}) do
|
||||
__MODULE__
|
||||
|> join(:inner, [r], f in assoc(r, :follower))
|
||||
|> where([r], r.state == "pending")
|
||||
|> where([r], r.state == ^:follow_pending)
|
||||
|> where([r], r.following_id == ^id)
|
||||
|> select([r, f], f)
|
||||
|> Repo.all()
|
||||
|
|
@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|
||||
def following?(%User{id: follower_id}, %User{id: followed_id}) do
|
||||
__MODULE__
|
||||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
|
||||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
|
||||
|> Repo.exists?()
|
||||
end
|
||||
|
||||
|
|
@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
__MODULE__
|
||||
|> join(:inner, [r], u in User, on: r.following_id == u.id)
|
||||
|> where([r], r.follower_id == ^user.id)
|
||||
|> where([r], r.state == "accept")
|
||||
|> where([r], r.state == ^:follow_accept)
|
||||
|> select([r, u], u.follower_address)
|
||||
|> Repo.all()
|
||||
|
||||
|
|
@ -157,4 +170,30 @@ defmodule Pleroma.FollowingRelationship do
|
|||
fr -> fr.follower_id == follower.id and fr.following_id == following.id
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_not_self_relationship(%Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_follower_id_following_id_inequality()
|
||||
|> validate_following_id_follower_id_inequality()
|
||||
end
|
||||
|
||||
defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :follower_id, fn _, follower_id ->
|
||||
if follower_id == get_field(changeset, :following_id) do
|
||||
[source_id: "can't be equal to following_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :following_id, fn _, following_id ->
|
||||
if following_id == get_field(changeset, :follower_id) do
|
||||
[target_id: "can't be equal to follower_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,13 +31,23 @@ defmodule Pleroma.Formatter do
|
|||
def mention_handler("@" <> nickname, buffer, opts, acc) do
|
||||
case User.get_cached_by_nickname(nickname) do
|
||||
%User{id: id} = user ->
|
||||
ap_id = get_ap_id(user)
|
||||
user_url = user.uri || user.ap_id
|
||||
nickname_text = get_nickname_text(nickname, opts)
|
||||
|
||||
link =
|
||||
~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
|
||||
nickname_text
|
||||
}</span></a></span>)
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:span,
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:a,
|
||||
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
||||
"data-user": id,
|
||||
class: "u-url mention",
|
||||
href: user_url,
|
||||
rel: "ugc"
|
||||
),
|
||||
class: "h-card"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|
||||
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
|
||||
|
||||
|
|
@ -49,7 +59,15 @@ defmodule Pleroma.Formatter do
|
|||
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
|
||||
tag = String.downcase(tag)
|
||||
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
|
||||
link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
|
||||
|
||||
link =
|
||||
Phoenix.HTML.Tag.content_tag(:a, tag_text,
|
||||
class: "hashtag",
|
||||
"data-tag": tag,
|
||||
href: url,
|
||||
rel: "tag ugc"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|
||||
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
|
||||
end
|
||||
|
|
@ -128,9 +146,6 @@ defmodule Pleroma.Formatter do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_ap_id(%User{source_data: %{"url" => url}}) when is_binary(url), do: url
|
||||
defp get_ap_id(%User{ap_id: ap_id}), do: ap_id
|
||||
|
||||
defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
|
||||
defp get_nickname_text(nickname, _), do: User.local_nickname(nickname)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,8 +49,10 @@ defmodule Pleroma.Gun.Conn do
|
|||
|
||||
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
|
||||
|
||||
max_connections = pool_opts[:max_connections] || 250
|
||||
|
||||
conn_pid =
|
||||
if Connections.count(name) < opts[:max_connection] do
|
||||
if Connections.count(name) < max_connections do
|
||||
do_open(uri, opts)
|
||||
else
|
||||
close_least_used_and_do_open(name, uri, opts)
|
||||
|
|
|
|||
17
lib/pleroma/plugs/auth_expected_plug.ex
Normal file
17
lib/pleroma/plugs/auth_expected_plug.ex
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.AuthExpectedPlug do
|
||||
import Plug.Conn
|
||||
|
||||
def init(options), do: options
|
||||
|
||||
def call(conn, _) do
|
||||
put_private(conn, :auth_expected, true)
|
||||
end
|
||||
|
||||
def auth_expected?(conn) do
|
||||
conn.private[:auth_expected]
|
||||
end
|
||||
end
|
||||
|
|
@ -42,13 +42,13 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
|||
else
|
||||
{:user_match, false} ->
|
||||
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
|
||||
assign(conn, :valid_signature, false)
|
||||
|
||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||
{:user, nil} ->
|
||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
|
@ -60,7 +60,7 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
|||
else
|
||||
_ ->
|
||||
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
||||
Logger.debug("key_id=#{key_id_from_conn(conn)}")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||
assign(conn, :valid_signature, false)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
|
|||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
alias Pleroma.Plugs.PlugHelper
|
||||
|
||||
use Pleroma.Web, :plug
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
def init(%{scopes: _} = options), do: options
|
||||
|
||||
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
||||
def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
||||
op = options[:op] || :|
|
||||
token = assigns[:token]
|
||||
|
||||
|
|
|
|||
38
lib/pleroma/plugs/plug_helper.ex
Normal file
38
lib/pleroma/plugs/plug_helper.ex
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.PlugHelper do
|
||||
@moduledoc "Pleroma Plug helper"
|
||||
|
||||
def append_to_called_plugs(conn, plug_module) do
|
||||
append_to_private_list(conn, :called_plugs, plug_module)
|
||||
end
|
||||
|
||||
def append_to_skipped_plugs(conn, plug_module) do
|
||||
append_to_private_list(conn, :skipped_plugs, plug_module)
|
||||
end
|
||||
|
||||
def plug_called?(conn, plug_module) do
|
||||
contained_in_private_list?(conn, :called_plugs, plug_module)
|
||||
end
|
||||
|
||||
def plug_skipped?(conn, plug_module) do
|
||||
contained_in_private_list?(conn, :skipped_plugs, plug_module)
|
||||
end
|
||||
|
||||
def plug_called_or_skipped?(conn, plug_module) do
|
||||
plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
|
||||
end
|
||||
|
||||
defp append_to_private_list(conn, private_variable, value) do
|
||||
list = conn.private[private_variable] || []
|
||||
modified_list = Enum.uniq(list ++ [value])
|
||||
Plug.Conn.put_private(conn, private_variable, modified_list)
|
||||
end
|
||||
|
||||
defp contained_in_private_list?(conn, private_variable, value) do
|
||||
list = conn.private[private_variable] || []
|
||||
value in list
|
||||
end
|
||||
end
|
||||
|
|
@ -110,20 +110,9 @@ defmodule Pleroma.Plugs.RateLimiter do
|
|||
end
|
||||
|
||||
def disabled?(conn) do
|
||||
localhost_or_socket =
|
||||
case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do
|
||||
{127, 0, 0, 1} -> true
|
||||
{0, 0, 0, 0, 0, 0, 0, 1} -> true
|
||||
{:local, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
|
||||
remote_ip_not_found =
|
||||
if Map.has_key?(conn.assigns, :remote_ip_found),
|
||||
do: !conn.assigns.remote_ip_found,
|
||||
else: false
|
||||
|
||||
localhost_or_socket and remote_ip_not_found
|
||||
if Map.has_key?(conn.assigns, :remote_ip_found),
|
||||
do: !conn.assigns.remote_ip_found,
|
||||
else: false
|
||||
end
|
||||
|
||||
@inspect_bucket_not_found {:error, :not_found}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ defmodule Pleroma.Plugs.RemoteIp do
|
|||
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||
"""
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
@headers ~w[
|
||||
|
|
@ -28,12 +26,11 @@ defmodule Pleroma.Plugs.RemoteIp do
|
|||
|
||||
def init(_), do: nil
|
||||
|
||||
def call(%{remote_ip: original_remote_ip} = conn, _) do
|
||||
def call(conn, _) do
|
||||
config = Pleroma.Config.get(__MODULE__, [])
|
||||
|
||||
if Keyword.get(config, :enabled, false) do
|
||||
%{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config))
|
||||
assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
|
||||
RemoteIp.call(conn, remote_ip_opts(config))
|
||||
else
|
||||
conn
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
conn ->
|
||||
conn
|
||||
end
|
||||
|> merge_resp_headers([{"content-security-policy", "sandbox"}])
|
||||
|
||||
config = Pleroma.Config.get(Pleroma.Upload)
|
||||
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ defmodule Pleroma.Pool.Connections do
|
|||
|
||||
@impl true
|
||||
def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
|
||||
Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
|
||||
Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
|
||||
|
||||
state =
|
||||
with {key, conn} <- find_conn(state.conns, conn_pid) do
|
||||
|
|
|
|||
31
lib/pleroma/tests/oauth_test_controller.ex
Normal file
31
lib/pleroma/tests/oauth_test_controller.ex
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# A test controller reachable only in :test env.
|
||||
# Serves to test OAuth scopes check skipping / enforcement.
|
||||
defmodule Pleroma.Tests.OAuthTestController do
|
||||
@moduledoc false
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(:skip_plug, OAuthScopesPlug when action == :skipped_oauth)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"]} when action != :missed_oauth)
|
||||
|
||||
def skipped_oauth(conn, _params) do
|
||||
noop(conn)
|
||||
end
|
||||
|
||||
def performed_oauth(conn, _params) do
|
||||
noop(conn)
|
||||
end
|
||||
|
||||
def missed_oauth(conn, _params) do
|
||||
noop(conn)
|
||||
end
|
||||
|
||||
defp noop(conn), do: json(conn, %{})
|
||||
end
|
||||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.HTML
|
||||
|
|
@ -28,6 +29,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||
|
|
@ -82,6 +84,7 @@ defmodule Pleroma.User do
|
|||
field(:password, :string, virtual: true)
|
||||
field(:password_confirmation, :string, virtual: true)
|
||||
field(:keys, :string)
|
||||
field(:public_key, :string)
|
||||
field(:ap_id, :string)
|
||||
field(:avatar, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
|
|
@ -94,7 +97,6 @@ defmodule Pleroma.User do
|
|||
field(:last_digest_emailed_at, :naive_datetime)
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
field(:source_data, :map, default: %{})
|
||||
field(:note_count, :integer, default: 0)
|
||||
field(:follower_count, :integer, default: 0)
|
||||
field(:following_count, :integer, default: 0)
|
||||
|
|
@ -112,7 +114,7 @@ defmodule Pleroma.User do
|
|||
field(:show_role, :boolean, default: true)
|
||||
field(:settings, :map, default: nil)
|
||||
field(:magic_key, :string, default: nil)
|
||||
field(:uri, :string, default: nil)
|
||||
field(:uri, Types.Uri, default: nil)
|
||||
field(:hide_followers_count, :boolean, default: false)
|
||||
field(:hide_follows_count, :boolean, default: false)
|
||||
field(:hide_followers, :boolean, default: false)
|
||||
|
|
@ -122,7 +124,7 @@ defmodule Pleroma.User do
|
|||
field(:pinned_activities, {:array, :string}, default: [])
|
||||
field(:email_notifications, :map, default: %{"digest" => false})
|
||||
field(:mascot, :map, default: nil)
|
||||
field(:emoji, {:array, :map}, default: [])
|
||||
field(:emoji, :map, default: %{})
|
||||
field(:pleroma_settings_store, :map, default: %{})
|
||||
field(:fields, {:array, :map}, default: [])
|
||||
field(:raw_fields, {:array, :map}, default: [])
|
||||
|
|
@ -132,6 +134,8 @@ defmodule Pleroma.User do
|
|||
field(:skip_thread_containment, :boolean, default: false)
|
||||
field(:actor_type, :string, default: "Person")
|
||||
field(:also_known_as, {:array, :string}, default: [])
|
||||
field(:inbox, :string)
|
||||
field(:shared_inbox, :string)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
|
|
@ -306,6 +310,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
# Should probably be renamed or removed
|
||||
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
|
||||
|
||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||
|
|
@ -339,62 +344,72 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def remote_user_creation(params) do
|
||||
defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
|
||||
|
||||
defp fix_follower_address(%{nickname: nickname} = params),
|
||||
do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
|
||||
|
||||
defp fix_follower_address(params), do: params
|
||||
|
||||
def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
name =
|
||||
case params[:name] do
|
||||
name when is_binary(name) and byte_size(name) > 0 -> name
|
||||
_ -> params[:nickname]
|
||||
end
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.put(:name, name)
|
||||
|> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
|
||||
|> truncate_if_exists(:name, name_limit)
|
||||
|> truncate_if_exists(:bio, bio_limit)
|
||||
|> truncate_fields_param()
|
||||
|> fix_follower_address()
|
||||
|
||||
changeset =
|
||||
%User{local: false}
|
||||
|> cast(
|
||||
params,
|
||||
[
|
||||
:bio,
|
||||
:name,
|
||||
:ap_id,
|
||||
:nickname,
|
||||
:avatar,
|
||||
:ap_enabled,
|
||||
:source_data,
|
||||
:banner,
|
||||
:locked,
|
||||
:magic_key,
|
||||
:uri,
|
||||
:hide_followers,
|
||||
:hide_follows,
|
||||
:hide_followers_count,
|
||||
:hide_follows_count,
|
||||
:follower_count,
|
||||
:fields,
|
||||
:following_count,
|
||||
:discoverable,
|
||||
:invisible,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
]
|
||||
)
|
||||
|> validate_required([:name, :ap_id])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> validate_fields(true)
|
||||
|
||||
case params[:source_data] do
|
||||
%{"followers" => followers, "following" => following} ->
|
||||
changeset
|
||||
|> put_change(:follower_address, followers)
|
||||
|> put_change(:following_address, following)
|
||||
|
||||
_ ->
|
||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
||||
put_change(changeset, :follower_address, followers)
|
||||
end
|
||||
struct
|
||||
|> cast(
|
||||
params,
|
||||
[
|
||||
:bio,
|
||||
:name,
|
||||
:emoji,
|
||||
:ap_id,
|
||||
:inbox,
|
||||
:shared_inbox,
|
||||
:nickname,
|
||||
:public_key,
|
||||
:avatar,
|
||||
:ap_enabled,
|
||||
:banner,
|
||||
:locked,
|
||||
:last_refreshed_at,
|
||||
:magic_key,
|
||||
:uri,
|
||||
:follower_address,
|
||||
:following_address,
|
||||
:hide_followers,
|
||||
:hide_follows,
|
||||
:hide_followers_count,
|
||||
:hide_follows_count,
|
||||
:follower_count,
|
||||
:fields,
|
||||
:following_count,
|
||||
:discoverable,
|
||||
:invisible,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
]
|
||||
)
|
||||
|> validate_required([:name, :ap_id])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> validate_fields(true)
|
||||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
|
|
@ -407,7 +422,11 @@ defmodule Pleroma.User do
|
|||
[
|
||||
:bio,
|
||||
:name,
|
||||
:emoji,
|
||||
:avatar,
|
||||
:public_key,
|
||||
:inbox,
|
||||
:shared_inbox,
|
||||
:locked,
|
||||
:no_rich_text,
|
||||
:default_scope,
|
||||
|
|
@ -434,6 +453,7 @@ defmodule Pleroma.User do
|
|||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> put_fields()
|
||||
|> put_emoji()
|
||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
||||
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
||||
|
|
@ -469,6 +489,18 @@ defmodule Pleroma.User do
|
|||
|> elem(0)
|
||||
end
|
||||
|
||||
defp put_emoji(changeset) do
|
||||
bio = get_change(changeset, :bio)
|
||||
name = get_change(changeset, :name)
|
||||
|
||||
if bio || name do
|
||||
emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
|
||||
put_change(changeset, :emoji, emoji)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp put_change_if_present(changeset, map_field, value_function) do
|
||||
if value = get_change(changeset, map_field) do
|
||||
with {:ok, new_value} <- value_function.(value) do
|
||||
|
|
@ -488,49 +520,6 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
||||
|
||||
params = if remote?, do: truncate_fields_param(params), else: params
|
||||
|
||||
struct
|
||||
|> cast(
|
||||
params,
|
||||
[
|
||||
:bio,
|
||||
:name,
|
||||
:follower_address,
|
||||
:following_address,
|
||||
:avatar,
|
||||
:last_refreshed_at,
|
||||
:ap_enabled,
|
||||
:source_data,
|
||||
:banner,
|
||||
:locked,
|
||||
:magic_key,
|
||||
:follower_count,
|
||||
:following_count,
|
||||
:hide_follows,
|
||||
:fields,
|
||||
:hide_followers,
|
||||
:allow_following_move,
|
||||
:discoverable,
|
||||
:hide_followers_count,
|
||||
:hide_follows_count,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
]
|
||||
)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> validate_fields(remote?)
|
||||
end
|
||||
|
||||
def update_as_admin_changeset(struct, params) do
|
||||
struct
|
||||
|> update_changeset(params)
|
||||
|
|
@ -606,7 +595,7 @@ defmodule Pleroma.User do
|
|||
|
||||
struct
|
||||
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|
|
@ -702,7 +691,7 @@ defmodule Pleroma.User do
|
|||
|
||||
# "Locked" (self-locked) users demand explicit authorization of follow requests
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
|
||||
follow(follower, followed, "pending")
|
||||
follow(follower, followed, :follow_pending)
|
||||
end
|
||||
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
||||
|
|
@ -722,14 +711,14 @@ defmodule Pleroma.User do
|
|||
def follow_all(follower, followeds) do
|
||||
followeds
|
||||
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|
||||
|> Enum.each(&follow(follower, &1, "accept"))
|
||||
|> Enum.each(&follow(follower, &1, :follow_accept))
|
||||
|
||||
set_cache(follower)
|
||||
end
|
||||
|
||||
defdelegate following(user), to: FollowingRelationship
|
||||
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
cond do
|
||||
|
|
@ -756,7 +745,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def unfollow(%User{} = follower, %User{} = followed) do
|
||||
case get_follow_state(follower, followed) do
|
||||
state when state in ["accept", "pending"] ->
|
||||
state when state in [:follow_pending, :follow_accept] ->
|
||||
FollowingRelationship.unfollow(follower, followed)
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
|
|
@ -774,6 +763,7 @@ defmodule Pleroma.User do
|
|||
|
||||
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||
|
||||
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
|
||||
def get_follow_state(%User{} = follower, %User{} = following) do
|
||||
following_relationship = FollowingRelationship.get(follower, following)
|
||||
get_follow_state(follower, following, following_relationship)
|
||||
|
|
@ -787,8 +777,11 @@ defmodule Pleroma.User do
|
|||
case {following_relationship, following.local} do
|
||||
{nil, false} ->
|
||||
case Utils.fetch_latest_follow(follower, following) do
|
||||
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state
|
||||
_ -> nil
|
||||
%Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
|
||||
FollowingRelationship.state_to_enum(state)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{%{state: state}, _} ->
|
||||
|
|
@ -1287,7 +1280,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def blocks?(%User{} = user, %User{} = target) do
|
||||
blocks_user?(user, target) ||
|
||||
(!User.following?(user, target) && blocks_domain?(user, target))
|
||||
(blocks_domain?(user, target) and not User.following?(user, target))
|
||||
end
|
||||
|
||||
def blocks_user?(%User{} = user, %User{} = target) do
|
||||
|
|
@ -1619,8 +1612,7 @@ defmodule Pleroma.User do
|
|||
|> set_cache()
|
||||
end
|
||||
|
||||
# AP style
|
||||
def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
|
||||
def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
|
||||
key =
|
||||
public_key_pem
|
||||
|> :public_key.pem_decode()
|
||||
|
|
@ -1630,7 +1622,7 @@ defmodule Pleroma.User do
|
|||
{:ok, key}
|
||||
end
|
||||
|
||||
def public_key(_), do: {:error, "not found key"}
|
||||
def public_key(_), do: {:error, "key not found"}
|
||||
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||
|
|
@ -1641,17 +1633,6 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
defp blank?(""), do: nil
|
||||
defp blank?(n), do: n
|
||||
|
||||
def insert_or_update_user(data) do
|
||||
data
|
||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||
|> remote_user_creation()
|
||||
|> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|
||||
|> set_cache()
|
||||
end
|
||||
|
||||
def ap_enabled?(%User{local: true}), do: true
|
||||
def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
|
||||
def ap_enabled?(_), do: false
|
||||
|
|
@ -1960,12 +1941,6 @@ defmodule Pleroma.User do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def update_source_data(user, source_data) do
|
||||
user
|
||||
|> cast(%{source_data: source_data}, [:source_data])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
||||
%{
|
||||
admin: is_admin,
|
||||
|
|
@ -1973,21 +1948,6 @@ defmodule Pleroma.User do
|
|||
}
|
||||
end
|
||||
|
||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
||||
def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
|
||||
limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
|
||||
|
||||
attachment
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|> Enum.take(limit)
|
||||
end
|
||||
|
||||
def fields(%{fields: nil}), do: []
|
||||
|
||||
def fields(%{fields: fields}), do: fields
|
||||
|
||||
def validate_fields(changeset, remote? \\ false) do
|
||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||
|
|
@ -2175,9 +2135,7 @@ defmodule Pleroma.User do
|
|||
# - display name
|
||||
def sanitize_html(%User{} = user, filter) do
|
||||
fields =
|
||||
user
|
||||
|> User.fields()
|
||||
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||
Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
|
||||
%{
|
||||
"name" => name,
|
||||
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do
|
|||
as: :relationships,
|
||||
on: r.following_id == ^id and r.follower_id == u.id
|
||||
)
|
||||
|> where([relationships: r], r.state == "accept")
|
||||
|> where([relationships: r], r.state == ^:follow_accept)
|
||||
end
|
||||
|
||||
defp compose_query({:friends, %User{id: id}}, query) do
|
||||
|
|
@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do
|
|||
as: :relationships,
|
||||
on: r.following_id == u.id and r.follower_id == ^id
|
||||
)
|
||||
|> where([relationships: r], r.state == "accept")
|
||||
|> where([relationships: r], r.state == ^:follow_accept)
|
||||
end
|
||||
|
||||
defp compose_query({:recipients_from_activity, to}, query) do
|
||||
|
|
@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do
|
|||
)
|
||||
|> where(
|
||||
[u, following: f, relationships: r],
|
||||
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
|
||||
u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
|
||||
)
|
||||
|> distinct(true)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
|
|||
schema "user_relationships" do
|
||||
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:relationship_type, UserRelationshipTypeEnum)
|
||||
field(:relationship_type, Pleroma.UserRelationship.Type)
|
||||
|
||||
timestamps(updated_at: false)
|
||||
end
|
||||
|
||||
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
||||
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
|
||||
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
|
||||
# `def create_notification_mute/2`, `def create_inverse_subscription/2`
|
||||
def unquote(:"create_#{relationship_type}")(source, target),
|
||||
|
|
@ -40,7 +41,7 @@ defmodule Pleroma.UserRelationship do
|
|||
|
||||
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
|
||||
|
||||
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
|
||||
def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
|
||||
|
||||
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
||||
user_relationship
|
||||
|
|
@ -129,17 +130,27 @@ defmodule Pleroma.UserRelationship do
|
|||
end
|
||||
|
||||
@doc ":relationships option for StatusView / AccountView / NotificationView"
|
||||
def view_relationships_option(nil = _reading_user, _actors) do
|
||||
def view_relationships_option(reading_user, actors, opts \\ [])
|
||||
|
||||
def view_relationships_option(nil = _reading_user, _actors, _opts) do
|
||||
%{user_relationships: [], following_relationships: []}
|
||||
end
|
||||
|
||||
def view_relationships_option(%User{} = reading_user, actors) do
|
||||
def view_relationships_option(%User{} = reading_user, actors, opts) do
|
||||
{source_to_target_rel_types, target_to_source_rel_types} =
|
||||
if opts[:source_mutes_only] do
|
||||
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
|
||||
{[:mute], []}
|
||||
else
|
||||
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
|
||||
end
|
||||
|
||||
user_relationships =
|
||||
UserRelationship.dictionary(
|
||||
[reading_user],
|
||||
actors,
|
||||
[:block, :mute, :notification_mute, :reblog_mute],
|
||||
[:block, :inverse_subscription]
|
||||
source_to_target_rel_types,
|
||||
target_to_source_rel_types
|
||||
)
|
||||
|
||||
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
|
||||
|
|
@ -147,16 +158,14 @@ defmodule Pleroma.UserRelationship do
|
|||
%{user_relationships: user_relationships, following_relationships: following_relationships}
|
||||
end
|
||||
|
||||
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
||||
defp validate_not_self_relationship(%Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_change(:target_id, fn _, target_id ->
|
||||
if target_id == get_field(changeset, :source_id) do
|
||||
[target_id: "can't be equal to source_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
|> validate_change(:source_id, fn _, source_id ->
|
||||
|> validate_source_id_target_id_inequality()
|
||||
|> validate_target_id_source_id_inequality()
|
||||
end
|
||||
|
||||
defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :source_id, fn _, source_id ->
|
||||
if source_id == get_field(changeset, :target_id) do
|
||||
[source_id: "can't be equal to target_id"]
|
||||
else
|
||||
|
|
@ -164,4 +173,14 @@ defmodule Pleroma.UserRelationship do
|
|||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :target_id, fn _, target_id ->
|
||||
if target_id == get_field(changeset, :source_id) do
|
||||
[target_id: "can't be equal to source_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -721,7 +721,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch_activities_for_context_query(context, opts) do
|
||||
def fetch_activities_for_context_query(context, opts) do
|
||||
public = [Constants.as_public()]
|
||||
|
||||
recipients =
|
||||
|
|
@ -1427,19 +1427,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
emojis =
|
||||
data
|
||||
|> Map.get("tag", [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||
|> Enum.reduce(%{}, fn %{"icon" => %{"url" => url}, "name" => name}, acc ->
|
||||
Map.put(acc, String.trim(name, ":"), url)
|
||||
end)
|
||||
|
||||
locked = data["manuallyApprovesFollowers"] || false
|
||||
data = Transmogrifier.maybe_fix_user_object(data)
|
||||
discoverable = data["discoverable"] || false
|
||||
invisible = data["invisible"] || false
|
||||
actor_type = data["type"] || "Person"
|
||||
|
||||
public_key =
|
||||
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||
data["publicKey"]["publicKeyPem"]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
shared_inbox =
|
||||
if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do
|
||||
data["endpoints"]["sharedInbox"]
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
user_data = %{
|
||||
ap_id: data["id"],
|
||||
uri: get_actor_url(data["url"]),
|
||||
ap_enabled: true,
|
||||
source_data: data,
|
||||
banner: banner,
|
||||
fields: fields,
|
||||
emoji: emojis,
|
||||
locked: locked,
|
||||
discoverable: discoverable,
|
||||
invisible: invisible,
|
||||
|
|
@ -1449,7 +1471,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
following_address: data["following"],
|
||||
bio: data["summary"],
|
||||
actor_type: actor_type,
|
||||
also_known_as: Map.get(data, "alsoKnownAs", [])
|
||||
also_known_as: Map.get(data, "alsoKnownAs", []),
|
||||
public_key: public_key,
|
||||
inbox: data["inbox"],
|
||||
shared_inbox: shared_inbox
|
||||
}
|
||||
|
||||
# nickname can be nil because of virtual actors
|
||||
|
|
@ -1551,11 +1576,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
def make_user_from_ap_id(ap_id) do
|
||||
if _user = User.get_cached_by_ap_id(ap_id) do
|
||||
user = User.get_cached_by_ap_id(ap_id)
|
||||
|
||||
if user && !User.ap_enabled?(user) do
|
||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||
else
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
User.insert_or_update_user(data)
|
||||
if user do
|
||||
user
|
||||
|> User.remote_user_changeset(data)
|
||||
|> User.update_and_set_cache()
|
||||
else
|
||||
data
|
||||
|> User.remote_user_changeset()
|
||||
|> Repo.insert()
|
||||
|> User.set_cache()
|
||||
end
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
|||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inRepyTo, :string)
|
||||
field(:uri, Types.Uri)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
|
|
|
|||
|
|
@ -6,28 +6,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
|
|||
def cast(object) when is_binary(object) do
|
||||
# Host has to be present and scheme has to be an http scheme (for now)
|
||||
case URI.parse(object) do
|
||||
%URI{host: nil} ->
|
||||
:error
|
||||
|
||||
%URI{scheme: scheme} when scheme in ["https", "http"] ->
|
||||
{:ok, object}
|
||||
|
||||
_ ->
|
||||
:error
|
||||
%URI{host: nil} -> :error
|
||||
%URI{host: ""} -> :error
|
||||
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def cast(%{"id" => object}), do: cast(object)
|
||||
|
||||
def cast(_) do
|
||||
:error
|
||||
end
|
||||
def cast(_), do: :error
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
def dump(data), do: {:ok, data}
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
def load(data), do: {:ok, data}
|
||||
end
|
||||
|
|
|
|||
20
lib/pleroma/web/activity_pub/object_validators/types/uri.ex
Normal file
20
lib/pleroma/web/activity_pub/object_validators/types/uri.ex
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Uri do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(uri) when is_binary(uri) do
|
||||
case URI.parse(uri) do
|
||||
%URI{host: nil} -> :error
|
||||
%URI{host: ""} -> :error
|
||||
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, uri}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def dump(data), do: {:ok, data}
|
||||
|
||||
def load(data), do: {:ok, data}
|
||||
end
|
||||
|
|
@ -141,8 +141,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|> Enum.map(& &1.ap_id)
|
||||
end
|
||||
|
||||
defp maybe_use_sharedinbox(%User{source_data: data}),
|
||||
do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
defp maybe_use_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
|
||||
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
|
||||
|
||||
@doc """
|
||||
Determine a user inbox to use based on heuristics. These heuristics
|
||||
|
|
@ -157,7 +157,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
"""
|
||||
def determine_inbox(
|
||||
%Activity{data: activity_data},
|
||||
%User{source_data: data} = user
|
||||
%User{inbox: inbox} = user
|
||||
) do
|
||||
to = activity_data["to"] || []
|
||||
cc = activity_data["cc"] || []
|
||||
|
|
@ -174,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
maybe_use_sharedinbox(user)
|
||||
|
||||
true ->
|
||||
data["inbox"]
|
||||
inbox
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -192,14 +192,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
inboxes =
|
||||
recipients
|
||||
|> Enum.filter(&User.ap_enabled?/1)
|
||||
|> Enum.map(fn %{source_data: data} -> data["inbox"] end)
|
||||
|> Enum.map(fn actor -> actor.inbox end)
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|
||||
Repo.checkout(fn ->
|
||||
Enum.each(inboxes, fn {inbox, unreachable_since} ->
|
||||
%User{ap_id: ap_id} =
|
||||
Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
|
||||
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
|
||||
|
||||
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
|
||||
# instance would only accept a first message for the first recipient and ignore the rest.
|
||||
|
|
|
|||
|
|
@ -205,16 +205,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("conversation", context)
|
||||
end
|
||||
|
||||
defp add_if_present(map, _key, nil), do: map
|
||||
|
||||
defp add_if_present(map, key, value) do
|
||||
Map.put(map, key, value)
|
||||
end
|
||||
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||
attachments =
|
||||
Enum.map(attachment, fn data ->
|
||||
media_type = data["mediaType"] || data["mimeType"]
|
||||
href = data["url"] || data["href"]
|
||||
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
|
||||
url =
|
||||
cond do
|
||||
is_list(data["url"]) -> List.first(data["url"])
|
||||
is_map(data["url"]) -> data["url"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
data
|
||||
|> Map.put("mediaType", media_type)
|
||||
|> Map.put("url", url)
|
||||
media_type =
|
||||
cond do
|
||||
is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
|
||||
is_binary(data["mediaType"]) -> data["mediaType"]
|
||||
is_binary(data["mimeType"]) -> data["mimeType"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
href =
|
||||
cond do
|
||||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
||||
is_binary(data["url"]) -> data["url"]
|
||||
is_binary(data["href"]) -> data["href"]
|
||||
end
|
||||
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> add_if_present("mediaType", media_type)
|
||||
|> add_if_present("type", Map.get(url || %{}, "type"))
|
||||
|
||||
%{"url" => [attachment_url]}
|
||||
|> add_if_present("mediaType", media_type)
|
||||
|> add_if_present("type", data["type"])
|
||||
|> add_if_present("name", data["name"])
|
||||
end)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
|
|
@ -494,7 +524,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||
{_, {:ok, _}} <-
|
||||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||
{:ok, _relationship} <-
|
||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
|
|
@ -504,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
else
|
||||
{:user_blocked, true} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
|
@ -515,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
{:follow, {:error, _}} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
|
@ -525,7 +556,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
})
|
||||
|
||||
{:user_locked, true} ->
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
|
||||
:noop
|
||||
end
|
||||
|
||||
|
|
@ -545,7 +576,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: follow_activity.data["to"],
|
||||
type: "Accept",
|
||||
|
|
@ -568,7 +599,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: follow_activity.data["to"],
|
||||
|
|
@ -680,7 +711,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
actor
|
||||
|> User.upgrade_changeset(new_user_data, true)
|
||||
|> User.remote_user_changeset(new_user_data)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{
|
||||
|
|
@ -1129,7 +1160,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def take_emoji_tags(%User{emoji: emoji}) do
|
||||
emoji
|
||||
|> Enum.flat_map(&Map.to_list/1)
|
||||
|> Map.to_list()
|
||||
|> Enum.map(&build_emoji_tag/1)
|
||||
end
|
||||
|
||||
|
|
@ -1223,12 +1254,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def upgrade_user_from_ap_id(ap_id) do
|
||||
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||
already_ap <- User.ap_enabled?(user),
|
||||
{:ok, user} <- upgrade_user(user, data) do
|
||||
if not already_ap do
|
||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||
end
|
||||
|
||||
{:ok, user} <- update_user(user, data) do
|
||||
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
|
||||
{:ok, user}
|
||||
else
|
||||
%User{} = user -> {:ok, user}
|
||||
|
|
@ -1236,9 +1263,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
defp upgrade_user(user, data) do
|
||||
defp update_user(user, data) do
|
||||
user
|
||||
|> User.upgrade_changeset(data, true)
|
||||
|> User.remote_user_changeset(data)
|
||||
|> User.update_and_set_cache()
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
emoji_tags = Transmogrifier.take_emoji_tags(user)
|
||||
|
||||
fields =
|
||||
user
|
||||
|> User.fields()
|
||||
|> Enum.map(&Map.put(&1, "type", "PropertyValue"))
|
||||
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
|
|
@ -103,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
},
|
||||
"endpoints" => endpoints,
|
||||
"attachment" => fields,
|
||||
"tag" => (user.source_data["tag"] || []) ++ emoji_tags,
|
||||
"tag" => emoji_tags,
|
||||
"discoverable" => user.discoverable
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
require Logger
|
||||
|
|
@ -258,7 +260,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
end
|
||||
|
||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||
|
|
@ -277,7 +279,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
|
|
@ -576,9 +578,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
@doc "Sends registration invite via email"
|
||||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
|
||||
with true <-
|
||||
Config.get([:instance, :invites_enabled]) &&
|
||||
!Config.get([:instance, :registrations_open]),
|
||||
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(
|
||||
|
|
@ -589,6 +590,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
),
|
||||
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
errors(
|
||||
conn,
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
)
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
errors(
|
||||
conn,
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -801,7 +814,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||
end
|
||||
|
||||
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
||||
|
|
@ -903,16 +916,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end)
|
||||
|> List.flatten()
|
||||
|
||||
response = %{configs: merged}
|
||||
|
||||
response =
|
||||
if Restarter.Pleroma.need_reboot?() do
|
||||
Map.put(response, :need_reboot, true)
|
||||
else
|
||||
response
|
||||
end
|
||||
|
||||
json(conn, response)
|
||||
json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -939,28 +943,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
|
||||
need_reboot? =
|
||||
Restarter.Pleroma.need_reboot?() ||
|
||||
Enum.any?(updated, fn config ->
|
||||
if !Restarter.Pleroma.need_reboot?() do
|
||||
changed_reboot_settings? =
|
||||
(updated ++ deleted)
|
||||
|> Enum.any?(fn config ->
|
||||
group = ConfigDB.from_string(config.group)
|
||||
key = ConfigDB.from_string(config.key)
|
||||
value = ConfigDB.from_binary(config.value)
|
||||
Config.TransferTask.pleroma_need_restart?(group, key, value)
|
||||
end)
|
||||
|
||||
response = %{configs: updated}
|
||||
|
||||
response =
|
||||
if need_reboot? do
|
||||
Restarter.Pleroma.need_reboot()
|
||||
Map.put(response, :need_reboot, need_reboot?)
|
||||
else
|
||||
response
|
||||
end
|
||||
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|> render("index.json", response)
|
||||
|> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -972,6 +970,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def need_reboot(conn, _params) do
|
||||
json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
end
|
||||
|
||||
defp configurable_from_database(conn) do
|
||||
if Config.get(:configurable_from_database) do
|
||||
:ok
|
||||
|
|
@ -1017,6 +1019,83 @@ 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
actor: merge_account_views(user),
|
||||
content: content,
|
||||
created_at: created_at,
|
||||
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
|
||||
statuses:
|
||||
StatusView.render("index.json", %{
|
||||
activities: statuses,
|
||||
as: :activity,
|
||||
skip_relationships: false
|
||||
}),
|
||||
state: report.data["state"],
|
||||
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
|
||||
}
|
||||
|
|
|
|||
44
lib/pleroma/web/api_spec.ex
Normal file
44
lib/pleroma/web/api_spec.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# 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 do
|
||||
alias OpenApiSpex.OpenApi
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
@behaviour OpenApi
|
||||
|
||||
@impl OpenApi
|
||||
def spec do
|
||||
%OpenApi{
|
||||
servers: [
|
||||
# Populate the Server info from a phoenix endpoint
|
||||
OpenApiSpex.Server.from_endpoint(Endpoint)
|
||||
],
|
||||
info: %OpenApiSpex.Info{
|
||||
title: "Pleroma",
|
||||
description: Application.spec(:pleroma, :description) |> to_string(),
|
||||
version: Application.spec(:pleroma, :vsn) |> to_string()
|
||||
},
|
||||
# populate the paths from a phoenix router
|
||||
paths: OpenApiSpex.Paths.from_router(Router),
|
||||
components: %OpenApiSpex.Components{
|
||||
securitySchemes: %{
|
||||
"oAuth" => %OpenApiSpex.SecurityScheme{
|
||||
type: "oauth2",
|
||||
flows: %OpenApiSpex.OAuthFlows{
|
||||
password: %OpenApiSpex.OAuthFlow{
|
||||
authorizationUrl: "/oauth/authorize",
|
||||
tokenUrl: "/oauth/token",
|
||||
scopes: %{"read" => "read", "write" => "write", "follow" => "follow"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# discover request/response schemas from path specs
|
||||
|> OpenApiSpex.resolve_schema_modules()
|
||||
end
|
||||
end
|
||||
27
lib/pleroma/web/api_spec/helpers.ex
Normal file
27
lib/pleroma/web/api_spec/helpers.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# 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.Helpers do
|
||||
def request_body(description, schema_ref, opts \\ []) do
|
||||
media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
|
||||
|
||||
content =
|
||||
media_types
|
||||
|> Enum.map(fn type ->
|
||||
{type,
|
||||
%OpenApiSpex.MediaType{
|
||||
schema: schema_ref,
|
||||
example: opts[:example],
|
||||
examples: opts[:examples]
|
||||
}}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
%OpenApiSpex.RequestBody{
|
||||
description: description,
|
||||
content: content,
|
||||
required: opts[:required] || false
|
||||
}
|
||||
end
|
||||
end
|
||||
96
lib/pleroma/web/api_spec/operations/app_operation.ex
Normal file
96
lib/pleroma/web/api_spec/operations/app_operation.ex
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# 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.AppOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
@spec create_operation() :: Operation.t()
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["apps"],
|
||||
summary: "Create an application",
|
||||
description: "Create a new application to obtain OAuth2 credentials",
|
||||
operationId: "AppController.create",
|
||||
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", AppCreateResponse),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unprocessable Entity",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If a required parameter is missing or improperly formatted, the request will fail.",
|
||||
properties: %{
|
||||
error: %Schema{type: :string}
|
||||
},
|
||||
example: %{
|
||||
"error" => "Validation failed: Redirect URI must be an absolute URI."
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def verify_credentials_operation do
|
||||
%Operation{
|
||||
tags: ["apps"],
|
||||
summary: "Verify your app works",
|
||||
description: "Confirm that the app's OAuth2 credentials work.",
|
||||
operationId: "AppController.verify_credentials",
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["read"]
|
||||
}
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("App", "application/json", %Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
|
||||
properties: %{
|
||||
name: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
}),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unauthorized",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
|
||||
properties: %{
|
||||
error: %Schema{type: :string}
|
||||
},
|
||||
example: %{
|
||||
"error" => "The access token is invalid."
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# 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.DomainBlockOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["domain_blocks"],
|
||||
summary: "Fetch domain blocks",
|
||||
description: "View domains the user has blocked.",
|
||||
security: [%{"oAuth" => ["follow", "read:blocks"]}],
|
||||
operationId: "DomainBlockController.index",
|
||||
responses: %{
|
||||
200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["domain_blocks"],
|
||||
summary: "Block a domain",
|
||||
description: """
|
||||
Block a domain to:
|
||||
|
||||
- hide all public posts from it
|
||||
- hide all notifications from it
|
||||
- remove all followers from it
|
||||
- prevent following new users from it (but does not remove existing follows)
|
||||
""",
|
||||
operationId: "DomainBlockController.create",
|
||||
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["domain_blocks"],
|
||||
summary: "Unblock a domain",
|
||||
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
|
||||
operationId: "DomainBlockController.delete",
|
||||
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
33
lib/pleroma/web/api_spec/schemas/app_create_request.ex
Normal file
33
lib/pleroma/web/api_spec/schemas/app_create_request.ex
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# 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.Schemas.AppCreateRequest do
|
||||
alias OpenApiSpex.Schema
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateRequest",
|
||||
description: "POST body for creating an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
client_name: %Schema{type: :string, description: "A name for your application."},
|
||||
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."
|
||||
},
|
||||
scopes: %Schema{
|
||||
type: :string,
|
||||
description: "Space separated list of scopes. If none is provided, defaults to `read`."
|
||||
},
|
||||
website: %Schema{type: :string, description: "A URL to the homepage of your app"}
|
||||
},
|
||||
required: [:client_name, :redirect_uris],
|
||||
example: %{
|
||||
"client_name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
||||
33
lib/pleroma/web/api_spec/schemas/app_create_response.ex
Normal file
33
lib/pleroma/web/api_spec/schemas/app_create_response.ex
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# 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.Schemas.AppCreateResponse do
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateResponse",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
||||
20
lib/pleroma/web/api_spec/schemas/domain_block_request.ex
Normal file
20
lib/pleroma/web/api_spec/schemas/domain_block_request.ex
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# 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.Schemas.DomainBlockRequest do
|
||||
alias OpenApiSpex.Schema
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "DomainBlockRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
domain: %Schema{type: :string}
|
||||
},
|
||||
required: [:domain],
|
||||
example: %{
|
||||
"domain" => "facebook.com"
|
||||
}
|
||||
})
|
||||
end
|
||||
16
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
Normal file
16
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# 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.Schemas.DomainBlocksResponse do
|
||||
require OpenApiSpex
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "DomainBlocksResponse",
|
||||
description: "Response schema for domain blocks",
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
example: ["google.com", "facebook.com"]
|
||||
})
|
||||
end
|
||||
|
|
@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
end
|
||||
|
||||
defp preview?(draft) do
|
||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
|
||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
|
||||
%__MODULE__{draft | preview?: preview?}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
with {:ok, follower} <- User.follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
|
|
@ -60,7 +60,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def reject_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
|
@ -332,26 +332,6 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
defp maybe_create_activity_expiration(result, _), do: result
|
||||
|
||||
# Updates the emojis for a user based on their profile
|
||||
def update(user) do
|
||||
emoji = emoji_from_profile(user)
|
||||
source_data = Map.put(user.source_data, "tag", emoji)
|
||||
|
||||
user =
|
||||
case User.update_source_data(user, source_data) do
|
||||
{:ok, user} -> user
|
||||
_ -> user
|
||||
end
|
||||
|
||||
ActivityPub.update(%{
|
||||
local: true,
|
||||
to: [Pleroma.Constants.as_public(), user.follower_address],
|
||||
cc: [],
|
||||
actor: user.ap_id,
|
||||
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
|
||||
})
|
||||
end
|
||||
|
||||
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||
with %Activity{
|
||||
actor: ^user_ap_id,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Plugs.AuthenticationPlug
|
||||
|
|
@ -18,7 +17,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
require Logger
|
||||
|
|
@ -175,7 +173,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
||||
}
|
||||
|
||||
{note, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
|
||||
{note, Map.merge(emoji, Pleroma.Emoji.Formatter.get_emoji_map(option))}
|
||||
end)
|
||||
|
||||
end_time =
|
||||
|
|
@ -431,19 +429,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def emoji_from_profile(%User{bio: bio, name: name}) do
|
||||
[bio, name]
|
||||
|> Enum.map(&Emoji.Formatter.get_emoji/1)
|
||||
|> Enum.concat()
|
||||
|> Enum.map(fn {shortcode, %Emoji{file: path}} ->
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{path}"},
|
||||
"name" => ":#{shortcode}:"
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def maybe_notify_to_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => to, "type" => _type}} = _activity
|
||||
|
|
|
|||
|
|
@ -5,10 +5,18 @@
|
|||
defmodule Pleroma.Web.ControllerHelper do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||
alias Pleroma.Config
|
||||
|
||||
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
|
||||
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
||||
def truthy_param?(value), do: value not in @falsy_param_values
|
||||
|
||||
def explicitly_falsy_param?(value), do: value in @falsy_param_values
|
||||
|
||||
# Note: `nil` and `""` are considered falsy values in Pleroma
|
||||
def falsy_param?(value),
|
||||
do: explicitly_falsy_param?(value) or value in [nil, ""]
|
||||
|
||||
def truthy_param?(value), do: not falsy_param?(value)
|
||||
|
||||
def json_response(conn, status, json) do
|
||||
conn
|
||||
|
|
@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do
|
|||
def put_if_exist(map, _key, nil), do: map
|
||||
|
||||
def put_if_exist(map, key, value), do: Map.put(map, key, value)
|
||||
|
||||
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
|
||||
def skip_relationships?(params) do
|
||||
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
|
||||
false
|
||||
else
|
||||
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
|
||||
not truthy_param?(params["with_relationships"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
|||
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
|
||||
|
||||
def prepare_activity(activity, opts \\ []) do
|
||||
object = activity_object(activity)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
actor =
|
||||
if opts[:actor] do
|
||||
|
|
@ -33,7 +33,6 @@ defmodule Pleroma.Web.Feed.FeedView do
|
|||
%{
|
||||
activity: activity,
|
||||
data: Map.get(object, :data),
|
||||
object: object,
|
||||
actor: actor
|
||||
}
|
||||
end
|
||||
|
|
@ -68,9 +67,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
|||
|
||||
def last_activity(activities), do: List.last(activities)
|
||||
|
||||
def activity_object(activity), do: Object.normalize(activity)
|
||||
|
||||
def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
|
||||
def activity_title(%{"content" => content}, opts \\ %{}) do
|
||||
content
|
||||
|> Pleroma.Web.Metadata.Utils.scrub_html()
|
||||
|> Pleroma.Emoji.Formatter.demojify()
|
||||
|
|
@ -78,7 +75,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
|||
|> escape()
|
||||
end
|
||||
|
||||
def activity_content(%{data: %{"content" => content}}) do
|
||||
def activity_content(%{"content" => content}) do
|
||||
content
|
||||
|> String.replace(~r/[\n\r]/, "")
|
||||
|> escape()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastoFEController do
|
|||
when action == :index
|
||||
)
|
||||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :manifest])
|
||||
|
||||
@doc "GET /web/*path"
|
||||
def index(%{assigns: %{user: user, token: token}} = conn, _params)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
|
||||
only: [
|
||||
add_link_headers: 2,
|
||||
truthy_param?: 1,
|
||||
assign_account_by_id: 2,
|
||||
json_response: 3,
|
||||
skip_relationships?: 1
|
||||
]
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
|
|
@ -15,10 +21,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.ListView
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPIController
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
|
||||
plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
|
|
@ -95,6 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|> Map.put("fullname", params["fullname"] || nickname)
|
||||
|> Map.put("bio", params["bio"] || "")
|
||||
|> Map.put("confirm", params["password"])
|
||||
|> Map.put("trusted_app", app.trusted)
|
||||
|
||||
with :ok <- validate_email_param(params),
|
||||
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||
|
|
@ -140,9 +150,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "PATCH /api/v1/accounts/update_credentials"
|
||||
def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|
||||
user = original_user
|
||||
|
||||
def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||
user_params =
|
||||
[
|
||||
:no_rich_text,
|
||||
|
|
@ -178,8 +186,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
changeset = User.update_changeset(user, user_params)
|
||||
|
||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user, do: CommonAPI.update(user)
|
||||
|
||||
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
|
||||
else
|
||||
_e -> render_error(conn, :forbidden, "Invalid request")
|
||||
|
|
@ -237,7 +243,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", activities: activities, for: reading_user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: reading_user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
else
|
||||
_e -> render_error(conn, :not_found, "Can't find user")
|
||||
end
|
||||
|
|
@ -369,6 +380,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "GET /api/v1/endorsements"
|
||||
def endorsements(conn, params),
|
||||
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
|
||||
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||
|
||||
@doc "GET /api/v1/identity_proofs"
|
||||
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
|
||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
|
||||
|
||||
@doc "POST /api/v1/apps"
|
||||
def create(conn, params) do
|
||||
def create(%{body_params: params} = conn, _params) do
|
||||
scopes = Scopes.fetch_scopes(params, ["read"])
|
||||
|
||||
app_attrs =
|
||||
params
|
||||
|> Map.drop(["scope", "scopes"])
|
||||
|> Map.put("scopes", scopes)
|
||||
|> Map.take([:client_name, :redirect_uris, :website])
|
||||
|> Map.put(:scopes, scopes)
|
||||
|
||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
false <- cs.changes[:client_name] == @local_mastodon_name,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
|
|||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.User
|
||||
|
||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["follow", "read:blocks"]} when action == :index
|
||||
|
|
@ -26,13 +29,13 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
|
|||
end
|
||||
|
||||
@doc "POST /api/v1/domain_blocks"
|
||||
def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||
def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
|
||||
User.block_domain(blocker, domain)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
@doc "DELETE /api/v1/domain_blocks"
|
||||
def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||
def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
|
||||
User.unblock_domain(blocker, domain)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,21 +3,31 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
@moduledoc """
|
||||
Contains stubs for unimplemented Mastodon API endpoints.
|
||||
|
||||
Note: instead of routing directly to this controller's action,
|
||||
it's preferable to define an action in relevant (non-generic) controller,
|
||||
set up OAuth rules for it and call this controller's function from it.
|
||||
"""
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
require Logger
|
||||
|
||||
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object])
|
||||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
# Stubs for unimplemented mastodon api
|
||||
#
|
||||
def empty_array(conn, _) do
|
||||
Logger.debug("Unimplemented, returning an empty array")
|
||||
Logger.debug("Unimplemented, returning an empty array (list)")
|
||||
json(conn, [])
|
||||
end
|
||||
|
||||
def empty_object(conn, _) do
|
||||
Logger.debug("Unimplemented, returning an empty object")
|
||||
Logger.debug("Unimplemented, returning an empty object (map)")
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
|
@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(notifications)
|
||||
|> render("index.json", notifications: notifications, for: user)
|
||||
|> render("index.json",
|
||||
notifications: notifications,
|
||||
for: user,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
# GET /api/v1/notifications/:id
|
||||
|
|
@ -66,7 +70,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
|||
json(conn, %{})
|
||||
end
|
||||
|
||||
# POST /api/v1/notifications/dismiss
|
||||
# POST /api/v1/notifications/:id/dismiss
|
||||
# POST /api/v1/notifications/dismiss (deprecated)
|
||||
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
|
|
@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
|
||||
defp search_options(params, user) do
|
||||
[
|
||||
skip_relationships: skip_relationships?(params),
|
||||
resolve: params["resolve"] == "true",
|
||||
following: params["following"] == "true",
|
||||
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
||||
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
||||
limit: fetch_integer_param(params, "limit"),
|
||||
offset: fetch_integer_param(params, "offset"),
|
||||
type: params["type"],
|
||||
author: get_author(params),
|
||||
for_user: user
|
||||
|
|
@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
|
||||
defp resource_search(_, "accounts", query, options) do
|
||||
accounts = with_fallback(fn -> User.search(query, options) end)
|
||||
AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
|
||||
|
||||
AccountView.render("index.json",
|
||||
users: accounts,
|
||||
for: options[:for_user],
|
||||
as: :user,
|
||||
skip_relationships: false
|
||||
)
|
||||
end
|
||||
|
||||
defp resource_search(_, "statuses", query, options) do
|
||||
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
||||
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
|
||||
|
||||
StatusView.render("index.json",
|
||||
activities: statuses,
|
||||
for: options[:for_user],
|
||||
as: :activity,
|
||||
skip_relationships: options[:skip_relationships]
|
||||
)
|
||||
end
|
||||
|
||||
defp resource_search(:v2, "hashtags", query, _options) do
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
|
||||
|
||||
require Ecto.Query
|
||||
|
||||
|
|
@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
`ids` query param is required
|
||||
"""
|
||||
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
||||
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
|
||||
limit = 100
|
||||
|
||||
activities =
|
||||
|
|
@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|> Activity.all_by_ids_with_object()
|
||||
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
||||
|
||||
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||
render(conn, "index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/bookmarks"
|
||||
|
|
@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(bookmarks)
|
||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.SuggestionController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
||||
require Logger
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
|
||||
|
||||
@doc "GET /api/v1/suggestions"
|
||||
def index(conn, _) do
|
||||
json(conn, [])
|
||||
end
|
||||
def index(conn, params),
|
||||
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
|
||||
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
|
||||
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
|
@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
# TODO: Replace with a macro when there is a Phoenix release with
|
||||
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
|
||||
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
|
||||
# in it
|
||||
|
||||
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
|
||||
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
|
||||
|
|
@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/direct
|
||||
|
|
@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/public
|
||||
|
|
@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
else
|
||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
end
|
||||
|
|
@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/list/:list_id
|
||||
|
|
@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||
|> Enum.reverse()
|
||||
|
||||
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||
render(conn, "index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
else
|
||||
_e -> render_error(conn, :forbidden, "Error.")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
def render("index.json", %{users: users} = opts) do
|
||||
reading_user = opts[:for]
|
||||
|
||||
# Note: :skip_relationships option is currently intentionally not supported for accounts
|
||||
relationships_opt =
|
||||
cond do
|
||||
Map.has_key?(opts, :relationships) ->
|
||||
|
|
@ -73,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
followed_by =
|
||||
if following_relationships do
|
||||
case FollowingRelationship.find(following_relationships, target, reading_user) do
|
||||
%{state: "accept"} -> true
|
||||
%{state: :follow_accept} -> true
|
||||
_ -> false
|
||||
end
|
||||
else
|
||||
|
|
@ -83,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: follow_state == "accept",
|
||||
following: follow_state == :follow_accept,
|
||||
followed_by: followed_by,
|
||||
blocking:
|
||||
UserRelationship.exists?(
|
||||
|
|
@ -125,7 +126,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
reading_user,
|
||||
&User.subscribed_to?(&2, &1)
|
||||
),
|
||||
requested: follow_state == "pending",
|
||||
requested: follow_state == :follow_pending,
|
||||
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||
showing_reblogs:
|
||||
not UserRelationship.exists?(
|
||||
|
|
@ -180,23 +181,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
bot = user.actor_type in ["Application", "Service"]
|
||||
|
||||
emojis =
|
||||
(user.source_data["tag"] || [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||
Enum.map(user.emoji, fn {shortcode, url} ->
|
||||
%{
|
||||
"shortcode" => String.trim(name, ":"),
|
||||
"url" => MediaProxy.url(url),
|
||||
"static_url" => MediaProxy.url(url),
|
||||
"shortcode" => shortcode,
|
||||
"url" => url,
|
||||
"static_url" => url,
|
||||
"visible_in_picker" => false
|
||||
}
|
||||
end)
|
||||
|
||||
relationship =
|
||||
render("relationship.json", %{
|
||||
user: opts[:for],
|
||||
target: user,
|
||||
relationships: opts[:relationships]
|
||||
})
|
||||
if opts[:skip_relationships] do
|
||||
%{}
|
||||
else
|
||||
render("relationship.json", %{
|
||||
user: opts[:for],
|
||||
target: user,
|
||||
relationships: opts[:relationships]
|
||||
})
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(user.id),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
|
|||
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
def render("index.json", %{apps: apps, count: count, page_size: page_size, admin: true}) do
|
||||
%{
|
||||
apps: render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json", %{admin: true}),
|
||||
count: count,
|
||||
page_size: page_size
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{admin: true, app: %App{} = app} = assigns) do
|
||||
"show.json"
|
||||
|> render(Map.delete(assigns, :admin))
|
||||
|> Map.put(:trusted, app.trusted)
|
||||
|> Map.put(:id, app.id)
|
||||
end
|
||||
|
||||
def render("show.json", %{app: %App{} = app}) do
|
||||
%{
|
||||
id: app.id |> to_string,
|
||||
|
|
|
|||
|
|
@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
|> Enum.filter(& &1)
|
||||
|> Kernel.++(move_activities_targets)
|
||||
|
||||
UserRelationship.view_relationships_option(reading_user, actors)
|
||||
UserRelationship.view_relationships_option(reading_user, actors,
|
||||
source_mutes_only: opts[:skip_relationships]
|
||||
)
|
||||
end
|
||||
|
||||
opts = %{
|
||||
for: reading_user,
|
||||
parent_activities: parent_activities,
|
||||
relationships: relationships_opt
|
||||
}
|
||||
opts =
|
||||
opts
|
||||
|> Map.put(:parent_activities, parent_activities)
|
||||
|> Map.put(:relationships, relationships_opt)
|
||||
|
||||
safe_render_many(notifications, NotificationView, "show.json", opts)
|
||||
end
|
||||
|
|
@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
|
||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||
|
||||
render_opts = %{
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
}
|
||||
|
||||
with %{id: _} = account <-
|
||||
AccountView.render("show.json", %{
|
||||
user: actor,
|
||||
for: reading_user,
|
||||
relationships: opts[:relationships]
|
||||
}) do
|
||||
AccountView.render(
|
||||
"show.json",
|
||||
Map.merge(render_opts, %{user: actor, for: reading_user})
|
||||
) do
|
||||
response = %{
|
||||
id: to_string(notification.id),
|
||||
type: mastodon_type,
|
||||
|
|
@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
}
|
||||
}
|
||||
|
||||
render_opts = %{relationships: opts[:relationships]}
|
||||
|
||||
case mastodon_type do
|
||||
"mention" ->
|
||||
put_status(response, activity, reading_user, render_opts)
|
||||
|
|
@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
put_status(response, parent_activity_fn.(), reading_user, render_opts)
|
||||
|
||||
"move" ->
|
||||
# Note: :skip_relationships option being applied to _account_ rendering (here)
|
||||
put_target(response, activity, reading_user, render_opts)
|
||||
|
||||
"pleroma:emoji_reaction" ->
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
true ->
|
||||
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
|
||||
|
||||
UserRelationship.view_relationships_option(reading_user, actors)
|
||||
UserRelationship.view_relationships_option(reading_user, actors,
|
||||
source_mutes_only: opts[:skip_relationships]
|
||||
)
|
||||
end
|
||||
|
||||
opts =
|
||||
|
|
@ -153,7 +155,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
AccountView.render("show.json", %{
|
||||
user: user,
|
||||
for: opts[:for],
|
||||
relationships: opts[:relationships]
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
}),
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
|
|
@ -301,6 +304,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
_ -> []
|
||||
end
|
||||
|
||||
# Status muted state (would do 1 request per status unless user mutes are preloaded)
|
||||
muted =
|
||||
thread_muted? ||
|
||||
UserRelationship.exists?(
|
||||
|
|
@ -319,7 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
AccountView.render("show.json", %{
|
||||
user: user,
|
||||
for: opts[:for],
|
||||
relationships: opts[:relationships]
|
||||
relationships: opts[:relationships],
|
||||
skip_relationships: opts[:skip_relationships]
|
||||
}),
|
||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.OAuth.App do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
|
@ -16,14 +17,24 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
field(:website, :string)
|
||||
field(:client_id, :string)
|
||||
field(:client_secret, :string)
|
||||
field(:trusted, :boolean, default: false)
|
||||
|
||||
has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
|
||||
has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec changeset(App.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()
|
||||
def register_changeset(struct, params \\ %{}) do
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website])
|
||||
|> changeset(params)
|
||||
|> validate_required([:client_name, :redirect_uris, :scopes])
|
||||
|
||||
if changeset.valid? do
|
||||
|
|
@ -41,6 +52,21 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
end
|
||||
end
|
||||
|
||||
@spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create(params) do
|
||||
with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
|
||||
Repo.insert(changeset)
|
||||
end
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets app by attrs or create new with attrs.
|
||||
And updates the scopes if need.
|
||||
|
|
@ -65,4 +91,58 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
|> change(%{scopes: scopes})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
|
||||
def search(params) do
|
||||
query = from(a in __MODULE__)
|
||||
|
||||
query =
|
||||
if params[:client_name] do
|
||||
from(a in query, where: a.client_name == ^params[:client_name])
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if params[:client_id] do
|
||||
from(a in query, where: a.client_id == ^params[:client_id])
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
if Map.has_key?(params, :trusted) do
|
||||
from(a in query, where: a.trusted == ^params[:trusted])
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
query =
|
||||
from(u in query,
|
||||
limit: ^params[:page_size],
|
||||
offset: ^((params[:page] - 1) * params[:page_size])
|
||||
)
|
||||
|
||||
count = Repo.aggregate(__MODULE__, :count, :id)
|
||||
|
||||
{:ok, Repo.all(query), count}
|
||||
end
|
||||
|
||||
@spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
def destroy(id) do
|
||||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||
Repo.delete(app)
|
||||
end
|
||||
end
|
||||
|
||||
@spec errors(Ecto.Changeset.t()) :: map()
|
||||
def errors(changeset) do
|
||||
Enum.reduce(changeset.errors, %{}, fn
|
||||
{:client_name, {error, _}}, acc ->
|
||||
Map.put(acc, :name, error)
|
||||
|
||||
{key, {error, _}}, acc ->
|
||||
Map.put(acc, key, error)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
plug(:fetch_flash)
|
||||
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
|
||||
|
||||
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug)
|
||||
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
|
|||
Note: `scopes` is used by Mastodon — supporting it but sticking to
|
||||
OAuth's standard `scope` wherever we control it
|
||||
"""
|
||||
@spec fetch_scopes(map(), list()) :: list()
|
||||
@spec fetch_scopes(map() | struct(), list()) :: list()
|
||||
|
||||
def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
|
||||
parse_scopes(scopes, default)
|
||||
end
|
||||
|
||||
def fetch_scopes(params, default) do
|
||||
parse_scopes(params["scope"] || params["scopes"], default)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
require Pleroma.Constants
|
||||
|
|
@ -58,38 +57,32 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
||||
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
|
||||
{:ok, user} =
|
||||
{:ok, _user} =
|
||||
user
|
||||
|> Changeset.change(%{avatar: nil})
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
CommonAPI.update(user)
|
||||
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
|
||||
def update_avatar(%{assigns: %{user: user}} = conn, params) do
|
||||
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
||||
{:ok, user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
||||
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
||||
%{"url" => [%{"href" => href} | _]} = data
|
||||
|
||||
CommonAPI.update(user)
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
||||
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
|
||||
with {:ok, user} <- User.update_banner(user, %{}) do
|
||||
CommonAPI.update(user)
|
||||
with {:ok, _user} <- User.update_banner(user, %{}) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}} = conn, params) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
|
||||
{:ok, user} <- User.update_banner(user, object.data) do
|
||||
CommonAPI.update(user)
|
||||
{:ok, _user} <- User.update_banner(user, object.data) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
|
|
@ -139,7 +132,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", activities: activities, for: for_user, as: :activity)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: for_user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation.Participation
|
||||
|
|
@ -34,7 +34,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:conversations"]} when action == :update_conversation
|
||||
%{scopes: ["write:conversations"]} when action in [:update_conversation, :read_conversations]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
|
||||
|
|
@ -110,12 +110,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
end
|
||||
|
||||
def conversation_statuses(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{assigns: %{user: %{id: user_id} = user}} = conn,
|
||||
%{"id" => participation_id} = params
|
||||
) do
|
||||
with %Participation{} = participation <-
|
||||
Participation.get(participation_id, preload: [:conversation]),
|
||||
true <- user.id == participation.user_id do
|
||||
with %Participation{user_id: ^user_id} = participation <-
|
||||
Participation.get(participation_id, preload: [:conversation]) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("blocking_user", user)
|
||||
|
|
@ -124,13 +123,19 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
|
||||
activities =
|
||||
participation.conversation.ap_id
|
||||
|> ActivityPub.fetch_activities_for_context(params)
|
||||
|> ActivityPub.fetch_activities_for_context_query(params)
|
||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
else
|
||||
_error ->
|
||||
conn
|
||||
|
|
@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
|
||||
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
|
||||
with notifications <- Notification.set_read_up_to(user, max_id) do
|
||||
notifications = Enum.take(notifications, 80)
|
||||
|
||||
conn
|
||||
|> put_view(NotificationView)
|
||||
|> render("index.json", %{notifications: notifications, for: user})
|
||||
|> render("index.json",
|
||||
notifications: notifications,
|
||||
for: user,
|
||||
skip_relationships: skip_relationships?(params)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,5 +64,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
|
||||
def fetch_data_for_activity(_), do: %{}
|
||||
|
||||
def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
|
||||
def perform(:fetch, %Activity{} = activity) do
|
||||
fetch_data_for_activity(activity)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :authenticated_api do
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.AuthExpectedPlug)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
|
||||
plug(Pleroma.Plugs.UserFetcherPlug)
|
||||
|
|
@ -44,6 +46,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :admin_api do
|
||||
|
|
@ -61,6 +64,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.UserIsAdminPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
|
|
@ -94,10 +98,12 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
pipeline :config do
|
||||
plug(:accepts, ["json", "xml"])
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :pleroma_api do
|
||||
plug(:accepts, ["html", "json"])
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :mailbox_preview do
|
||||
|
|
@ -198,12 +204,18 @@ defmodule Pleroma.Web.Router do
|
|||
get("/config", AdminAPIController, :config_show)
|
||||
post("/config", AdminAPIController, :config_update)
|
||||
get("/config/descriptions", AdminAPIController, :config_descriptions)
|
||||
get("/need_reboot", AdminAPIController, :need_reboot)
|
||||
get("/restart", AdminAPIController, :restart)
|
||||
|
||||
get("/moderation_log", AdminAPIController, :list_log)
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||
|
|
@ -333,7 +345,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/relationships", AccountController, :relationships)
|
||||
|
||||
get("/accounts/:id/lists", AccountController, :lists)
|
||||
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
|
||||
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
|
||||
|
||||
get("/follow_requests", FollowRequestController, :index)
|
||||
get("/blocks", AccountController, :blocks)
|
||||
|
|
@ -347,9 +359,11 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/notifications", NotificationController, :index)
|
||||
get("/notifications/:id", NotificationController, :show)
|
||||
post("/notifications/:id/dismiss", NotificationController, :dismiss)
|
||||
post("/notifications/clear", NotificationController, :clear)
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
|
||||
get("/scheduled_statuses", ScheduledActivityController, :index)
|
||||
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
|
||||
|
|
@ -500,6 +514,12 @@ defmodule Pleroma.Web.Router do
|
|||
)
|
||||
end
|
||||
|
||||
scope "/api" do
|
||||
pipe_through(:api)
|
||||
|
||||
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
|
|
@ -658,6 +678,17 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
end
|
||||
|
||||
# Test-only routes needed to test action dispatching and plug chain execution
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
scope "/test/authenticated_api", Pleroma.Tests do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
for action <- [:skipped_oauth, :performed_oauth, :missed_oauth] do
|
||||
get("/#{action}", OAuthTestController, action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.MongooseIM do
|
||||
get("/user_exists", MongooseIMController, :user_exists)
|
||||
get("/check_password", MongooseIMController, :check_password)
|
||||
|
|
|
|||
|
|
@ -18,15 +18,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do
|
|||
|
||||
@media_types ["image", "audio", "video"]
|
||||
|
||||
def emoji_for_user(%User{} = user) do
|
||||
user.source_data
|
||||
|> Map.get("tag", [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
end
|
||||
|
||||
def fetch_media_type(%{"mediaType" => mediaType}) do
|
||||
Utils.fetch_media_type(@media_types, mediaType)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<id><%= @data["id"] %></id>
|
||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<content type="html"><%= activity_content(@object) %></content>
|
||||
<published><%= @data["published"] %></published>
|
||||
<updated><%= @data["published"] %></updated>
|
||||
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<content type="html"><%= activity_content(@data) %></content>
|
||||
<published><%= @activity.data["published"] %></published>
|
||||
<updated><%= @activity.data["published"] %></updated>
|
||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||
<%= activity_context(@activity) %>
|
||||
</ostatus:conversation>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
<guid><%= @data["id"] %></guid>
|
||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<description><%= activity_content(@object) %></description>
|
||||
<pubDate><%= @data["published"] %></pubDate>
|
||||
<updated><%= @data["published"] %></updated>
|
||||
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<description><%= activity_content(@data) %></description>
|
||||
<pubDate><%= @activity.data["published"] %></pubDate>
|
||||
<updated><%= @activity.data["published"] %></updated>
|
||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||
<%= activity_context(@activity) %>
|
||||
</ostatus:conversation>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<entry>
|
||||
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||
|
||||
|
||||
<%= render @view_module, "_tag_author.atom", assigns %>
|
||||
|
||||
|
||||
<id><%= @data["id"] %></id>
|
||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<content type="html"><%= activity_content(@object) %></content>
|
||||
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
<content type="html"><%= activity_content(@data) %></content>
|
||||
|
||||
<%= if @activity.local do %>
|
||||
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
|
||||
<% end %>
|
||||
|
||||
<published><%= @data["published"] %></published>
|
||||
<updated><%= @data["published"] %></updated>
|
||||
<published><%= @activity.data["published"] %></published>
|
||||
<updated><%= @activity.data["published"] %></updated>
|
||||
|
||||
<ostatus:conversation ref="<%= activity_context(@activity) %>">
|
||||
<%= activity_context(@activity) %>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<%= if @data["summary"] do %>
|
||||
<summary><%= @data["summary"] %></summary>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= for id <- @activity.recipients do %>
|
||||
<%= if id == Pleroma.Constants.as_public() do %>
|
||||
<link rel="mentioned"
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= for tag <- @data["tag"] || [] do %>
|
||||
<category term="<%= tag %>"></category>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<item>
|
||||
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
|
||||
|
||||
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
|
||||
|
||||
|
||||
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
|
||||
<link><%= activity_context(@activity) %></link>
|
||||
<pubDate><%= pub_date(@data["published"]) %></pubDate>
|
||||
|
||||
<description><%= activity_content(@object) %></description>
|
||||
<pubDate><%= pub_date(@activity.data["published"]) %></pubDate>
|
||||
|
||||
<description><%= activity_content(@data) %></description>
|
||||
<%= for attachment <- @data["attachment"] || [] do %>
|
||||
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
|
||||
<% end %>
|
||||
|
||||
</item>
|
||||
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<img src="<%= User.avatar_url(@user) |> MediaProxy.url %>" width="48" height="48" alt="">
|
||||
</div>
|
||||
<span class="display-name">
|
||||
<bdi><%= raw (@user.name |> Formatter.emojify(emoji_for_user(@user))) %></bdi>
|
||||
<bdi><%= raw Formatter.emojify(@user.name, @user.emoji) %></bdi>
|
||||
<span class="nickname"><%= @user.nickname %></span>
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<input type="hidden" name="profile" value="">
|
||||
<button type="submit" class="collapse">Remote follow</button>
|
||||
</form>
|
||||
<%= raw Formatter.emojify(@user.name, emoji_for_user(@user)) %> |
|
||||
<%= raw Formatter.emojify(@user.name, @user.emoji) %> |
|
||||
<%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %>
|
||||
</h3>
|
||||
<p><%= raw @user.bio %></p>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
|
||||
def register_user(params, opts \\ []) do
|
||||
token = params["token"]
|
||||
trusted_app? = params["trusted_app"]
|
||||
|
||||
params = %{
|
||||
nickname: params["nickname"],
|
||||
|
|
@ -29,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
|
||||
# true if captcha is disabled or enabled and valid, false otherwise
|
||||
captcha_ok =
|
||||
if not captcha_enabled do
|
||||
if trusted_app? || not captcha_enabled do
|
||||
:ok
|
||||
else
|
||||
Pleroma.Captcha.validate(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||
|
||||
plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
|
||||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
|
||||
action_fallback(:errors)
|
||||
|
|
|
|||
|
|
@ -29,11 +29,40 @@ defmodule Pleroma.Web do
|
|||
import Pleroma.Web.Router.Helpers
|
||||
import Pleroma.Web.TranslationHelpers
|
||||
|
||||
alias Pleroma.Plugs.PlugHelper
|
||||
|
||||
plug(:set_put_layout)
|
||||
|
||||
defp set_put_layout(conn, _) do
|
||||
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
|
||||
end
|
||||
|
||||
# Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain
|
||||
defp skip_plug(conn, plug_module) do
|
||||
try do
|
||||
plug_module.ensure_skippable()
|
||||
rescue
|
||||
UndefinedFunctionError ->
|
||||
raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code."
|
||||
end
|
||||
|
||||
PlugHelper.append_to_skipped_plugs(conn, plug_module)
|
||||
end
|
||||
|
||||
# Here we can apply before-action hooks (e.g. verify whether auth checks were preformed)
|
||||
defp action(conn, params) do
|
||||
if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) &&
|
||||
not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
|
||||
conn
|
||||
|> render_error(
|
||||
:forbidden,
|
||||
"Security violation: OAuth scopes check was neither handled nor explicitly skipped."
|
||||
)
|
||||
|> halt()
|
||||
else
|
||||
super(conn, params)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -96,6 +125,26 @@ defmodule Pleroma.Web do
|
|||
end
|
||||
end
|
||||
|
||||
def plug do
|
||||
quote do
|
||||
alias Pleroma.Plugs.PlugHelper
|
||||
|
||||
def ensure_skippable, do: :noop
|
||||
|
||||
@impl Plug
|
||||
@doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise."
|
||||
def call(%Plug.Conn{} = conn, options) do
|
||||
if PlugHelper.plug_skipped?(conn, __MODULE__) do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> PlugHelper.append_to_called_plugs(__MODULE__)
|
||||
|> perform(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue