Merge branch 'develop' into refactor/notification_settings
This commit is contained in:
commit
80c21100db
314 changed files with 5798 additions and 2411 deletions
|
|
@ -3,15 +3,48 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Pleroma do
|
||||
@apps [
|
||||
:restarter,
|
||||
:ecto,
|
||||
:ecto_sql,
|
||||
:postgrex,
|
||||
:db_connection,
|
||||
:cachex,
|
||||
:flake_id,
|
||||
:swoosh,
|
||||
:timex
|
||||
]
|
||||
@cachex_children ["object", "user"]
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
if Pleroma.Config.get(:env) != :test do
|
||||
Application.put_env(:logger, :console, level: :debug)
|
||||
end
|
||||
|
||||
{:ok, _} = Application.ensure_all_started(:pleroma)
|
||||
apps =
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
|
||||
[:gun | @apps]
|
||||
else
|
||||
[:hackney | @apps]
|
||||
end
|
||||
|
||||
Enum.each(apps, &Application.ensure_all_started/1)
|
||||
|
||||
children = [
|
||||
Pleroma.Repo,
|
||||
{Pleroma.Config.TransferTask, false},
|
||||
Pleroma.Web.Endpoint
|
||||
]
|
||||
|
||||
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
||||
|
||||
Supervisor.start_link(children ++ cachex_children,
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
)
|
||||
|
||||
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
|
||||
pleroma_rebooted?()
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
defp migrate_from_db(opts) do
|
||||
if Pleroma.Config.get([:configurable_from_database]) do
|
||||
env = opts[:env] || "prod"
|
||||
env = opts[:env] || Pleroma.Config.get(:env)
|
||||
|
||||
config_path =
|
||||
if Pleroma.Config.get(:release) do
|
||||
|
|
@ -105,6 +105,10 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
:ok = File.close(file)
|
||||
System.cmd("mix", ["format", config_path])
|
||||
|
||||
shell_info(
|
||||
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
|
||||
)
|
||||
else
|
||||
migration_error()
|
||||
end
|
||||
|
|
@ -112,7 +116,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
defp migration_error do
|
||||
shell_error(
|
||||
"Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true."
|
||||
"Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
options,
|
||||
:uploads_dir,
|
||||
"What directory should media uploads go in (when using the local uploader)?",
|
||||
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
)
|
||||
|> Path.expand()
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
options,
|
||||
:static_dir,
|
||||
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
|
||||
Pleroma.Config.get([:instance, :static_dir])
|
||||
Config.get([:instance, :static_dir])
|
||||
)
|
||||
|> Path.expand()
|
||||
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.tag(tags)
|
||||
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
|
|
@ -245,7 +245,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.untag(tags)
|
||||
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ defmodule Pleroma.Application do
|
|||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Config.Holder.save_default()
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Config.DeprecationWarnings.warn()
|
||||
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
|
|
@ -162,7 +163,8 @@ defmodule Pleroma.Application do
|
|||
defp seconds_valid_interval,
|
||||
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
||||
|
||||
defp build_cachex(type, opts),
|
||||
@spec build_cachex(String.t(), keyword()) :: map()
|
||||
def build_cachex(type, opts),
|
||||
do: %{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ defmodule Pleroma.Config.Loader do
|
|||
:swarm
|
||||
]
|
||||
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla
|
||||
]
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
@reader Config.Reader
|
||||
|
||||
|
|
@ -47,7 +52,8 @@ defmodule Pleroma.Config.Loader do
|
|||
@spec filter_group(atom(), keyword()) :: keyword()
|
||||
def filter_group(group, configs) do
|
||||
Enum.reject(configs[group], fn {key, _v} ->
|
||||
key in @reject_keys or (group == :phoenix and key == :serve_endpoints) or group == :postgrex
|
||||
key in @reject_keys or group in @reject_groups or
|
||||
(group == :phoenix and key == :serve_endpoints)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ defmodule Pleroma.Config.TransferTask do
|
|||
{:pleroma, :gopher, [:enabled]}
|
||||
]
|
||||
|
||||
def start_link(_) do
|
||||
load_and_update_env()
|
||||
def start_link(restart_pleroma? \\ true) do
|
||||
load_and_update_env([], restart_pleroma?)
|
||||
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||
:ignore
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ defmodule Pleroma.Docs.Generator do
|
|||
implementation.process(descriptions)
|
||||
end
|
||||
|
||||
@spec list_modules_in_dir(String.t(), String.t()) :: [module()]
|
||||
def list_modules_in_dir(dir, start) do
|
||||
with {:ok, files} <- File.ls(dir) do
|
||||
files
|
||||
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|
||||
|> Enum.map(fn filename ->
|
||||
module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
|
||||
String.to_atom(start <> module)
|
||||
end)
|
||||
end
|
||||
@spec list_behaviour_implementations(behaviour :: module()) :: [module()]
|
||||
def list_behaviour_implementations(behaviour) do
|
||||
:code.all_loaded()
|
||||
|> Enum.filter(fn {module, _} ->
|
||||
# This shouldn't be needed as all modules are expected to have module_info/1,
|
||||
# but in test enviroments some transient modules `:elixir_compiler_XX`
|
||||
# are loaded for some reason (where XX is a random integer).
|
||||
if function_exported?(module, :module_info, 1) do
|
||||
module.module_info(:attributes)
|
||||
|> Keyword.get_values(:behaviour)
|
||||
|> List.flatten()
|
||||
|> Enum.member?(behaviour)
|
||||
end
|
||||
end)
|
||||
|> Enum.map(fn {module, _} -> module end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -87,6 +92,12 @@ defmodule Pleroma.Docs.Generator do
|
|||
else: string
|
||||
end
|
||||
|
||||
defp format_suggestions({:list_behaviour_implementations, behaviour}) do
|
||||
behaviour
|
||||
|> list_behaviour_implementations()
|
||||
|> format_suggestions()
|
||||
end
|
||||
|
||||
defp format_suggestions([]), do: []
|
||||
|
||||
defp format_suggestions([suggestion | tail]) do
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
defmodule Pleroma.Docs.JSON do
|
||||
@behaviour Pleroma.Docs.Generator
|
||||
@external_resource "config/description.exs"
|
||||
@raw_config Pleroma.Config.Loader.read("config/description.exs")
|
||||
@raw_descriptions @raw_config[:pleroma][:config_description]
|
||||
@term __MODULE__.Compiled
|
||||
|
||||
@spec compile :: :ok
|
||||
def compile do
|
||||
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
|
||||
end
|
||||
|
||||
@spec compiled_descriptions :: Map.t()
|
||||
def compiled_descriptions do
|
||||
:persistent_term.get(@term)
|
||||
end
|
||||
|
||||
@spec process(keyword()) :: {:ok, String.t()}
|
||||
def process(descriptions) do
|
||||
|
|
@ -13,11 +27,4 @@ defmodule Pleroma.Docs.JSON do
|
|||
{:ok, path}
|
||||
end
|
||||
end
|
||||
|
||||
def compile do
|
||||
with config <- Pleroma.Config.Loader.read("config/description.exs") do
|
||||
config[:pleroma][:config_description]
|
||||
|> Pleroma.Docs.Generator.convert_to_strings()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -68,6 +68,11 @@ defmodule Pleroma.Docs.Markdown do
|
|||
IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n")
|
||||
end
|
||||
|
||||
defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do
|
||||
suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour)
|
||||
print_suggestions(file, suggestions)
|
||||
end
|
||||
|
||||
defp print_suggestions(_file, nil), do: nil
|
||||
|
||||
defp print_suggestions(_file, ""), do: nil
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
||||
defp instance_config, do: Pleroma.Config.get(:instance)
|
||||
defp instance_config, do: Config.get(:instance)
|
||||
defp instance_name, do: instance_config()[:name]
|
||||
|
||||
defp instance_notify_email do
|
||||
|
|
@ -72,6 +72,8 @@ defmodule Pleroma.Emails.AdminEmail do
|
|||
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
|
||||
#{comment_html}
|
||||
#{statuses_html}
|
||||
<p>
|
||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
|
||||
"""
|
||||
|
||||
new()
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ defmodule Pleroma.Emoji.Loader do
|
|||
if File.exists?(emoji_txt) do
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
|
||||
extensions = Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
||||
|
|
|
|||
|
|
@ -34,10 +34,18 @@ defmodule Pleroma.Filter do
|
|||
Repo.one(query)
|
||||
end
|
||||
|
||||
def get_filters(%User{id: user_id} = _user) do
|
||||
def get_active(query) do
|
||||
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
|
||||
end
|
||||
|
||||
def get_irreversible(query) do
|
||||
from(f in query, where: f.hide)
|
||||
end
|
||||
|
||||
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
|
||||
query =
|
||||
from(
|
||||
f in Pleroma.Filter,
|
||||
f in query,
|
||||
where: f.user_id == ^user_id,
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|
|
@ -95,4 +103,34 @@ defmodule Pleroma.Filter do
|
|||
|> validate_required([:phrase, :context])
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def compose_regex(user_or_filters, format \\ :postgres)
|
||||
|
||||
def compose_regex(%User{} = user, format) do
|
||||
__MODULE__
|
||||
|> get_active()
|
||||
|> get_irreversible()
|
||||
|> get_filters(user)
|
||||
|> compose_regex(format)
|
||||
end
|
||||
|
||||
def compose_regex([_ | _] = filters, format) do
|
||||
phrases =
|
||||
filters
|
||||
|> Enum.map(& &1.phrase)
|
||||
|> Enum.join("|")
|
||||
|
||||
case format do
|
||||
:postgres ->
|
||||
"\\y(#{phrases})\\y"
|
||||
|
||||
:re ->
|
||||
~r/\b#{phrases}\b/i
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def compose_regex(_, _), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ defmodule Pleroma.HTML do
|
|||
result =
|
||||
content
|
||||
|> Floki.parse_fragment!()
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]")
|
||||
|> Floki.attribute("a", "href")
|
||||
|> Enum.at(0)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Instances.Instance do
|
|||
schema "instances" do
|
||||
field(:host, :string)
|
||||
field(:unreachable_since, :naive_datetime_usec)
|
||||
field(:favicon, :string)
|
||||
field(:favicon_updated_at, :naive_datetime)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
@ -25,7 +27,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:host, :unreachable_since])
|
||||
|> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at])
|
||||
|> validate_required([:host])
|
||||
|> unique_constraint(:host)
|
||||
end
|
||||
|
|
@ -120,4 +122,48 @@ defmodule Pleroma.Instances.Instance do
|
|||
end
|
||||
|
||||
defp parse_datetime(datetime), do: datetime
|
||||
|
||||
def get_or_update_favicon(%URI{host: host} = instance_uri) do
|
||||
existing_record = Repo.get_by(Instance, %{host: host})
|
||||
now = NaiveDateTime.utc_now()
|
||||
|
||||
if existing_record && existing_record.favicon_updated_at &&
|
||||
NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do
|
||||
existing_record.favicon
|
||||
else
|
||||
favicon = scrape_favicon(instance_uri)
|
||||
|
||||
if existing_record do
|
||||
existing_record
|
||||
|> changeset(%{favicon: favicon, favicon_updated_at: now})
|
||||
|> Repo.update()
|
||||
else
|
||||
%Instance{}
|
||||
|> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
favicon
|
||||
end
|
||||
end
|
||||
|
||||
defp scrape_favicon(%URI{} = instance_uri) do
|
||||
try do
|
||||
with {:ok, %Tesla.Env{body: html}} <-
|
||||
Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]),
|
||||
favicon_rel <-
|
||||
html
|
||||
|> Floki.parse_document!()
|
||||
|> Floki.attribute("link[rel=icon]", "href")
|
||||
|> List.first(),
|
||||
favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(),
|
||||
true <- is_binary(favicon) do
|
||||
favicon
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -25,18 +24,27 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
|||
|> type_from_activity()
|
||||
|
||||
notification
|
||||
|> Notification.changeset(%{type: type})
|
||||
|> Ecto.Changeset.change(%{type: type})
|
||||
|> Repo.update()
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_by_ap_id(ap_id) do
|
||||
q =
|
||||
from(u in User,
|
||||
select: u.id
|
||||
)
|
||||
|
||||
Repo.get_by(q, ap_id: ap_id)
|
||||
end
|
||||
|
||||
# This is copied over from Notifications to keep this stable.
|
||||
defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
||||
case type do
|
||||
"Follow" ->
|
||||
accepted_function = fn activity ->
|
||||
with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
|
||||
%User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
|
||||
with %User{} = follower <- get_by_ap_id(activity.data["actor"]),
|
||||
%User{} = followed <- get_by_ap_id(activity.data["object"]) do
|
||||
Pleroma.FollowingRelationship.following?(follower, followed)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ defmodule Pleroma.Notification do
|
|||
|> preload([n, a, o], activity: {a, object: o})
|
||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||
|> exclude_blocked(user, exclude_blocked_opts)
|
||||
|> exclude_filtered(user)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
||||
|
|
@ -158,6 +159,20 @@ defmodule Pleroma.Notification do
|
|||
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||
end
|
||||
|
||||
defp exclude_filtered(query, user) do
|
||||
case Pleroma.Filter.compose_regex(user) do
|
||||
nil ->
|
||||
query
|
||||
|
||||
regex ->
|
||||
from([_n, a, o] in query,
|
||||
where:
|
||||
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
||||
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@valid_visibilities ~w[direct unlisted public private]
|
||||
|
||||
defp exclude_visibility(query, %{exclude_visibilities: visibility})
|
||||
|
|
@ -337,6 +352,7 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
end
|
||||
|
||||
@spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
|
||||
def create_notifications(activity, options \\ [])
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
|
||||
|
|
@ -367,6 +383,7 @@ defmodule Pleroma.Notification do
|
|||
do_send = do_send && user in enabled_receivers
|
||||
create_notification(activity, user, do_send)
|
||||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
{:ok, notifications}
|
||||
end
|
||||
|
|
@ -480,6 +497,10 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
|
||||
[object_id]
|
||||
end
|
||||
|
||||
def get_potential_receiver_ap_ids(activity) do
|
||||
[]
|
||||
|> Utils.maybe_notify_to_recipients(activity)
|
||||
|
|
@ -551,7 +572,8 @@ defmodule Pleroma.Notification do
|
|||
:self,
|
||||
:invisible,
|
||||
:block_from_strangers,
|
||||
:recently_followed
|
||||
:recently_followed,
|
||||
:filtered
|
||||
]
|
||||
|> Enum.find(&skip?(&1, activity, user))
|
||||
end
|
||||
|
|
@ -590,6 +612,26 @@ defmodule Pleroma.Notification do
|
|||
end)
|
||||
end
|
||||
|
||||
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
|
||||
|
||||
def skip?(:filtered, activity, user) do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
cond do
|
||||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["actor"] == user.ap_id ->
|
||||
false
|
||||
|
||||
not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
|
||||
Regex.match?(regex, object.data["content"])
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def skip?(_, _, _), do: false
|
||||
|
||||
def for_user_and_activity(user, activity) do
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ defmodule Pleroma.Object.Fetcher do
|
|||
{:transmogrifier, {:error, {:reject, nil}}} ->
|
||||
{:reject, nil}
|
||||
|
||||
{:transmogrifier, _} ->
|
||||
{:error, "Transmogrifier failure."}
|
||||
{:transmogrifier, _} = e ->
|
||||
{:error, e}
|
||||
|
||||
{:object, data, nil} ->
|
||||
reinject_object(%Object{}, data)
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
img_src = "img-src 'self' data: blob:"
|
||||
media_src = "media-src 'self'"
|
||||
|
||||
# Strict multimedia CSP enforcement only when MediaProxy is enabled
|
||||
{img_src, media_src} =
|
||||
if Config.get([:media_proxy, :enabled]) &&
|
||||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
||||
sources = get_proxy_and_attachment_sources()
|
||||
sources = build_csp_multimedia_source_list()
|
||||
{[img_src, sources], [media_src, sources]}
|
||||
else
|
||||
{[img_src, " https:"], [media_src, " https:"]}
|
||||
|
|
@ -81,14 +82,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
|
||||
|
||||
connect_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
if Config.get(:env) == :dev do
|
||||
[connect_src, " http://localhost:3035/"]
|
||||
else
|
||||
connect_src
|
||||
end
|
||||
|
||||
script_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
if Config.get(:env) == :dev do
|
||||
"script-src 'self' 'unsafe-eval'"
|
||||
else
|
||||
"script-src 'self'"
|
||||
|
|
@ -107,29 +108,28 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
|> :erlang.iolist_to_binary()
|
||||
end
|
||||
|
||||
defp get_proxy_and_attachment_sources do
|
||||
defp build_csp_multimedia_source_list do
|
||||
media_proxy_whitelist =
|
||||
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
|
||||
add_source(acc, host)
|
||||
end)
|
||||
|
||||
media_proxy_base_url =
|
||||
if Config.get([:media_proxy, :base_url]),
|
||||
do: URI.parse(Config.get([:media_proxy, :base_url])).host
|
||||
media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
|
||||
|
||||
upload_base_url =
|
||||
if Config.get([Pleroma.Upload, :base_url]),
|
||||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
|
||||
upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
|
||||
|
||||
s3_endpoint =
|
||||
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
|
||||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
|
||||
s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
|
||||
|
||||
captcha_method = Config.get([Pleroma.Captcha, :method])
|
||||
|
||||
captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))
|
||||
|
||||
[]
|
||||
|> add_source(media_proxy_base_url)
|
||||
|> add_source(upload_base_url)
|
||||
|> add_source(s3_endpoint)
|
||||
|> add_source(media_proxy_whitelist)
|
||||
|> add_source(captcha_endpoint)
|
||||
end
|
||||
|
||||
defp add_source(iodata, nil), do: iodata
|
||||
|
|
@ -139,6 +139,16 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
|
||||
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
|
||||
|
||||
defp build_csp_param(nil), do: nil
|
||||
|
||||
defp build_csp_param(url) when is_binary(url) do
|
||||
%{host: host, scheme: scheme} = URI.parse(url)
|
||||
|
||||
if scheme do
|
||||
[scheme, "://", host]
|
||||
end
|
||||
end
|
||||
|
||||
def warn_if_disabled do
|
||||
unless Config.get([:http_security, :enabled]) do
|
||||
Logger.warn("
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Plugs.StaticFEPlug do
|
|||
def init(options), do: options
|
||||
|
||||
def call(conn, _) do
|
||||
if enabled?() and accepts_html?(conn) do
|
||||
if enabled?() and requires_html?(conn) do
|
||||
conn
|
||||
|> StaticFEController.call(:show)
|
||||
|> halt()
|
||||
|
|
@ -20,10 +20,7 @@ defmodule Pleroma.Plugs.StaticFEPlug do
|
|||
|
||||
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
|
||||
|
||||
defp accepts_html?(conn) do
|
||||
case get_req_header(conn, "accept") do
|
||||
[accept | _] -> String.contains?(accept, "text/html")
|
||||
_ -> false
|
||||
end
|
||||
defp requires_html?(conn) do
|
||||
Phoenix.Controller.get_format(conn) == "html"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy do
|
||||
@range_headers ~w(range if-range)
|
||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
|
||||
~w(if-unmodified-since if-none-match if-range range)
|
||||
~w(if-unmodified-since if-none-match) ++ @range_headers
|
||||
@resp_cache_headers ~w(etag date last-modified)
|
||||
@keep_resp_headers @resp_cache_headers ++
|
||||
~w(content-type content-disposition content-encoding content-range) ++
|
||||
~w(accept-ranges vary)
|
||||
~w(content-length content-type content-disposition content-encoding) ++
|
||||
~w(content-range accept-ranges vary)
|
||||
@default_cache_control_header "public, max-age=1209600"
|
||||
@valid_resp_codes [200, 206, 304]
|
||||
@max_read_duration :timer.seconds(30)
|
||||
|
|
@ -170,6 +171,8 @@ defmodule Pleroma.ReverseProxy do
|
|||
end
|
||||
|
||||
defp response(conn, client, url, status, headers, opts) do
|
||||
Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}")
|
||||
|
||||
result =
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|
|
@ -220,7 +223,9 @@ defmodule Pleroma.ReverseProxy do
|
|||
end
|
||||
end
|
||||
|
||||
defp head_response(conn, _url, code, headers, opts) do
|
||||
defp head_response(conn, url, code, headers, opts) do
|
||||
Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}")
|
||||
|
||||
conn
|
||||
|> put_resp_headers(build_resp_headers(headers, opts))
|
||||
|> send_resp(code, "")
|
||||
|
|
@ -262,20 +267,33 @@ defmodule Pleroma.ReverseProxy do
|
|||
headers
|
||||
|> downcase_headers()
|
||||
|> Enum.filter(fn {k, _} -> k in @keep_req_headers end)
|
||||
|> (fn headers ->
|
||||
headers = headers ++ Keyword.get(opts, :req_headers, [])
|
||||
|> build_req_range_or_encoding_header(opts)
|
||||
|> build_req_user_agent_header(opts)
|
||||
|> Keyword.merge(Keyword.get(opts, :req_headers, []))
|
||||
end
|
||||
|
||||
if Keyword.get(opts, :keep_user_agent, false) do
|
||||
List.keystore(
|
||||
headers,
|
||||
"user-agent",
|
||||
0,
|
||||
{"user-agent", Pleroma.Application.user_agent()}
|
||||
)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end).()
|
||||
# Disable content-encoding if any @range_headers are requested (see #1823).
|
||||
defp build_req_range_or_encoding_header(headers, _opts) do
|
||||
range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end)
|
||||
|
||||
if range? && List.keymember?(headers, "accept-encoding", 0) do
|
||||
List.keydelete(headers, "accept-encoding", 0)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp build_req_user_agent_header(headers, opts) do
|
||||
if Keyword.get(opts, :keep_user_agent, false) do
|
||||
List.keystore(
|
||||
headers,
|
||||
"user-agent",
|
||||
0,
|
||||
{"user-agent", Pleroma.Application.user_agent()}
|
||||
)
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp build_resp_headers(headers, opts) do
|
||||
|
|
@ -283,7 +301,7 @@ defmodule Pleroma.ReverseProxy do
|
|||
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|
||||
|> build_resp_cache_headers(opts)
|
||||
|> build_resp_content_disposition_header(opts)
|
||||
|> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
|
||||
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
|
||||
end
|
||||
|
||||
defp build_resp_cache_headers(headers, _opts) do
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ defmodule Pleroma.Upload do
|
|||
with {:ok, upload} <- prepare_upload(upload, opts),
|
||||
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
|
||||
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
|
||||
description = Map.get(opts, :description) || upload.name,
|
||||
{_, true} <-
|
||||
{:description_limit,
|
||||
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
|
||||
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
|
||||
{:ok,
|
||||
%{
|
||||
|
|
@ -75,9 +79,12 @@ defmodule Pleroma.Upload do
|
|||
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
||||
}
|
||||
],
|
||||
"name" => Map.get(opts, :description) || upload.name
|
||||
"name" => description
|
||||
}}
|
||||
else
|
||||
{:description_limit, _} ->
|
||||
{:error, :description_too_long}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
|
||||
|
|
|
|||
18
lib/pleroma/upload/filter/exiftool.ex
Normal file
18
lib/pleroma/upload/filter/exiftool.ex
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exiftool do
|
||||
@moduledoc """
|
||||
Strips GPS related EXIF tags and overwrites the file in place.
|
||||
Also strips or replaces filesystem metadata e.g., timestamps.
|
||||
"""
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true)
|
||||
:ok
|
||||
end
|
||||
|
||||
def filter(_), do: :ok
|
||||
end
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.User do
|
|||
field(:keys, :string)
|
||||
field(:public_key, :string)
|
||||
field(:ap_id, :string)
|
||||
field(:avatar, :map)
|
||||
field(:avatar, :map, default: %{})
|
||||
field(:local, :boolean, default: true)
|
||||
field(:follower_address, :string)
|
||||
field(:following_address, :string)
|
||||
|
|
@ -115,7 +115,7 @@ defmodule Pleroma.User do
|
|||
field(:is_moderator, :boolean, default: false)
|
||||
field(:is_admin, :boolean, default: false)
|
||||
field(:show_role, :boolean, default: true)
|
||||
field(:settings, :map, default: nil)
|
||||
field(:mastofe_settings, :map, default: nil)
|
||||
field(:uri, ObjectValidators.Uri, default: nil)
|
||||
field(:hide_followers_count, :boolean, default: false)
|
||||
field(:hide_follows_count, :boolean, default: false)
|
||||
|
|
@ -138,6 +138,7 @@ defmodule Pleroma.User do
|
|||
field(:also_known_as, {:array, :string}, default: [])
|
||||
field(:inbox, :string)
|
||||
field(:shared_inbox, :string)
|
||||
field(:accepts_chat_messages, :boolean, default: nil)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
|
|
@ -388,8 +389,8 @@ defmodule Pleroma.User do
|
|||
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)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
name =
|
||||
case params[:name] do
|
||||
|
|
@ -436,7 +437,8 @@ defmodule Pleroma.User do
|
|||
:discoverable,
|
||||
:invisible,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
:also_known_as,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
)
|
||||
|> validate_required([:name, :ap_id])
|
||||
|
|
@ -448,8 +450,8 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
struct
|
||||
|> cast(
|
||||
|
|
@ -481,7 +483,8 @@ defmodule Pleroma.User do
|
|||
:pleroma_settings_store,
|
||||
:discoverable,
|
||||
:actor_type,
|
||||
:also_known_as
|
||||
:also_known_as,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
)
|
||||
|> unique_constraint(:nickname)
|
||||
|
|
@ -527,11 +530,21 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp put_emoji(changeset) do
|
||||
bio = get_change(changeset, :bio)
|
||||
name = get_change(changeset, :name)
|
||||
emojified_fields = [:bio, :name, :raw_fields]
|
||||
|
||||
if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
|
||||
bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
|
||||
name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
|
||||
|
||||
emoji = Map.merge(bio, name)
|
||||
|
||||
emoji =
|
||||
changeset
|
||||
|> get_field(:raw_fields)
|
||||
|> Enum.reduce(emoji, fn x, acc ->
|
||||
Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
|
||||
end)
|
||||
|
||||
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
|
||||
|
|
@ -539,14 +552,11 @@ defmodule Pleroma.User do
|
|||
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
|
||||
put_change(changeset, map_field, new_value)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
with {:ok, value} <- fetch_change(changeset, map_field),
|
||||
{:ok, new_value} <- value_function.(value) do
|
||||
put_change(changeset, map_field, new_value)
|
||||
else
|
||||
changeset
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -621,12 +631,13 @@ defmodule Pleroma.User do
|
|||
def force_password_reset(user), do: update_password_reset_pending(user, true)
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
||||
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||
|
||||
need_confirmation? =
|
||||
if is_nil(opts[:need_confirmation]) do
|
||||
Pleroma.Config.get([:instance, :account_activation_required])
|
||||
Config.get([:instance, :account_activation_required])
|
||||
else
|
||||
opts[:need_confirmation]
|
||||
end
|
||||
|
|
@ -641,13 +652,14 @@ defmodule Pleroma.User do
|
|||
:nickname,
|
||||
:password,
|
||||
:password_confirmation,
|
||||
:emoji
|
||||
:emoji,
|
||||
:accepts_chat_messages
|
||||
])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|
|
@ -662,7 +674,7 @@ defmodule Pleroma.User do
|
|||
def maybe_validate_required_email(changeset, true), do: changeset
|
||||
|
||||
def maybe_validate_required_email(changeset, _) do
|
||||
if Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
validate_required(changeset, [:email])
|
||||
else
|
||||
changeset
|
||||
|
|
@ -682,7 +694,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp autofollow_users(user) do
|
||||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||
candidates = Config.get([:instance, :autofollowed_nicknames])
|
||||
|
||||
autofollowed_users =
|
||||
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|
||||
|
|
@ -709,7 +721,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def try_send_confirmation_email(%User{} = user) do
|
||||
if user.confirmation_pending &&
|
||||
Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
Config.get([:instance, :account_activation_required]) do
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.account_confirmation_email()
|
||||
|> Pleroma.Emails.Mailer.deliver_async()
|
||||
|
|
@ -766,7 +778,7 @@ defmodule Pleroma.User do
|
|||
defdelegate following(user), to: FollowingRelationship
|
||||
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
cond do
|
||||
followed.deactivated ->
|
||||
|
|
@ -967,7 +979,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
|
||||
restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
|
||||
restrict_to_local = Config.get([:instance, :limit_to_local_content])
|
||||
|
||||
cond do
|
||||
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
|
||||
|
|
@ -1163,7 +1175,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec update_follower_count(User.t()) :: {:ok, User.t()}
|
||||
def update_follower_count(%User{} = user) do
|
||||
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
if user.local or !Config.get([:instance, :external_user_synchronization]) do
|
||||
follower_count = FollowingRelationship.follower_count(user)
|
||||
|
||||
user
|
||||
|
|
@ -1176,7 +1188,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec update_following_count(User.t()) :: {:ok, User.t()}
|
||||
def update_following_count(%User{local: false} = user) do
|
||||
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
if Config.get([:instance, :external_user_synchronization]) do
|
||||
{:ok, maybe_fetch_follow_information(user)}
|
||||
else
|
||||
{:ok, user}
|
||||
|
|
@ -1263,7 +1275,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
if blocks?(target, subscriber) and deny_follow_blocked do
|
||||
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
|
||||
|
|
@ -1309,7 +1321,8 @@ defmodule Pleroma.User do
|
|||
|
||||
unsubscribe(blocked, blocker)
|
||||
|
||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
|
||||
if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||
|
|
@ -1527,8 +1540,7 @@ defmodule Pleroma.User do
|
|||
blocked_identifiers,
|
||||
fn blocked_identifier ->
|
||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||
{:ok, _user_block} <- block(blocker, blocked),
|
||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||
{:ok, _block} <- CommonAPI.block(blocker, blocked) do
|
||||
blocked
|
||||
else
|
||||
err ->
|
||||
|
|
@ -1546,7 +1558,7 @@ defmodule Pleroma.User do
|
|||
fn followed_identifier ->
|
||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
|
||||
followed
|
||||
else
|
||||
err ->
|
||||
|
|
@ -1654,7 +1666,7 @@ defmodule Pleroma.User do
|
|||
Pleroma.HTML.Scrubber.TwitterText
|
||||
end
|
||||
|
||||
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
|
||||
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
|
||||
|
||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
|
|
@ -1836,7 +1848,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp local_nickname_regex do
|
||||
if Pleroma.Config.get([:instance, :extended_nickname_format]) do
|
||||
if Config.get([:instance, :extended_nickname_format]) do
|
||||
@extended_local_nickname_regex
|
||||
else
|
||||
@strict_local_nickname_regex
|
||||
|
|
@ -1964,8 +1976,8 @@ defmodule Pleroma.User do
|
|||
|
||||
def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
|
||||
# use instance-default
|
||||
config = Pleroma.Config.get([:assets, :mascots])
|
||||
default_mascot = Pleroma.Config.get([:assets, :default_mascot])
|
||||
config = Config.get([:assets, :mascots])
|
||||
default_mascot = Config.get([:assets, :default_mascot])
|
||||
mascot = Keyword.get(config, default_mascot)
|
||||
|
||||
%{
|
||||
|
|
@ -2060,7 +2072,7 @@ defmodule Pleroma.User do
|
|||
|
||||
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)
|
||||
limit = Config.get([:instance, limit_name], 0)
|
||||
|
||||
changeset
|
||||
|> validate_length(:fields, max: limit)
|
||||
|
|
@ -2074,8 +2086,8 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp valid_field?(%{"name" => name, "value" => value}) do
|
||||
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
|
||||
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
|
||||
name_limit = Config.get([:instance, :account_field_name_length], 255)
|
||||
value_limit = Config.get([:instance, :account_field_value_length], 255)
|
||||
|
||||
is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
|
||||
String.length(value) <= value_limit
|
||||
|
|
@ -2085,10 +2097,10 @@ defmodule Pleroma.User do
|
|||
|
||||
defp truncate_field(%{"name" => name, "value" => value}) do
|
||||
{name, _chopped} =
|
||||
String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
|
||||
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
||||
|
||||
{value, _chopped} =
|
||||
String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
|
||||
String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
|
||||
|
||||
%{"name" => name, "value" => value}
|
||||
end
|
||||
|
|
@ -2118,8 +2130,8 @@ defmodule Pleroma.User do
|
|||
|
||||
def mastodon_settings_update(user, settings) do
|
||||
user
|
||||
|> cast(%{settings: settings}, [:settings])
|
||||
|> validate_required([:settings])
|
||||
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|
||||
|> validate_required([:mastofe_settings])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
|
|
@ -2143,7 +2155,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
|
||||
if id not in user.pinned_activities do
|
||||
max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
|
||||
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
||||
params = %{pinned_activities: user.pinned_activities ++ [id]}
|
||||
|
||||
user
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ defmodule Pleroma.User.Search do
|
|||
|> base_query(following)
|
||||
|> filter_blocked_user(for_user)
|
||||
|> filter_invisible_users()
|
||||
|> filter_internal_users()
|
||||
|> filter_blocked_domains(for_user)
|
||||
|> fts_search(query_string)
|
||||
|> trigram_rank(query_string)
|
||||
|
|
@ -68,11 +69,15 @@ defmodule Pleroma.User.Search do
|
|||
u in query,
|
||||
where:
|
||||
fragment(
|
||||
# The fragment must _exactly_ match `users_fts_index`, otherwise the index won't work
|
||||
"""
|
||||
(to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
|
||||
(
|
||||
setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
|
||||
setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')
|
||||
) @@ to_tsquery('simple', ?)
|
||||
""",
|
||||
u.name,
|
||||
u.nickname,
|
||||
u.name,
|
||||
^query_string
|
||||
)
|
||||
)
|
||||
|
|
@ -87,15 +92,23 @@ defmodule Pleroma.User.Search do
|
|||
|> Enum.join(" | ")
|
||||
end
|
||||
|
||||
# Considers nickname match, localized nickname match, name match; preferences nickname match
|
||||
defp trigram_rank(query, query_string) do
|
||||
from(
|
||||
u in query,
|
||||
select_merge: %{
|
||||
search_rank:
|
||||
fragment(
|
||||
"similarity(?, trim(? || ' ' || coalesce(?, '')))",
|
||||
"""
|
||||
similarity(?, ?) +
|
||||
similarity(?, regexp_replace(?, '@.+', '')) +
|
||||
similarity(?, trim(coalesce(?, '')))
|
||||
""",
|
||||
^query_string,
|
||||
u.nickname,
|
||||
^query_string,
|
||||
u.nickname,
|
||||
^query_string,
|
||||
u.name
|
||||
)
|
||||
}
|
||||
|
|
@ -109,6 +122,10 @@ defmodule Pleroma.User.Search do
|
|||
from(q in query, where: q.invisible == false)
|
||||
end
|
||||
|
||||
defp filter_internal_users(query) do
|
||||
from(q in query, where: q.actor_type != "Application")
|
||||
end
|
||||
|
||||
defp filter_blocked_user(query, %User{} = blocker) do
|
||||
query
|
||||
|> join(:left, [u], b in Pleroma.UserRelationship,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Constants
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
|
|
@ -321,28 +322,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
|
||||
with {:ok, result} <-
|
||||
Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_follow(follower, followed, activity_id, local, opts) do
|
||||
skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
|
||||
data = make_follow_data(follower, followed, activity_id)
|
||||
|
||||
with {:ok, activity} <- insert(data, local),
|
||||
_ <- skip_notify_and_stream || notify_and_stream(activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, error} -> Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t()} | nil | {:error, any()}
|
||||
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||
|
|
@ -366,33 +345,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
||||
with {:ok, result} <-
|
||||
Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_block(blocker, blocked, activity_id, local) do
|
||||
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
|
||||
|
||||
if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
|
||||
unfollow(blocker, blocked, nil, local)
|
||||
end
|
||||
|
||||
block_data = make_block_data(blocker, blocked, activity_id)
|
||||
|
||||
with {:ok, activity} <- insert(block_data, local),
|
||||
_ <- notify_and_stream(activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, error} -> Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def flag(
|
||||
%{
|
||||
|
|
@ -473,6 +425,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|> restrict_filtered(opts)
|
||||
|> where(
|
||||
[activity],
|
||||
fragment(
|
||||
|
|
@ -988,6 +941,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_instance(query, _), do: query
|
||||
|
||||
defp restrict_filtered(query, %{user: %User{} = user}) do
|
||||
case Filter.compose_regex(user) do
|
||||
nil ->
|
||||
query
|
||||
|
||||
regex ->
|
||||
from([activity, object] in query,
|
||||
where:
|
||||
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
|
||||
activity.actor == ^user.ap_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
|
||||
restrict_filtered(query, %{user: user})
|
||||
end
|
||||
|
||||
defp restrict_filtered(query, _), do: query
|
||||
|
||||
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
|
||||
|
||||
defp exclude_poll_votes(query, _) do
|
||||
|
|
@ -1118,6 +1091,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_muted(restrict_muted_opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts, config)
|
||||
|
|
@ -1126,6 +1100,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||
|> restrict_instance(opts)
|
||||
|> restrict_announce_object_actor(opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> exclude_poll_votes(opts)
|
||||
|> exclude_chat_messages(opts)
|
||||
|
|
@ -1251,6 +1226,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end)
|
||||
|
||||
locked = data["manuallyApprovesFollowers"] || false
|
||||
capabilities = data["capabilities"] || %{}
|
||||
accepts_chat_messages = capabilities["acceptsChatMessages"]
|
||||
data = Transmogrifier.maybe_fix_user_object(data)
|
||||
discoverable = data["discoverable"] || false
|
||||
invisible = data["invisible"] || false
|
||||
|
|
@ -1289,7 +1266,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
also_known_as: Map.get(data, "alsoKnownAs", []),
|
||||
public_key: public_key,
|
||||
inbox: data["inbox"],
|
||||
shared_inbox: shared_inbox
|
||||
shared_inbox: shared_inbox,
|
||||
accepts_chat_messages: accepts_chat_messages
|
||||
}
|
||||
|
||||
# nickname can be nil because of virtual actors
|
||||
|
|
@ -1398,6 +1376,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def maybe_handle_clashing_nickname(data) do
|
||||
nickname = data[:nickname]
|
||||
|
||||
with %User{} = old_user <- User.get_by_nickname(nickname),
|
||||
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
|
||||
Logger.info(
|
||||
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
|
||||
data[:ap_id]
|
||||
}, renaming."
|
||||
)
|
||||
|
||||
old_user
|
||||
|> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"})
|
||||
|> User.update_and_set_cache()
|
||||
else
|
||||
{:ap_id_comparison, true} ->
|
||||
Logger.info(
|
||||
"Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
|
||||
)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def make_user_from_ap_id(ap_id) do
|
||||
user = User.get_cached_by_ap_id(ap_id)
|
||||
|
||||
|
|
@ -1410,6 +1413,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> User.remote_user_changeset(data)
|
||||
|> User.update_and_set_cache()
|
||||
else
|
||||
maybe_handle_clashing_nickname(data)
|
||||
|
||||
data
|
||||
|> User.remote_user_changeset()
|
||||
|> Repo.insert()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def follow(follower, followed) do
|
||||
data = %{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"actor" => follower.ap_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed.ap_id,
|
||||
"to" => [followed.ap_id]
|
||||
}
|
||||
|
||||
{:ok, data, []}
|
||||
end
|
||||
|
||||
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
||||
def emoji_react(actor, object, emoji) do
|
||||
with {:ok, data, meta} <- object_action(actor, object) do
|
||||
|
|
@ -138,6 +151,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
}, []}
|
||||
end
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def block(blocker, blocked) do
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"type" => "Block",
|
||||
"actor" => blocker.ap_id,
|
||||
"object" => blocked.ap_id,
|
||||
"to" => [blocked.ap_id]
|
||||
}, []}
|
||||
end
|
||||
|
||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||
def announce(actor, object, options \\ []) do
|
||||
public? = Keyword.get(options, :public, false)
|
||||
|
|
|
|||
|
|
@ -27,11 +27,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
|||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||
{:old_user, true} <- {:old_user, old_user?(u)} do
|
||||
{:ok, message}
|
||||
else
|
||||
{:ok, %User{local: true}} ->
|
||||
{:ok, message}
|
||||
|
||||
{:contains_links, false} ->
|
||||
{:ok, message}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
|
|||
@impl true
|
||||
def describe do
|
||||
mrf_object_age =
|
||||
Pleroma.Config.get(:mrf_object_age)
|
||||
Config.get(:mrf_object_age)
|
||||
|> Enum.into(%{})
|
||||
|
||||
{:ok, %{mrf_object_age: mrf_object_age}}
|
||||
|
|
|
|||
|
|
@ -47,5 +47,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
|
||||
@impl true
|
||||
def describe,
|
||||
do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
%{host: actor_host} = URI.parse(actor)
|
||||
|
||||
reject_deletes =
|
||||
Pleroma.Config.get([:mrf_simple, :reject_deletes])
|
||||
Config.get([:mrf_simple, :reject_deletes])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(reject_deletes, actor_host) do
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
|
|
@ -24,6 +26,35 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => "Follow"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> FollowValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||
with {:ok, block_activity} <-
|
||||
block_activity
|
||||
|> BlockValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
block_activity = stringify_keys(block_activity)
|
||||
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
|
||||
|
||||
meta =
|
||||
if !outgoing_blocks do
|
||||
Keyword.put(meta, :do_not_federate, true)
|
||||
else
|
||||
meta
|
||||
end
|
||||
|
||||
{:ok, block_activity, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Update"} = update_activity, meta) do
|
||||
with {:ok, update_activity} <-
|
||||
update_activity
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Block"])
|
||||
|> validate_actor_presence()
|
||||
|> validate_actor_presence(field_name: :object)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> validate_data
|
||||
end
|
||||
end
|
||||
|
|
@ -93,12 +93,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
|||
- If both users are in our system
|
||||
- If at least one of the users in this ChatMessage is a local user
|
||||
- If the recipient is not blocking the actor
|
||||
- If the recipient is explicitly not accepting chat messages
|
||||
"""
|
||||
def validate_local_concern(cng) do
|
||||
with actor_ap <- get_field(cng, :actor),
|
||||
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
|
||||
{_, %User{} = recipient} <-
|
||||
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
|
||||
{_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
|
||||
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
|
||||
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
|
||||
cng
|
||||
|
|
@ -107,6 +109,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
|||
cng
|
||||
|> add_error(:actor, "actor is blocked by recipient")
|
||||
|
||||
{:not_accepting_chats?, true} ->
|
||||
cng
|
||||
|> add_error(:to, "recipient does not accept chat messages")
|
||||
|
||||
{:local?, false} ->
|
||||
cng
|
||||
|> add_error(:actor, "actor and recipient are both remote")
|
||||
|
|
|
|||
|
|
@ -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.ActivityPub.ObjectValidators.FollowValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:state, :string, default: "pending")
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Follow"])
|
||||
|> validate_inclusion(:state, ~w{pending reject accept})
|
||||
|> validate_actor_presence()
|
||||
|> validate_actor_presence(field_name: :object)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> validate_data
|
||||
end
|
||||
end
|
||||
|
|
@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
def follow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
{:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
collection, and so on.
|
||||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Activity.Ir.Topics
|
||||
alias Pleroma.Chat
|
||||
alias Pleroma.Chat.MessageReference
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -20,6 +22,84 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Tasks this handle
|
||||
# - Follows if possible
|
||||
# - Sends a notification
|
||||
# - Generates accept or reject if appropriate
|
||||
def handle(
|
||||
%{
|
||||
data: %{
|
||||
"id" => follow_id,
|
||||
"type" => "Follow",
|
||||
"object" => followed_user,
|
||||
"actor" => following_user
|
||||
}
|
||||
} = object,
|
||||
meta
|
||||
) do
|
||||
with %User{} = follower <- User.get_cached_by_ap_id(following_user),
|
||||
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
||||
{_, {:ok, _}, _, _} <-
|
||||
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
|
||||
if followed.local && !followed.locked do
|
||||
Utils.update_follow_state_for_all(object, "accept")
|
||||
FollowingRelationship.update(follower, followed, :follow_accept)
|
||||
User.update_follower_count(followed)
|
||||
User.update_following_count(follower)
|
||||
|
||||
%{
|
||||
to: [following_user],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.accept()
|
||||
end
|
||||
else
|
||||
{:following, {:error, _}, follower, followed} ->
|
||||
Utils.update_follow_state_for_all(object, "reject")
|
||||
FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
if followed.local do
|
||||
%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_id,
|
||||
local: true
|
||||
}
|
||||
|> ActivityPub.reject()
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{:ok, notifications} = Notification.create_notifications(object, do_send: false)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> add_notifications(notifications)
|
||||
|
||||
updated_object = Activity.get_by_ap_id(follow_id)
|
||||
|
||||
{:ok, updated_object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Unfollow and block
|
||||
def handle(
|
||||
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
|
||||
object,
|
||||
meta
|
||||
) do
|
||||
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
|
||||
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
|
||||
User.block(blocker, blocked)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Update the user
|
||||
#
|
||||
|
|
@ -82,7 +162,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
if !User.is_internal_user?(user) do
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
|
||||
object
|
||||
|> Topics.get_activity_topics()
|
||||
|> Streamer.stream(object)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
|
|
@ -190,14 +273,20 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
{:ok, object}
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
||||
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
|
||||
{:ok, _} <- Utils.remove_like_from_object(object, liked_object),
|
||||
{:ok, _} <- Repo.delete(object) do
|
||||
:ok
|
||||
defp undo_like(nil, object), do: delete_object(object)
|
||||
|
||||
defp undo_like(%Object{} = liked_object, object) do
|
||||
with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
|
||||
delete_object(object)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
||||
object.data["object"]
|
||||
|> Object.get_by_ap_id()
|
||||
|> undo_like(object)
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
|
||||
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
|
||||
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
|
||||
|
|
@ -227,6 +316,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
|
||||
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
|
||||
|
||||
@spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
|
||||
defp delete_object(object) do
|
||||
with {:ok, _} <- Repo.delete(object), do: :ok
|
||||
end
|
||||
|
||||
defp send_notifications(meta) do
|
||||
Keyword.get(meta, :notifications, [])
|
||||
|> Enum.each(fn notification ->
|
||||
|
|
|
|||
|
|
@ -233,18 +233,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
||||
is_binary(data["url"]) -> data["url"]
|
||||
is_binary(data["href"]) -> data["href"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", Map.get(url || %{}, "type"))
|
||||
if href do
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", Map.get(url || %{}, "type"))
|
||||
|
||||
%{"url" => [attachment_url]}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", data["type"])
|
||||
|> Maps.put_if_present("name", data["name"])
|
||||
%{"url" => [attachment_url]}
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", data["type"])
|
||||
|> Maps.put_if_present("name", data["name"])
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
end
|
||||
|
|
@ -263,12 +269,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
def fix_url(%{"type" => object_type, "url" => url} = object)
|
||||
when object_type in ["Video", "Audio"] and is_list(url) do
|
||||
first_element = Enum.at(url, 0)
|
||||
attachment =
|
||||
Enum.find(url, fn x ->
|
||||
media_type = x["mediaType"] || x["mimeType"] || ""
|
||||
|
||||
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
||||
is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
|
||||
end)
|
||||
|
||||
link_element =
|
||||
Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", [first_element])
|
||||
|> Map.put("attachment", [attachment])
|
||||
|> Map.put("url", link_element["href"])
|
||||
end
|
||||
|
||||
|
|
@ -446,12 +458,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
|
||||
actor = Containment.get_actor(data)
|
||||
|
||||
data =
|
||||
Map.put(data, "actor", actor)
|
||||
|> fix_addressing
|
||||
|
||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
|
||||
data <- Map.put(data, "actor", actor) |> fix_addressing() do
|
||||
object = fix_object(object, options)
|
||||
|
||||
params = %{
|
||||
|
|
@ -520,66 +529,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
|
||||
_options
|
||||
) do
|
||||
with %User{local: true} = followed <-
|
||||
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
|
||||
{:ok, %User{} = follower} <-
|
||||
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||
{_, {: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, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
else
|
||||
{:user_blocked, true} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:follow, {:error, _}} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
{:user_locked, true} ->
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
|
||||
:noop
|
||||
end
|
||||
|
||||
ActivityPub.notify_and_stream(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
|
||||
_options
|
||||
|
|
@ -673,7 +622,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def handle_incoming(%{"type" => type} = data, _options)
|
||||
when type in ["Like", "EmojiReact", "Announce"] do
|
||||
when type in ~w{Like EmojiReact Announce} do
|
||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||
{:ok, activity, _meta} <-
|
||||
Pipeline.common_pipeline(data, local: false) do
|
||||
|
|
@ -684,9 +633,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Update"} = data,
|
||||
%{"type" => type} = data,
|
||||
_options
|
||||
) do
|
||||
)
|
||||
when type in ~w{Update Block Follow} do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
|
@ -765,21 +715,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
||||
_options
|
||||
) do
|
||||
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||
User.unfollow(blocker, blocked)
|
||||
User.block(blocker, blocked)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Move",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,15 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
|
||||
|
||||
capabilities =
|
||||
if is_boolean(user.accepts_chat_messages) do
|
||||
%{
|
||||
"acceptsChatMessages" => user.accepts_chat_messages
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
"type" => user.actor_type,
|
||||
|
|
@ -101,7 +110,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"endpoints" => endpoints,
|
||||
"attachment" => fields,
|
||||
"tag" => emoji_tags,
|
||||
"discoverable" => user.discoverable
|
||||
"discoverable" => user.discoverable,
|
||||
"capabilities" => capabilities
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
|
||||
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
||||
|
||||
def visible_for_user?(nil, _), do: false
|
||||
|
||||
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
||||
|
||||
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
||||
user.ap_id in activity.data["to"] ||
|
||||
list_ap_id
|
||||
|
|
@ -54,8 +58,6 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
|||
|> Pleroma.List.member?(user)
|
||||
end
|
||||
|
||||
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
||||
|
||||
def visible_for_user?(%{local: local} = activity, nil) do
|
||||
cfg_key =
|
||||
if local,
|
||||
|
|
|
|||
|
|
@ -206,8 +206,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def user_show(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("show.json", %{user: user})
|
||||
|
|
@ -233,11 +233,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
end
|
||||
|
||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
|
||||
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
{_, page_size} = page_params(params)
|
||||
|
||||
activities =
|
||||
|
|
@ -526,7 +526,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
@doc "Show a given user's credentials"
|
||||
def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("credentials.json", %{user: user, for: admin})
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
|
|||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
||||
@descriptions Pleroma.Docs.JSON.compile()
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
|
||||
|
||||
|
|
@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
|
|||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
|
||||
|
||||
def descriptions(conn, _params) do
|
||||
descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
|
||||
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
|
||||
|
||||
json(conn, descriptions)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
|||
|> List.first()
|
||||
|
||||
_ ->
|
||||
nil
|
||||
"application/json"
|
||||
end
|
||||
|
||||
private_data = Map.put(private_data, :operation_id, operation_id)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
description: "Update the user's display and preferences.",
|
||||
operationId: "AccountController.update_credentials",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
requestBody: request_body("Parameters", update_creadentials_request(), required: true),
|
||||
requestBody: request_body("Parameters", update_credentials_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Account", "application/json", Account),
|
||||
403 => Operation.response("Error", "application/json", ApiError)
|
||||
|
|
@ -203,14 +203,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
security: [%{"oAuth" => ["follow", "write:follows"]}],
|
||||
description: "Follow the given account",
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
|
||||
Operation.parameter(
|
||||
:reblogs,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Receive this account's reblogs in home timeline? Defaults to true."
|
||||
)
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
|
||||
],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
reblogs: %Schema{
|
||||
type: :boolean,
|
||||
description: "Receive this account's reblogs in home timeline? Defaults to true.",
|
||||
default: true
|
||||
}
|
||||
}
|
||||
},
|
||||
required: false
|
||||
),
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
|
|
@ -438,6 +447,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
}
|
||||
end
|
||||
|
||||
# TODO: This is actually a token respone, but there's no oauth operation file yet.
|
||||
defp create_response do
|
||||
%Schema{
|
||||
title: "AccountCreateResponse",
|
||||
|
|
@ -446,19 +456,25 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
properties: %{
|
||||
token_type: %Schema{type: :string},
|
||||
access_token: %Schema{type: :string},
|
||||
scope: %Schema{type: :array, items: %Schema{type: :string}},
|
||||
created_at: %Schema{type: :integer, format: :"date-time"}
|
||||
refresh_token: %Schema{type: :string},
|
||||
scope: %Schema{type: :string},
|
||||
created_at: %Schema{type: :integer, format: :"date-time"},
|
||||
me: %Schema{type: :string},
|
||||
expires_in: %Schema{type: :integer}
|
||||
},
|
||||
example: %{
|
||||
"token_type" => "Bearer",
|
||||
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
|
||||
"refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
|
||||
"created_at" => 1_585_918_714,
|
||||
"scope" => ["read", "write", "follow", "push"],
|
||||
"token_type" => "Bearer"
|
||||
"expires_in" => 600,
|
||||
"scope" => "read write follow push",
|
||||
"me" => "https://gensokyo.2hu/users/raymoo"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_creadentials_request do
|
||||
defp update_credentials_request do
|
||||
%Schema{
|
||||
title: "AccountUpdateCredentialsRequest",
|
||||
description: "POST body for creating an account",
|
||||
|
|
@ -492,6 +508,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
nullable: true,
|
||||
description: "Whether manual approval of follow requests is required."
|
||||
},
|
||||
accepts_chat_messages: %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Whether the user accepts receiving chat messages."
|
||||
},
|
||||
fields_attributes: %Schema{
|
||||
nullable: true,
|
||||
oneOf: [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
|
|
@ -40,48 +39,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def update_avatar_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user avatar image",
|
||||
operationId: "PleromaAPI.AccountController.update_avatar",
|
||||
requestBody:
|
||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
responses: %{
|
||||
200 => update_response(),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_banner_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user banner image",
|
||||
operationId: "PleromaAPI.AccountController.update_banner",
|
||||
requestBody: request_body("Parameters", update_banner_request(), required: true),
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
responses: %{
|
||||
200 => update_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_background_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
summary: "Set/clear user background image",
|
||||
operationId: "PleromaAPI.AccountController.update_background",
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", update_avatar_or_background_request(), required: true),
|
||||
responses: %{
|
||||
200 => update_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def favourites_operation do
|
||||
%Operation{
|
||||
tags: ["Accounts"],
|
||||
|
|
@ -136,52 +93,4 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
|||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
defp update_avatar_or_background_request do
|
||||
%Schema{
|
||||
title: "PleromaAccountUpdateAvatarOrBackgroundRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
img: %Schema{
|
||||
nullable: true,
|
||||
type: :string,
|
||||
format: :binary,
|
||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_banner_request do
|
||||
%Schema{
|
||||
title: "PleromaAccountUpdateBannerRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
banner: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
format: :binary,
|
||||
description: "Image encoded using `multipart/form-data` or an empty string to clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_response do
|
||||
Operation.response("PleromaAccountUpdateResponse", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
nullable: true,
|
||||
description: "Image URL"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"url" =>
|
||||
"https://cofe.party/media/9d0add56-bcb6-4c0f-8225-cbbd0b6dd773/13eadb6972c9ccd3f4ffa3b8196f0e0d38b4d2f27594457c52e52946c054cd9a.gif"
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
operationId: "StatusController.delete",
|
||||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 => empty_object_response(),
|
||||
200 => status_response(),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
allow_following_move: %Schema{type: :boolean},
|
||||
background_image: %Schema{type: :string, nullable: true},
|
||||
allow_following_move: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user allows automatically follow moved following accounts"
|
||||
},
|
||||
background_image: %Schema{type: :string, nullable: true, format: :uri},
|
||||
chat_token: %Schema{type: :string},
|
||||
confirmation_pending: %Schema{type: :boolean},
|
||||
confirmation_pending: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"whether the user account is waiting on email confirmation to be activated"
|
||||
},
|
||||
hide_favorites: %Schema{type: :boolean},
|
||||
hide_followers_count: %Schema{type: :boolean},
|
||||
hide_followers: %Schema{type: :boolean},
|
||||
hide_follows_count: %Schema{type: :boolean},
|
||||
hide_follows: %Schema{type: :boolean},
|
||||
is_admin: %Schema{type: :boolean},
|
||||
is_moderator: %Schema{type: :boolean},
|
||||
hide_followers_count: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user has follower stat hiding enabled"
|
||||
},
|
||||
hide_followers: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user has follower hiding enabled"
|
||||
},
|
||||
hide_follows_count: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user has follow stat hiding enabled"
|
||||
},
|
||||
hide_follows: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user has follow hiding enabled"
|
||||
},
|
||||
is_admin: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user is an admin of the local instance"
|
||||
},
|
||||
is_moderator: %Schema{
|
||||
type: :boolean,
|
||||
description: "whether the user is a moderator of the local instance"
|
||||
},
|
||||
skip_thread_containment: %Schema{type: :boolean},
|
||||
tags: %Schema{type: :array, items: %Schema{type: :string}},
|
||||
unread_conversation_count: %Schema{type: :integer},
|
||||
tags: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
description:
|
||||
"List of tags being used for things like extra roles or moderation(ie. marking all media as nsfw all)."
|
||||
},
|
||||
unread_conversation_count: %Schema{
|
||||
type: :integer,
|
||||
description: "The count of unread conversations. Only returned to the account owner."
|
||||
},
|
||||
notification_settings: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
|
|
@ -63,7 +96,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
},
|
||||
relationship: AccountRelationship,
|
||||
settings_store: %Schema{
|
||||
type: :object
|
||||
type: :object,
|
||||
description:
|
||||
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
||||
},
|
||||
accepts_chat_messages: %Schema{type: :boolean, nullable: true},
|
||||
favicon: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
nullable: true,
|
||||
description: "Favicon image of the user's instance"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -71,16 +113,32 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
type: :object,
|
||||
properties: %{
|
||||
fields: %Schema{type: :array, items: AccountField},
|
||||
note: %Schema{type: :string},
|
||||
note: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Plaintext version of the bio without formatting applied by the backend, used for editing the bio."
|
||||
},
|
||||
privacy: VisibilityScope,
|
||||
sensitive: %Schema{type: :boolean},
|
||||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
actor_type: ActorType,
|
||||
discoverable: %Schema{type: :boolean},
|
||||
no_rich_text: %Schema{type: :boolean},
|
||||
show_role: %Schema{type: :boolean}
|
||||
discoverable: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"whether the user allows discovery of the account in search results and other services."
|
||||
},
|
||||
no_rich_text: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"whether the HTML tags for rich-text formatting are stripped from all statuses requested from the API."
|
||||
},
|
||||
show_role: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"whether the user wants their role (e.g admin, moderator) to be shown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,6 +173,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
"is_admin" => false,
|
||||
"is_moderator" => false,
|
||||
"skip_thread_containment" => false,
|
||||
"accepts_chat_messages" => true,
|
||||
"chat_token" =>
|
||||
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
|
||||
"unread_conversation_count" => 0,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
}
|
||||
},
|
||||
content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"},
|
||||
text: %Schema{
|
||||
type: :string,
|
||||
description: "Original unformatted content in plain text",
|
||||
nullable: true
|
||||
},
|
||||
created_at: %Schema{
|
||||
type: :string,
|
||||
format: "date-time",
|
||||
|
|
@ -184,6 +189,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
thread_muted: %Schema{
|
||||
type: :boolean,
|
||||
description: "`true` if the thread the post belongs to is muted"
|
||||
},
|
||||
parent_visible: %Schema{
|
||||
type: :boolean,
|
||||
description: "`true` if the parent post is visible to the user"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
draft.poll
|
||||
)
|
||||
|> Map.put("emoji", emoji)
|
||||
|> Map.put("source", draft.status)
|
||||
|
||||
%__MODULE__{draft | object: object}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
require Pleroma.Constants
|
||||
require Logger
|
||||
|
||||
def block(blocker, blocked) do
|
||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||
{:ok, block}
|
||||
end
|
||||
end
|
||||
|
||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
||||
|
|
@ -94,10 +101,14 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
def follow(follower, followed) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
||||
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
|
||||
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
|
||||
{:ok, follower, followed, activity}
|
||||
if activity.data["state"] == "reject" do
|
||||
{:error, :rejected}
|
||||
else
|
||||
{:ok, follower, followed, activity}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
|
||||
when is_list(options) do
|
||||
limits = Pleroma.Config.get([:instance, :poll_limits])
|
||||
limits = Config.get([:instance, :poll_limits])
|
||||
|
||||
with :ok <- validate_poll_expiration(expires_in, limits),
|
||||
:ok <- validate_poll_options_amount(options, limits),
|
||||
|
|
@ -502,7 +502,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
|
||||
|
||||
def make_report_content_html(comment) do
|
||||
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
|
||||
max_size = Config.get([:instance, :max_report_comment_size], 1000)
|
||||
|
||||
if String.length(comment) <= max_size do
|
||||
{:ok, format_input(comment, "text/plain")}
|
||||
|
|
@ -564,7 +564,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
|
||||
def validate_character_limit(full_payload, _attachments) do
|
||||
limit = Pleroma.Config.get([:instance, :limit])
|
||||
limit = Config.get([:instance, :limit])
|
||||
length = String.length(full_payload)
|
||||
|
||||
if length <= limit do
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPIController
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
|
||||
|
|
@ -101,12 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
:ok <- TwitterAPI.validate_captcha(app, params),
|
||||
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
|
||||
json(conn, %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
scope: app.scopes,
|
||||
created_at: Token.Utils.format_created_at(token)
|
||||
})
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
{:error, error} -> json_response(conn, :bad_request, %{error: error})
|
||||
end
|
||||
|
|
@ -148,6 +144,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
# We use an empty string as a special value to reset
|
||||
# avatars, banners, backgrounds
|
||||
user_image_value = fn
|
||||
"" -> {:ok, nil}
|
||||
value -> {:ok, value}
|
||||
end
|
||||
|
||||
user_params =
|
||||
[
|
||||
:no_rich_text,
|
||||
|
|
@ -160,7 +163,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
:show_role,
|
||||
:skip_thread_containment,
|
||||
:allow_following_move,
|
||||
:discoverable
|
||||
:discoverable,
|
||||
:accepts_chat_messages
|
||||
]
|
||||
|> Enum.reduce(%{}, fn key, acc ->
|
||||
Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
|
||||
|
|
@ -168,9 +172,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|> Maps.put_if_present(:name, params[:display_name])
|
||||
|> Maps.put_if_present(:bio, params[:note])
|
||||
|> Maps.put_if_present(:raw_bio, params[:note])
|
||||
|> Maps.put_if_present(:avatar, params[:avatar])
|
||||
|> Maps.put_if_present(:banner, params[:header])
|
||||
|> Maps.put_if_present(:background, params[:pleroma_background_image])
|
||||
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|
||||
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|
||||
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|
||||
|> Maps.put_if_present(
|
||||
:raw_fields,
|
||||
params[:fields_attributes],
|
||||
|
|
@ -346,7 +350,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
{:error, "Can not follow yourself"}
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
|
||||
def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do
|
||||
with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
|
||||
render(conn, "relationship.json", user: follower, target: followed)
|
||||
else
|
||||
|
|
@ -385,8 +389,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|
||||
@doc "POST /api/v1/accounts/:id/block"
|
||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||
with {:ok, _user_block} <- User.block(blocker, blocked),
|
||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
|
||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
def search(conn, params), do: do_search(:v1, conn, params)
|
||||
|
||||
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||
query = String.trim(query)
|
||||
options = search_options(params, user)
|
||||
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
||||
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
||||
|
|
|
|||
|
|
@ -200,11 +200,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
@doc "DELETE /api/v1/statuses/:id"
|
||||
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
json(conn, %{})
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
try_render(conn, "show.json",
|
||||
activity: activity,
|
||||
for: user,
|
||||
with_direct_conversation_id: true,
|
||||
with_source: true
|
||||
)
|
||||
else
|
||||
{:error, :not_found} = e -> e
|
||||
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
||||
_e -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -88,21 +88,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
)
|
||||
end
|
||||
|
||||
defp restrict_unauthenticated?(true = _local_only) do
|
||||
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
|
||||
end
|
||||
|
||||
defp restrict_unauthenticated?(_) do
|
||||
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/public
|
||||
def public(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = params[:local]
|
||||
|
||||
cfg_key =
|
||||
if local_only do
|
||||
:local
|
||||
else
|
||||
:federated
|
||||
end
|
||||
|
||||
restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key])
|
||||
|
||||
if restrict? and is_nil(user) do
|
||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||
fail_on_bad_auth(conn)
|
||||
else
|
||||
activities =
|
||||
params
|
||||
|
|
@ -123,6 +122,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
end
|
||||
end
|
||||
|
||||
defp fail_on_bad_auth(conn) do
|
||||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
end
|
||||
|
||||
defp hashtag_fetching(params, user, local_only) do
|
||||
tags =
|
||||
[params[:tag], params[:any]]
|
||||
|
|
@ -157,15 +160,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = params[:local]
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity
|
||||
)
|
||||
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||
fail_on_bad_auth(conn)
|
||||
else
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/list/:list_id
|
||||
|
|
|
|||
|
|
@ -204,6 +204,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
%{}
|
||||
end
|
||||
|
||||
favicon =
|
||||
if Pleroma.Config.get([:instances_favicons, :enabled]) do
|
||||
user
|
||||
|> Map.get(:ap_id, "")
|
||||
|> URI.parse()
|
||||
|> URI.merge("/")
|
||||
|> Pleroma.Instances.Instance.get_or_update_favicon()
|
||||
|> MediaProxy.url()
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(user.id),
|
||||
username: username_from_nickname(user.nickname),
|
||||
|
|
@ -245,7 +257,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
hide_favorites: user.hide_favorites,
|
||||
relationship: relationship,
|
||||
skip_thread_containment: user.skip_thread_containment,
|
||||
background_image: image_url(user.background) |> MediaProxy.url()
|
||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||
accepts_chat_messages: user.accepts_chat_messages,
|
||||
favicon: favicon
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|
|
|
|||
|
|
@ -34,10 +34,14 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||
background_image: Keyword.get(instance, :background_image),
|
||||
chat_limit: Keyword.get(instance, :chat_limit),
|
||||
description_limit: Keyword.get(instance, :description_limit),
|
||||
pleroma: %{
|
||||
metadata: %{
|
||||
account_activation_required: Keyword.get(instance, :account_activation_required),
|
||||
features: features(),
|
||||
federation: federation()
|
||||
federation: federation(),
|
||||
fields_limits: fields_limits()
|
||||
},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
}
|
||||
|
|
@ -88,4 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
end
|
||||
|> Map.put(:enabled, Config.get([:instance, :federating]))
|
||||
end
|
||||
|
||||
def fields_limits do
|
||||
%{
|
||||
max_fields: Config.get([:instance, :max_account_fields]),
|
||||
max_remote_fields: Config.get([:instance, :max_remote_account_fields]),
|
||||
name_length: Config.get([:instance, :account_field_name_length]),
|
||||
value_length: Config.get([:instance, :account_field_value_length])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||
|
||||
# TODO: Add cached version.
|
||||
defp get_replied_to_activities([]), do: %{}
|
||||
|
|
@ -333,6 +333,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
reblog: nil,
|
||||
card: card,
|
||||
content: content_html,
|
||||
text: opts[:with_source] && object.data["source"],
|
||||
created_at: created_at,
|
||||
reblogs_count: announcement_count,
|
||||
replies_count: object.data["repliesCount"] || 0,
|
||||
|
|
@ -364,7 +365,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
expires_at: expires_at,
|
||||
direct_conversation_id: direct_conversation_id,
|
||||
thread_muted: thread_muted?,
|
||||
emoji_reactions: emoji_reactions
|
||||
emoji_reactions: emoji_reactions,
|
||||
parent_visible: visible_for_user?(reply_to, opts[:for])
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
|
||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
||||
[
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
"proxy",
|
||||
sig_base64,
|
||||
url_base64,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
|
|||
alias Pleroma.Web.Auth.TOTPAuthenticator
|
||||
alias Pleroma.Web.OAuth.MFAView, as: View
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
plug(:fetch_session when action in [:show, :verify])
|
||||
|
|
@ -74,7 +75,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
|
|||
{:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
|
||||
{:ok, _} <- validates_challenge(user, params),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build(user, token))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
_error ->
|
||||
conn
|
||||
|
|
|
|||
|
|
@ -5,4 +5,13 @@
|
|||
defmodule Pleroma.Web.OAuth.MFAView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
alias Pleroma.MFA
|
||||
|
||||
def render("mfa_response.json", %{token: token, user: user}) do
|
||||
%{
|
||||
error: "mfa_required",
|
||||
mfa_token: token.token,
|
||||
supported_challenge_types: MFA.supported_methods(user)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.MFAController
|
||||
alias Pleroma.Web.OAuth.MFAView
|
||||
alias Pleroma.Web.OAuth.OAuthView
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||
|
|
@ -233,9 +235,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||
{:ok, token} <- RefreshToken.grant(token) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, Token.Response.build(user, token, response_attrs))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
_error -> render_invalid_credentials_error(conn)
|
||||
end
|
||||
|
|
@ -247,9 +247,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, Token.Response.build(user, token, response_attrs))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
error ->
|
||||
handle_token_exchange_error(conn, error)
|
||||
|
|
@ -267,7 +265,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build(user, token))
|
||||
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
|
||||
else
|
||||
error ->
|
||||
handle_token_exchange_error(conn, error)
|
||||
|
|
@ -290,7 +288,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build_for_client_credentials(token))
|
||||
json(conn, OAuthView.render("token.json", %{token: token}))
|
||||
else
|
||||
_error ->
|
||||
handle_token_exchange_error(conn, :invalid_credentails)
|
||||
|
|
@ -548,7 +546,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
defp build_and_response_mfa_token(user, auth) do
|
||||
with {:ok, token} <- MFA.Token.create_token(user, auth) do
|
||||
Token.Response.build_for_mfa_token(user, token)
|
||||
MFAView.render("mfa_response.json", %{token: token, user: user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,4 +5,26 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
|
||||
alias Pleroma.Web.OAuth.Token.Utils
|
||||
|
||||
def render("token.json", %{token: token} = opts) do
|
||||
response = %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
created_at: Utils.format_created_at(token)
|
||||
}
|
||||
|
||||
if user = opts[:user] do
|
||||
response
|
||||
|> Map.put(:me, user.ap_id)
|
||||
else
|
||||
response
|
||||
end
|
||||
end
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.Token.Response do
|
||||
@moduledoc false
|
||||
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token.Utils
|
||||
|
||||
@doc false
|
||||
def build(%User{} = user, token, opts \\ %{}) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|> Map.merge(opts)
|
||||
end
|
||||
|
||||
def build_for_client_credentials(token) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
created_at: Utils.format_created_at(token),
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
}
|
||||
end
|
||||
|
||||
def build_for_mfa_token(user, mfa_token) do
|
||||
%{
|
||||
error: "mfa_required",
|
||||
mfa_token: mfa_token.token,
|
||||
supported_challenge_types: MFA.supported_methods(user)
|
||||
}
|
||||
end
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
||||
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
import Pleroma.Web.ControllerHelper,
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
|
|
@ -35,17 +34,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:accounts"]}
|
||||
# Note: the following actions are not permission-secured in Mastodon:
|
||||
when action in [
|
||||
:update_avatar,
|
||||
:update_banner,
|
||||
:update_background
|
||||
]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
||||
|
|
@ -68,56 +56,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
|
||||
def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
||||
{:ok, _user} =
|
||||
user
|
||||
|> Changeset.change(%{avatar: nil})
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
|
||||
def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
|
||||
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
|
||||
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
|
||||
%{"url" => [%{"href" => href} | _]} = data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
|
||||
def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
|
||||
with {:ok, _user} <- User.update_banner(user, %{}) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
||||
def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
|
||||
{:ok, _user} <- User.update_banner(user, object.data) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/accounts/update_background"
|
||||
def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
|
||||
with {:ok, _user} <- User.update_background(user, %{}) do
|
||||
json(conn, %{url: nil})
|
||||
end
|
||||
end
|
||||
|
||||
def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, object} <- ActivityPub.upload(params, type: :background),
|
||||
{:ok, _user} <- User.update_background(user, object.data) do
|
||||
%{"url" => [%{"href" => href} | _]} = object.data
|
||||
|
||||
json(conn, %{url: href})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
|
||||
def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
|
||||
render_error(conn, :forbidden, "Can't get favorites")
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Preload.Providers.Instance do
|
||||
alias Pleroma.Plugs.InstanceStatic
|
||||
alias Pleroma.Web.MastodonAPI.InstanceView
|
||||
alias Pleroma.Web.Nodeinfo.Nodeinfo
|
||||
alias Pleroma.Web.Preload.Providers.Provider
|
||||
|
||||
@behaviour Provider
|
||||
@instance_url :"/api/v1/instance"
|
||||
@panel_url :"/instance/panel.html"
|
||||
@nodeinfo_url :"/nodeinfo/2.0"
|
||||
@instance_url "/api/v1/instance"
|
||||
@panel_url "/instance/panel.html"
|
||||
@nodeinfo_url "/nodeinfo/2.0.json"
|
||||
|
||||
@impl Provider
|
||||
def generate_terms(_params) do
|
||||
|
|
@ -27,7 +28,7 @@ defmodule Pleroma.Web.Preload.Providers.Instance do
|
|||
end
|
||||
|
||||
defp build_panel_tag(acc) do
|
||||
instance_path = Path.join(:code.priv_dir(:pleroma), "static/instance/panel.html")
|
||||
instance_path = InstanceStatic.file_path(@panel_url |> to_string())
|
||||
|
||||
if File.exists?(instance_path) do
|
||||
panel_data = File.read!(instance_path)
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Preload.Providers.StatusNet do
|
||||
alias Pleroma.Web.Preload.Providers.Provider
|
||||
alias Pleroma.Web.TwitterAPI.UtilView
|
||||
|
||||
@behaviour Provider
|
||||
@config_url :"/api/statusnet/config.json"
|
||||
|
||||
@impl Provider
|
||||
def generate_terms(_params) do
|
||||
%{}
|
||||
|> build_config_tag()
|
||||
end
|
||||
|
||||
defp build_config_tag(acc) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
info_data = UtilView.status_net_config(instance)
|
||||
|
||||
Map.put(acc, @config_url, info_data)
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.Preload.Providers.Timelines do
|
|||
alias Pleroma.Web.Preload.Providers.Provider
|
||||
|
||||
@behaviour Provider
|
||||
@public_url :"/api/v1/timelines/public"
|
||||
@public_url "/api/v1/timelines/public"
|
||||
|
||||
@impl Provider
|
||||
def generate_terms(params) do
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Preload.Providers.User do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.Preload.Providers.Provider
|
||||
|
||||
@behaviour Provider
|
||||
@account_url :"/api/v1/accounts"
|
||||
@account_url_base "/api/v1/accounts"
|
||||
|
||||
@impl Provider
|
||||
def generate_terms(%{user: user}) do
|
||||
|
|
@ -16,10 +17,10 @@ defmodule Pleroma.Web.Preload.Providers.User do
|
|||
|
||||
def generate_terms(_params), do: %{}
|
||||
|
||||
def build_accounts_tag(acc, nil), do: acc
|
||||
|
||||
def build_accounts_tag(acc, user) do
|
||||
def build_accounts_tag(acc, %User{} = user) do
|
||||
account_data = AccountView.render("show.json", %{user: user, for: user})
|
||||
Map.put(acc, @account_url, account_data)
|
||||
Map.put(acc, "#{@account_url_base}/#{user.id}", account_data)
|
||||
end
|
||||
|
||||
def build_accounts_tag(acc, _), do: acc
|
||||
end
|
||||
|
|
|
|||
|
|
@ -86,7 +86,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
end
|
||||
|
||||
try do
|
||||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
|
||||
rich_media_agent = Pleroma.Application.user_agent() <> "; Bot"
|
||||
|
||||
{:ok, %Tesla.Env{body: html}} =
|
||||
Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts)
|
||||
|
||||
html
|
||||
|> parse_html()
|
||||
|
|
|
|||
|
|
@ -328,10 +328,6 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
|
||||
post("/notifications/read", NotificationController, :mark_as_read)
|
||||
|
||||
patch("/accounts/update_avatar", AccountController, :update_avatar)
|
||||
patch("/accounts/update_banner", AccountController, :update_banner)
|
||||
patch("/accounts/update_background", AccountController, :update_background)
|
||||
|
||||
get("/mascot", MascotController, :show)
|
||||
put("/mascot", MascotController, :update)
|
||||
|
||||
|
|
@ -516,10 +512,6 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api", Pleroma.Web do
|
||||
pipe_through(:config)
|
||||
|
||||
get("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
post("/help/test", TwitterAPI.UtilController, :help_test)
|
||||
get("/statusnet/config", TwitterAPI.UtilController, :config)
|
||||
get("/statusnet/version", TwitterAPI.UtilController, :version)
|
||||
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ defmodule Pleroma.Web.Streamer do
|
|||
:ok
|
||||
end
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Activity{} = item) do
|
||||
def filtered_by_user?(user, item, streamed_type \\ :activity)
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
|
||||
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
|
||||
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
|
||||
|
||||
|
|
@ -116,6 +118,9 @@ defmodule Pleroma.Web.Streamer do
|
|||
true <-
|
||||
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
|
||||
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
|
||||
true <-
|
||||
!(streamed_type == :activity && item.data["type"] == "Announce" &&
|
||||
parent.data["actor"] == user.ap_id),
|
||||
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
||||
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
||||
%{host: item_host} <- URI.parse(item.actor),
|
||||
|
|
@ -130,8 +135,8 @@ defmodule Pleroma.Web.Streamer do
|
|||
end
|
||||
end
|
||||
|
||||
def filtered_by_user?(%User{} = user, %Notification{activity: activity}) do
|
||||
filtered_by_user?(user, activity)
|
||||
def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
|
||||
filtered_by_user?(user, activity, :notification)
|
||||
end
|
||||
|
||||
defp do_stream("direct", item) do
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Notification
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.TwitterAPI.UtilView
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
||||
|
|
@ -42,12 +40,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
|
||||
|
||||
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
|
||||
|
||||
def help_test(conn, _params) do
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nick),
|
||||
avatar = User.avatar_url(user) do
|
||||
|
|
@ -89,80 +81,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
end
|
||||
|
||||
def config(%{assigns: %{format: "xml"}} = conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
response = UtilView.status_net_config(instance)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, response)
|
||||
end
|
||||
|
||||
def config(conn, _params) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
|
||||
vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
|
||||
uploadlimit = %{
|
||||
uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
|
||||
avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
|
||||
backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
|
||||
bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
|
||||
}
|
||||
|
||||
data = %{
|
||||
name: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
server: Web.base_url(),
|
||||
textlimit: to_string(Keyword.get(instance, :limit)),
|
||||
uploadlimit: uploadlimit,
|
||||
closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
|
||||
private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
|
||||
vapidPublicKey: vapid_public_key,
|
||||
accountActivationRequired:
|
||||
bool_to_val(Keyword.get(instance, :account_activation_required, false)),
|
||||
invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
|
||||
safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
|
||||
}
|
||||
|
||||
managed_config = Keyword.get(instance, :managed_config)
|
||||
|
||||
data =
|
||||
if managed_config do
|
||||
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
|
||||
Map.put(data, "pleromafe", pleroma_fe)
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
json(conn, %{site: data})
|
||||
end
|
||||
|
||||
defp bool_to_val(true), do: "1"
|
||||
defp bool_to_val(_), do: "0"
|
||||
defp bool_to_val(true, val, _), do: val
|
||||
defp bool_to_val(_, _, val), do: val
|
||||
|
||||
def frontend_configurations(conn, _params) do
|
||||
config =
|
||||
Pleroma.Config.get(:frontend_configurations, %{})
|
||||
Config.get(:frontend_configurations, %{})
|
||||
|> Enum.into(%{})
|
||||
|
||||
json(conn, config)
|
||||
end
|
||||
|
||||
def version(%{assigns: %{format: "xml"}} = conn, _params) do
|
||||
version = Pleroma.Application.named_version()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/xml")
|
||||
|> send_resp(200, "<version>#{version}</version>")
|
||||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
json(conn, Pleroma.Application.named_version())
|
||||
end
|
||||
|
||||
def emoji(conn, _params) do
|
||||
emoji =
|
||||
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastoFEView do
|
|||
"video\/mp4"
|
||||
]
|
||||
},
|
||||
settings: user.settings || @default_settings,
|
||||
settings: user.mastofe_settings || @default_settings,
|
||||
push_subscription: nil,
|
||||
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
|
||||
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
|
||||
|
|
|
|||
|
|
@ -11,13 +11,12 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(
|
||||
%{
|
||||
def perform(%Job{
|
||||
args: %{
|
||||
"op" => "cleanup_attachments",
|
||||
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
|
||||
},
|
||||
_job
|
||||
) do
|
||||
}
|
||||
}) do
|
||||
attachments
|
||||
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|
||||
|> fetch_objects
|
||||
|
|
@ -28,7 +27,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
|||
{:ok, :success}
|
||||
end
|
||||
|
||||
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
|
||||
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
|
||||
|
||||
defp do_clean({object_ids, attachment_urls}) do
|
||||
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
|
||||
|
|
|
|||
|
|
@ -11,59 +11,59 @@ defmodule Pleroma.Workers.BackgroundWorker do
|
|||
|
||||
@impl Oban.Worker
|
||||
|
||||
def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do
|
||||
def perform(%Job{args: %{"op" => "deactivate_user", "user_id" => user_id, "status" => status}}) do
|
||||
user = User.get_cached_by_id(user_id)
|
||||
User.perform(:deactivate_async, user, status)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
|
||||
user = User.get_cached_by_id(user_id)
|
||||
User.perform(:delete, user)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) do
|
||||
user = User.get_cached_by_id(user_id)
|
||||
User.perform(:force_password_reset, user)
|
||||
end
|
||||
|
||||
def perform(
|
||||
%{
|
||||
def perform(%Job{
|
||||
args: %{
|
||||
"op" => "blocks_import",
|
||||
"blocker_id" => blocker_id,
|
||||
"blocked_identifiers" => blocked_identifiers
|
||||
},
|
||||
_job
|
||||
) do
|
||||
}
|
||||
}) do
|
||||
blocker = User.get_cached_by_id(blocker_id)
|
||||
{:ok, User.perform(:blocks_import, blocker, blocked_identifiers)}
|
||||
end
|
||||
|
||||
def perform(
|
||||
%{
|
||||
def perform(%Job{
|
||||
args: %{
|
||||
"op" => "follow_import",
|
||||
"follower_id" => follower_id,
|
||||
"followed_identifiers" => followed_identifiers
|
||||
},
|
||||
_job
|
||||
) do
|
||||
}
|
||||
}) do
|
||||
follower = User.get_cached_by_id(follower_id)
|
||||
{:ok, User.perform(:follow_import, follower, followed_identifiers)}
|
||||
end
|
||||
|
||||
def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do
|
||||
def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do
|
||||
MediaProxyWarmingPolicy.perform(:preload, message)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "media_proxy_prefetch", "url" => url}, _job) do
|
||||
def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do
|
||||
MediaProxyWarmingPolicy.perform(:prefetch, url)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do
|
||||
activity = Activity.get_by_id(activity_id)
|
||||
Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
|
||||
def perform(%Job{
|
||||
args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
|
||||
}) do
|
||||
origin = User.get_cached_by_id(origin_id)
|
||||
target = User.get_cached_by_id(target_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
|
|||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_opts, _job) do
|
||||
def perform(_job) do
|
||||
if Config.get([:oauth2, :clean_expired_tokens], false) do
|
||||
Token.delete_expired_tokens()
|
||||
else
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
|
|||
require Logger
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_opts, _job) do
|
||||
def perform(_job) do
|
||||
config = Config.get([:email_notifications, :digest])
|
||||
|
||||
if config[:active] do
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "new_users_digest"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_args, _job) do
|
||||
def perform(_job) do
|
||||
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
|
||||
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
|
|||
@interval :timer.minutes(1)
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_opts, _job) do
|
||||
def perform(_job) do
|
||||
if Config.get([ActivityExpiration, :enabled]) do
|
||||
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Workers.Cron.StatsWorker do
|
|||
use Oban.Worker, queue: "background"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_opts, _job) do
|
||||
def perform(_job) do
|
||||
Pleroma.Stats.do_collect()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Workers.MailerWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "mailer"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do
|
||||
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
|
||||
encoded_email
|
||||
|> Base.decode64!()
|
||||
|> :erlang.binary_to_term()
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@ defmodule Pleroma.Workers.PublisherWorker do
|
|||
|
||||
use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
|
||||
|
||||
def backoff(attempt) when is_integer(attempt) do
|
||||
def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
|
||||
Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5)
|
||||
end
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
|
||||
activity = Activity.get_by_id(activity_id)
|
||||
Federator.perform(:publish, activity)
|
||||
end
|
||||
|
||||
def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do
|
||||
def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do
|
||||
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
|
||||
Federator.perform(:publish_one, String.to_atom(module_name), params)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do
|
||||
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
|
||||
Federator.perform(:incoming_ap_doc, params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,13 +8,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(
|
||||
%{
|
||||
"op" => "fetch_remote",
|
||||
"id" => id
|
||||
} = args,
|
||||
_job
|
||||
) do
|
||||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
|
|||
require Logger
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"activity_id" => activity_id}, _job) do
|
||||
def perform(%Job{args: %{"activity_id" => activity_id}}) do
|
||||
if Config.get([ScheduledActivity, :enabled]) do
|
||||
case Pleroma.Repo.get(ScheduledActivity, activity_id) do
|
||||
%ScheduledActivity{} = scheduled_activity ->
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Workers.TransmogrifierWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "transmogrifier"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "user_upgrade", "user_id" => user_id}}) do
|
||||
user = User.get_cached_by_id(user_id)
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
|
|||
use Pleroma.Workers.WorkerHelper, queue: "web_push"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do
|
||||
def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do
|
||||
notification =
|
||||
Notification
|
||||
|> Repo.get(notification_id)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ defmodule Pleroma.Workers.WorkerHelper do
|
|||
queue: unquote(queue),
|
||||
max_attempts: 1
|
||||
|
||||
alias Oban.Job
|
||||
|
||||
def enqueue(op, params, worker_args \\ []) do
|
||||
params = Map.merge(%{"op" => op}, params)
|
||||
queue_atom = String.to_atom(unquote(queue))
|
||||
|
|
@ -39,7 +41,7 @@ defmodule Pleroma.Workers.WorkerHelper do
|
|||
|
||||
unquote(caller_module)
|
||||
|> apply(:new, [params, worker_args])
|
||||
|> Pleroma.Repo.insert()
|
||||
|> Oban.insert()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue