Page MenuHomePhorge

No OneTemporary

Size
243 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 2b6c7d6bb..3991e4fa3 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -1,139 +1,124 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Pleroma do
@apps [
- :restarter,
:ecto,
:ecto_sql,
:postgrex,
:db_connection,
:cachex,
:flake_id,
:swoosh,
:timex,
:fast_html,
:oban
]
+
@cachex_children ["object", "user", "scrubber", "web_resp"]
+
@doc "Common functions to be reused in mix tasks"
+ @spec start_pleroma() :: {:ok, pid()}
def start_pleroma do
Pleroma.Config.Holder.save_default()
- Pleroma.Config.Oban.warn()
+ Pleroma.Config.DeprecationWarnings.check_oban_config()
Pleroma.Application.limiters_setup()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
unless System.get_env("DEBUG") do
Logger.remove_backend(:console)
end
adapter = Application.get_env(:tesla, :adapter)
apps =
if adapter == Tesla.Adapter.Gun do
[:gun | @apps]
else
[:hackney | @apps]
end
Enum.each(apps, &Application.ensure_all_started/1)
oban_config = [
crontab: [],
repo: Pleroma.Repo,
log: false,
queues: [],
plugins: []
]
- children =
- [
- Pleroma.Repo,
- Pleroma.Emoji,
- {Pleroma.Config.TransferTask, false},
- Pleroma.Web.Endpoint,
- {Oban, oban_config},
- {Majic.Pool,
- [name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
- ] ++
- http_children(adapter)
+ children = [
+ Pleroma.Repo,
+ Supervisor.child_spec({Task, &Pleroma.Application.Environment.load_from_db_and_update/0},
+ id: :update_env
+ ),
+ Pleroma.Emoji,
+ Pleroma.Web.Endpoint,
+ {Oban, oban_config},
+ {Majic.Pool,
+ [name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
+ ]
+
+ children = [Pleroma.Application.StartUpDependencies.adapter_module() | children]
- cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
+ cachex_children =
+ Enum.map(@cachex_children, &Pleroma.Application.StartUpDependencies.cachex_spec({&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?()
- end
- end
-
- defp pleroma_rebooted? do
- if Restarter.Pleroma.rebooted?() do
- :ok
- else
- Process.sleep(10)
- pleroma_rebooted?()
- end
end
def load_pleroma do
Application.load(:pleroma)
end
def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
Keyword.get(options, opt) || shell_prompt(prompt, defval, defname)
end
def shell_prompt(prompt, defval \\ nil, defname \\ nil) do
prompt_message = "#{prompt} [#{defname || defval}] "
input =
if mix_shell?(),
do: Mix.shell().prompt(prompt_message),
else: :io.get_line(prompt_message)
case input do
"\n" ->
case defval do
nil ->
shell_prompt(prompt, defval, defname)
defval ->
defval
end
input ->
String.trim(input)
end
end
def shell_info(message) do
if mix_shell?(),
do: Mix.shell().info(message),
else: IO.puts(message)
end
def shell_error(message) do
if mix_shell?(),
do: Mix.shell().error(message),
else: IO.puts(:stderr, message)
end
@doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)"
def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0)
def escape_sh_path(path) do
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
end
-
- defp http_children(Tesla.Adapter.Gun) do
- Pleroma.Gun.ConnectionPool.children() ++
- [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
- end
-
- defp http_children(_), do: []
end
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index 1962154b9..cd5b9635b 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -1,388 +1,386 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Config do
use Mix.Task
import Ecto.Query
import Mix.Pleroma
alias Pleroma.ConfigDB
alias Pleroma.Repo
@shortdoc "Manages the location of the config"
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
def run(["migrate_to_db"]) do
check_configdb(fn ->
start_pleroma()
migrate_to_db()
end)
end
def run(["migrate_from_db" | options]) do
check_configdb(fn ->
start_pleroma()
{opts, _} =
OptionParser.parse!(options,
strict: [env: :string, delete: :boolean],
aliases: [d: :delete]
)
migrate_from_db(opts)
end)
end
def run(["dump"]) do
check_configdb(fn ->
start_pleroma()
header = config_header()
settings =
ConfigDB
|> Repo.all()
|> Enum.sort()
unless settings == [] do
shell_info("#{header}")
Enum.each(settings, &dump(&1))
else
shell_error("No settings in ConfigDB.")
end
end)
end
def run(["dump", group, key]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
end)
end
def run(["dump", group]) do
check_configdb(fn ->
start_pleroma()
group = maybe_atomize(group)
dump_group(group)
end)
end
def run(["groups"]) do
check_configdb(fn ->
start_pleroma()
groups =
ConfigDB
|> distinct([c], true)
|> select([c], c.group)
|> Repo.all()
if length(groups) > 0 do
shell_info("The following configuration groups are set in ConfigDB:\r\n")
groups |> Enum.each(fn x -> shell_info("- #{x}") end)
shell_info("\r\n")
end
end)
end
def run(["reset", "--force"]) do
check_configdb(fn ->
start_pleroma()
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
end)
end
def run(["reset"]) do
check_configdb(fn ->
start_pleroma()
shell_info("The following settings will be permanently removed:")
ConfigDB
|> Repo.all()
|> Enum.sort()
|> Enum.each(&dump(&1))
shell_error("\nTHIS CANNOT BE UNDONE!")
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
truncatedb()
shell_info("The ConfigDB settings have been removed from the database.")
else
shell_error("No changes made.")
end
end)
end
def run(["delete", "--force", group, key]) do
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
delete_key(group, key)
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", "--force", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
delete_group(group)
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end
def run(["delete", group, key]) do
start_pleroma()
group = maybe_atomize(group)
key = maybe_atomize(key)
with true <- key_exists?(group, key) do
shell_info("The following settings will be removed from ConfigDB:\n")
group
|> ConfigDB.get_by_group_and_key(key)
|> dump()
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_key(group, key)
else
shell_error("No changes made.")
end
else
_ ->
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
end
end
def run(["delete", group]) do
start_pleroma()
group = maybe_atomize(group)
with true <- group_exists?(group) do
shell_info("The following settings will be removed from ConfigDB:\n")
dump_group(group)
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
delete_group(group)
else
shell_error("No changes made.")
end
else
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
end
end
@spec migrate_to_db(Path.t() | nil) :: any()
def migrate_to_db(file_path \\ nil) do
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
config_file =
if file_path do
file_path
else
if Pleroma.Config.get(:release) do
Pleroma.Config.get(:config_path)
else
"config/#{Pleroma.Config.get(:env)}.secret.exs"
end
end
do_migrate_to_db(config_file)
else
_ ->
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
end
end
defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
truncatedb()
custom_config =
config_file
|> read_file()
|> elem(0)
custom_config
- |> Keyword.keys()
- |> Enum.each(&create(&1, custom_config))
+ |> Pleroma.Config.Loader.filter()
+ |> Enum.each(&create/1)
else
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
end
end
- defp create(group, settings) do
- group
- |> Pleroma.Config.Loader.filter_group(settings)
- |> Enum.each(fn {key, value} ->
+ defp create({group, settings}) do
+ Enum.each(settings, fn {key, value} ->
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
shell_info("Settings for key #{key} migrated.")
end)
shell_info("Settings for group #{inspect(group)} migrated.")
end
defp migrate_from_db(opts) do
env = opts[:env] || Pleroma.Config.get(:env)
config_path =
if Pleroma.Config.get(:release) do
:config_path
|> Pleroma.Config.get()
|> Path.dirname()
else
"config"
end
|> Path.join("#{env}.exported_from_db.secret.exs")
file = File.open!(config_path, [:write, :utf8])
IO.write(file, config_header())
ConfigDB
|> Repo.all()
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
: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"
)
end
if Code.ensure_loaded?(Config.Reader) do
defp config_header, do: "import Config\r\n\r\n"
defp read_file(config_file), do: Config.Reader.read_imports!(config_file)
else
defp config_header, do: "use Mix.Config\r\n\r\n"
defp read_file(config_file), do: Mix.Config.eval!(config_file)
end
defp write_and_delete(config, file, delete?) do
config
|> write(file)
|> delete(delete?)
end
defp write(config, file) do
value = inspect(config.value, limit: :infinity)
IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
config
end
defp delete(config, true) do
{:ok, _} = Repo.delete(config)
shell_info(
"config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB."
)
end
defp delete(_config, _), do: :ok
defp dump(%ConfigDB{} = config) do
value = inspect(config.value, limit: :infinity)
shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
end
defp dump(_), do: :noop
defp dump_group(group) when is_atom(group) do
group
|> ConfigDB.get_all_by_group()
|> Enum.each(&dump/1)
end
defp group_exists?(group) do
group
|> ConfigDB.get_all_by_group()
|> Enum.any?()
end
defp key_exists?(group, key) do
group
|> ConfigDB.get_by_group_and_key(key)
|> is_nil
|> Kernel.!()
end
defp maybe_atomize(arg) when is_atom(arg), do: arg
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
defp maybe_atomize(arg) when is_binary(arg) do
if ConfigDB.module_name?(arg) do
String.to_existing_atom("Elixir." <> arg)
else
String.to_atom(arg)
end
end
defp check_configdb(callback) do
with true <- Pleroma.Config.get([:configurable_from_database]) do
callback.()
else
_ ->
shell_error(
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
)
end
end
defp delete_key(group, key) do
check_configdb(fn ->
ConfigDB.delete(%{group: group, key: key})
end)
end
defp delete_group(group) do
check_configdb(fn ->
group
|> ConfigDB.get_all_by_group()
|> Enum.each(&ConfigDB.delete/1)
end)
end
defp truncatedb do
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
end
end
diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex
index 45cca1c74..e0e9834f4 100644
--- a/lib/mix/tasks/pleroma/docs.ex
+++ b/lib/mix/tasks/pleroma/docs.ex
@@ -1,46 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Docs do
use Mix.Task
import Mix.Pleroma
@shortdoc "Generates docs from descriptions.exs"
@moduledoc """
Generates docs from `descriptions.exs`.
Supports two formats: `markdown` and `json`.
## Generate Markdown docs
`mix pleroma.docs`
## Generate JSON docs
`mix pleroma.docs json`
"""
def run(["json"]) do
do_run(Pleroma.Docs.JSON)
end
def run(_) do
do_run(Pleroma.Docs.Markdown)
end
defp do_run(implementation) do
start_pleroma()
- with descriptions <- Pleroma.Config.Loader.read("config/description.exs"),
+ with descriptions <- Pleroma.Config.Loader.read!("config/description.exs"),
{:ok, file_path} <-
Pleroma.Docs.Generator.process(
implementation,
descriptions[:pleroma][:config_description]
) do
type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON"
Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."])
end
end
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 06d399b2e..61e12d26e 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -1,316 +1,175 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do
use Application
- import Cachex.Spec
+ require Logger
alias Pleroma.Config
- require Logger
-
@name Mix.Project.config()[:name]
@version Mix.Project.config()[:version]
@repository Mix.Project.config()[:source_url]
@mix_env Mix.env()
+ @dynamic_supervisor Pleroma.Application.Supervisor
+
+ @type env() :: :test | :benchmark | :dev | :prod
def name, do: @name
def version, do: @version
def named_version, do: @name <> " " <> @version
def repository, do: @repository
+ def dynamic_supervisor, do: @dynamic_supervisor
+ @spec user_agent() :: String.t()
def user_agent do
if Process.whereis(Pleroma.Web.Endpoint) do
case Config.get([:http, :user_agent], :default) do
:default ->
info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>"
named_version() <> "; " <> info
custom ->
custom
end
else
# fallback, if endpoint is not started yet
"Pleroma Data Loader"
end
end
- # See http://elixir-lang.org/docs/stable/elixir/Application.html
- # for more information on OTP Applications
+ @doc """
+ Under main supervisor is started DynamicSupervisor, which later starts pleroma startup dependencies.
+ Pleroma start is splitted into three `phases`:
+ - running prestart requirements (runtime compilation, warnings, deprecations, monitoring, etc.)
+ - loading and updating environment (if database config is used and enabled)
+ - starting dependencies
+ """
+ @impl true
def start(_type, _args) do
+ children = [
+ {DynamicSupervisor, strategy: :one_for_one, name: @dynamic_supervisor},
+ {Pleroma.Application.ConfigDependentDeps, [dynamic_supervisor: @dynamic_supervisor]},
+ Pleroma.Repo
+ ]
+
+ {:ok, main_supervisor} =
+ Supervisor.start_link(children, strategy: :one_for_one, name: Pleroma.Supervisor)
+
+ run_prestart_requirements()
+
+ Pleroma.Application.Environment.load_from_db_and_update(pleroma_start: true)
+
+ Pleroma.Application.StartUpDependencies.start_all(@mix_env)
+
+ {:ok, main_supervisor}
+ end
+
+ defp run_prestart_requirements do
# Scrubbers are compiled at runtime and therefore will cause a conflict
# every time the application is restarted, so we disable module
# conflicts at runtime
Code.compiler_options(ignore_module_conflict: true)
+
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
# due to protocol consolidation warnings
Code.compiler_options(warnings_as_errors: false)
- Pleroma.Telemetry.Logger.attach()
- Config.Holder.save_default()
+
+ # compilation in runtime
Pleroma.HTML.compile_scrubbers()
- Pleroma.Config.Oban.warn()
- Config.DeprecationWarnings.warn()
- Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
- Pleroma.ApplicationRequirements.verify!()
- setup_instrumenters()
- load_custom_modules()
+ compile_custom_modules()
Pleroma.Docs.JSON.compile()
- limiters_setup()
- adapter = Application.get_env(:tesla, :adapter)
-
- if adapter == Tesla.Adapter.Gun do
- if version = Pleroma.OTPVersion.version() do
- [major, minor] =
- version
- |> String.split(".")
- |> Enum.map(&String.to_integer/1)
- |> Enum.take(2)
-
- if (major == 22 and minor < 2) or major < 22 do
- raise "
- !!!OTP VERSION WARNING!!!
- You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
- "
- end
- else
- raise "
- !!!OTP VERSION WARNING!!!
- To support correct handling of unordered certificates chains - OTP version must be > 22.2.
- "
- end
- end
+ # telemetry and prometheus
+ Pleroma.Telemetry.Logger.attach()
+ setup_instrumenters()
- # Define workers and child supervisors to be supervised
- children =
- [
- Pleroma.Repo,
- Config.TransferTask,
- Pleroma.Emoji,
- Pleroma.Web.Plugs.RateLimiter.Supervisor
- ] ++
- cachex_children() ++
- http_children(adapter, @mix_env) ++
- [
- Pleroma.Stats,
- Pleroma.JobQueueMonitor,
- {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
- {Oban, Config.get(Oban)},
- Pleroma.Web.Endpoint
- ] ++
- task_children(@mix_env) ++
- dont_run_in_test(@mix_env) ++
- chat_child(chat_enabled?()) ++
- [Pleroma.Gopher.Server]
-
- # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
- # for other strategies and supported options
- opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
- result = Supervisor.start_link(children, opts)
+ Config.Holder.save_default()
+ Config.DeprecationWarnings.warn()
+ Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
+ limiters_setup()
set_postgres_server_version()
- result
+ Pleroma.Application.Requirements.verify!()
end
defp set_postgres_server_version do
version =
with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"),
{num, _} <- Float.parse(version) do
num
else
e ->
Logger.warn(
"Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6"
)
9.6
end
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
end
- def load_custom_modules do
+ defp compile_custom_modules do
dir = Config.get([:modules, :runtime_dir])
if dir && File.exists?(dir) do
dir
|> Pleroma.Utils.compile_dir()
|> case do
{:error, _errors, _warnings} ->
raise "Invalid custom modules"
{:ok, modules, _warnings} ->
if @mix_env != :test do
Enum.each(modules, fn mod ->
Logger.info("Custom module loaded: #{inspect(mod)}")
end)
end
:ok
end
end
end
defp setup_instrumenters do
require Prometheus.Registry
if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do
:ok =
:telemetry.attach(
"prometheus-ecto",
[:pleroma, :repo, :query],
&Pleroma.Repo.Instrumenter.handle_event/4,
%{}
)
Pleroma.Repo.Instrumenter.setup()
end
Pleroma.Web.Endpoint.MetricsExporter.setup()
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
# Note: disabled until prometheus-phx is integrated into prometheus-phoenix:
# Pleroma.Web.Endpoint.Instrumenter.setup()
PrometheusPhx.setup()
end
- defp cachex_children do
- [
- build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
- build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
- build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
- build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
- build_cachex("scrubber", limit: 2500),
- build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
- build_cachex("web_resp", limit: 2500),
- build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
- build_cachex("failed_proxy_url", limit: 2500),
- build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
- build_cachex("chat_message_id_idempotency_key",
- expiration: chat_message_id_idempotency_key_expiration(),
- limit: 500_000
- )
- ]
- end
-
- defp emoji_packs_expiration,
- do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60))
-
- defp idempotency_expiration,
- do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
-
- defp chat_message_id_idempotency_key_expiration,
- do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
-
- defp seconds_valid_interval,
- do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
-
- @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]},
- type: :worker
- }
-
- defp chat_enabled?, do: Config.get([:chat, :enabled])
-
- defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
-
- defp dont_run_in_test(_) do
- [
- {Registry,
- [
- name: Pleroma.Web.Streamer.registry(),
- keys: :duplicate,
- partitions: System.schedulers_online()
- ]}
- ] ++ background_migrators()
- end
-
- defp background_migrators do
- [
- Pleroma.Migrators.HashtagsTableMigrator
- ]
- end
-
- defp chat_child(true) do
- [
- Pleroma.Web.ChatChannel.ChatChannelState,
- {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
- ]
- end
-
- defp chat_child(_), do: []
-
- defp task_children(:test) do
- [
- %{
- id: :web_push_init,
- start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
- restart: :temporary
- }
- ]
- end
-
- defp task_children(_) do
- [
- %{
- id: :web_push_init,
- start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
- restart: :temporary
- },
- %{
- id: :internal_fetch_init,
- start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
- restart: :temporary
- }
- ]
- end
-
- # start hackney and gun pools in tests
- defp http_children(_, :test) do
- http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
- end
-
- defp http_children(Tesla.Adapter.Hackney, _) do
- pools = [:federation, :media]
-
- pools =
- if Config.get([Pleroma.Upload, :proxy_remote]) do
- [:upload | pools]
- else
- pools
- end
-
- for pool <- pools do
- options = Config.get([:hackney_pools, pool])
- :hackney_pool.child_spec(pool, options)
- end
- end
-
- defp http_children(Tesla.Adapter.Gun, _) do
- Pleroma.Gun.ConnectionPool.children() ++
- [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
- end
-
- defp http_children(_, _), do: []
-
@spec limiters_setup() :: :ok
def limiters_setup do
config = Config.get(ConcurrentLimiter, [])
[Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|> Enum.each(fn module ->
mod_config = Keyword.get(config, module, [])
max_running = Keyword.get(mod_config, :max_running, 5)
max_waiting = Keyword.get(mod_config, :max_waiting, 5)
ConcurrentLimiter.new(module, max_running, max_waiting)
end)
end
end
diff --git a/lib/pleroma/application/config_dependent_deps.ex b/lib/pleroma/application/config_dependent_deps.ex
new file mode 100644
index 000000000..b2b6bd845
--- /dev/null
+++ b/lib/pleroma/application/config_dependent_deps.ex
@@ -0,0 +1,244 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Application.ConfigDependentDeps do
+ use GenServer
+
+ require Logger
+
+ @config_path_mods_relation [
+ {{:pleroma, :chat}, Pleroma.Web.ChatChannel.ChatChannelState},
+ {{:pleroma, Oban}, Oban},
+ {{:pleroma, :rate_limit}, Pleroma.Web.Plugs.RateLimiter.Supervisor},
+ {{:pleroma, :streamer}, Pleroma.Web.Streamer.registry()},
+ {{:pleroma, :pools}, Pleroma.Gun.GunSupervisor},
+ {{:pleroma, :connections_pool}, Pleroma.Gun.GunSupervisor},
+ {{:pleroma, :hackney_pools}, Pleroma.HTTP.HackneySupervisor},
+ {{:pleroma, :gopher}, Pleroma.Gopher.Server},
+ {{:pleroma, Pleroma.Captcha, [:seconds_valid]}, Pleroma.Web.Endpoint},
+ {{:pleroma, Pleroma.Upload, [:proxy_remote]},
+ Pleroma.Application.StartUpDependencies.adapter_module()},
+ {{:pleroma, :instance, [:upload_limit]}, Pleroma.Web.Endpoint},
+ {{:pleroma, :fed_sockets, [:enabled]}, Pleroma.Web.Endpoint},
+ {:eshhd, :eshhd},
+ {:ex_aws, :ex_aws}
+ ]
+
+ def start_link(opts) do
+ opts = Keyword.put_new(opts, :relations, @config_path_mods_relation)
+
+ GenServer.start_link(__MODULE__, opts, name: opts[:name] || __MODULE__)
+ end
+
+ @impl true
+ def init(opts) do
+ init_state = %{
+ dynamic_supervisor: opts[:dynamic_supervisor],
+ relations: opts[:relations],
+ reboot_paths: [],
+ pids: %{}
+ }
+
+ {:ok, init_state}
+ end
+
+ def start_dependency(module, server \\ __MODULE__) do
+ GenServer.call(server, {:start_dependency, module})
+ end
+
+ def need_reboot?(server \\ __MODULE__) do
+ GenServer.call(server, :need_reboot?)
+ end
+
+ def restart_dependencies(server \\ __MODULE__) do
+ GenServer.call(server, :restart_dependencies)
+ end
+
+ def clear_state(server \\ __MODULE__) do
+ GenServer.call(server, :clear_state)
+ end
+
+ def save_config_paths_for_restart(changes, server \\ __MODULE__) do
+ GenServer.call(server, {:save_config_paths, changes})
+ end
+
+ @impl true
+ def handle_call({:start_dependency, module}, _, state) do
+ {result, state} =
+ with {pid, state} when is_pid(pid) <- start_module(module, state) do
+ {{:ok, pid}, state}
+ else
+ error -> {error, state}
+ end
+
+ {:reply, result, state}
+ end
+
+ @impl true
+ def handle_call(:need_reboot?, _, state) do
+ {:reply, state[:reboot_paths] != [], state}
+ end
+
+ @impl true
+ def handle_call(:restart_dependencies, _, state) do
+ {paths, state} = Map.get_and_update(state, :reboot_paths, &{&1, []})
+ started_apps = Application.started_applications()
+
+ {result, state} =
+ Enum.reduce_while(paths, {:ok, state}, fn
+ path, {:ok, acc} when is_tuple(path) ->
+ case restart(path, acc, acc[:pids][path], with_terminate: true) do
+ {pid, state} when is_pid(pid) ->
+ {:cont, {:ok, state}}
+
+ :ignore ->
+ Logger.info("path #{inspect(path)} is ignored.")
+ {:cont, {:ok, acc}}
+
+ error ->
+ {:halt, {error, acc}}
+ end
+
+ app, {:ok, acc}
+ when is_atom(app) and app not in [:logger, :quack, :pleroma, :prometheus, :postgrex] ->
+ restart_app(app, started_apps)
+ {:cont, {:ok, acc}}
+ end)
+
+ {:reply, result, state}
+ end
+
+ @impl true
+ def handle_call(:clear_state, _, state) do
+ state =
+ state
+ |> Map.put(:reboot_paths, [])
+ |> Map.put(:pids, %{})
+
+ {:reply, :ok, state}
+ end
+
+ @impl true
+ def handle_call({:save_config_paths, changes}, _, state) do
+ paths =
+ Enum.reduce(changes, state[:reboot_paths], fn
+ %{group: group, key: key, value: value}, acc ->
+ with {path, _} <- find_relation(state[:relations], group, key, value) do
+ if path not in acc do
+ [path | acc]
+ else
+ acc
+ end
+ else
+ _ ->
+ acc
+ end
+ end)
+
+ {:reply, paths, put_in(state[:reboot_paths], paths)}
+ end
+
+ @impl true
+ def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
+ updated_state =
+ with {path, ^pid} <-
+ Enum.find(state[:pids], fn {_, registered_pid} -> registered_pid == pid end) do
+ {_new_pid, new_state} = restart(path, state, pid)
+ new_state
+ else
+ _ -> state
+ end
+
+ {:noreply, updated_state}
+ end
+
+ defp start_module(module, state) do
+ with {:ok, relations} <- find_relations(state[:relations], module) do
+ start_module(module, relations, state)
+ end
+ end
+
+ defp start_module(module, relations, state) do
+ spec =
+ module
+ |> Pleroma.Application.StartUpDependencies.spec()
+ |> Supervisor.child_spec(restart: :temporary)
+
+ with {:ok, pid} <-
+ DynamicSupervisor.start_child(
+ state[:dynamic_supervisor],
+ spec
+ ) do
+ pids = Map.new(relations, fn {path, _} -> {path, pid} end)
+ Process.monitor(pid)
+ {pid, put_in(state[:pids], Map.merge(state[:pids], pids))}
+ end
+ end
+
+ defp restart(path, state, pid, opts \\ [])
+
+ defp restart(path, state, nil, _) do
+ with {_, module} <- find_relation(state[:relations], path) do
+ start_module(module, state)
+ end
+ end
+
+ defp restart(path, state, pid, opts) when is_pid(pid) do
+ with {_, module} <- find_relation(state[:relations], path),
+ {:ok, relations} <- find_relations(state[:relations], module) do
+ if opts[:with_terminate] do
+ :ok = DynamicSupervisor.terminate_child(state[:dynamic_supervisor], pid)
+ end
+
+ paths_for_remove = Enum.map(relations, fn {path, _} -> path end)
+ state = put_in(state[:pids], Map.drop(state[:pids], paths_for_remove))
+
+ start_module(module, relations, state)
+ end
+ end
+
+ defp restart_app(app, started_applications) do
+ with {^app, _, _} <- List.keyfind(started_applications, app, 0) do
+ :ok = Application.stop(app)
+ :ok = Application.start(app)
+ else
+ nil ->
+ Logger.info("#{app} is not started.")
+
+ error ->
+ error
+ |> inspect()
+ |> Logger.error()
+ end
+ end
+
+ defp find_relations(relations, module) do
+ case Enum.filter(relations, fn {_, mod} -> mod == module end) do
+ [] ->
+ {:error, :relations_not_found}
+
+ relations ->
+ {:ok, relations}
+ end
+ end
+
+ defp find_relation(relations, group, key, value) do
+ Enum.find(relations, fn
+ {g, _} when is_atom(g) ->
+ g == group
+
+ {{g, k}, _} ->
+ g == group and k == key
+
+ {{g, k, subkeys}, _} ->
+ g == group and k == key and Enum.any?(Keyword.keys(value), &(&1 in subkeys))
+ end)
+ end
+
+ def find_relation(relations, path) do
+ with nil <- Enum.find(relations, fn {key, _} -> key == path end) do
+ {:error, :relation_not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/application/environment.ex b/lib/pleroma/application/environment.ex
new file mode 100644
index 000000000..9bad9980c
--- /dev/null
+++ b/lib/pleroma/application/environment.ex
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Application.Environment do
+ @moduledoc """
+ Overwrites environment config with settings from config file or database.
+ """
+
+ require Logger
+
+ @spec load_from_db_and_update(keyword()) :: :ok
+ def load_from_db_and_update(opts \\ []) do
+ Pleroma.ConfigDB.all()
+ |> update(opts)
+ end
+
+ @spec update([Pleroma.ConfigDB.t()], keyword()) :: :ok
+ def update(changes, opts \\ []) when is_list(changes) do
+ if Pleroma.Config.get(:configurable_from_database) do
+ defaults = Pleroma.Config.Holder.default_config()
+
+ configure_logger_and_quack(changes, defaults)
+
+ changes
+ |> Enum.map(fn config ->
+ {_, merged_value} = Pleroma.ConfigDB.merge_with_default(config, defaults)
+
+ %{config | value: merged_value}
+ end)
+ |> Enum.each(&update_env(&1))
+
+ cond do
+ # restart only apps on pleroma start
+ opts[:pleroma_start] ->
+ changes
+ |> Enum.filter(fn %{group: group} ->
+ group not in [:logger, :quack, :pleroma, :prometheus, :postgrex]
+ end)
+ |> Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart()
+
+ Pleroma.Application.ConfigDependentDeps.restart_dependencies()
+
+ opts[:only_update] ->
+ Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart(changes)
+
+ true ->
+ nil
+ end
+ end
+
+ :ok
+ end
+
+ defp configure_logger_and_quack(changes, defaults) do
+ {logger_changes, quack_changes} =
+ changes
+ |> Enum.filter(fn %{group: group} -> group in [:logger, :quack] end)
+ |> Enum.split_with(fn %{group: group} -> group == :logger end)
+
+ quack_config = to_keyword(quack_changes)
+
+ if quack_config != [] do
+ merged = Keyword.merge(defaults[:quack], quack_config)
+ Logger.configure_backend(Quack.Logger, merged)
+ end
+
+ logger_config = to_keyword(logger_changes)
+
+ if logger_config != [] do
+ merged = Keyword.merge(defaults, logger_config)
+
+ if logger_config[:backends] do
+ Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
+
+ Enum.each(merged[:backends], &Logger.add_backend/1)
+ end
+
+ if logger_config[:console] do
+ console = merged[:console]
+ console = put_in(console[:format], console[:format] <> "\n")
+
+ Logger.configure_backend(:console, console)
+ end
+
+ if logger_config[:ex_syslogger] do
+ Logger.configure_backend({ExSyslogger, :ex_syslogger}, merged[:ex_syslogger])
+ end
+
+ Logger.configure(merged)
+ end
+ end
+
+ defp to_keyword(changes) do
+ Enum.reduce(changes, [], fn
+ %{key: key, value: value}, acc ->
+ Keyword.put(acc, key, value)
+ end)
+ end
+
+ defp update_env(%{group: group, key: key, value: nil}), do: Application.delete_env(group, key)
+
+ defp update_env(%{group: group, key: key, value: config}) do
+ Application.put_env(group, key, config)
+ end
+end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application/requirements.ex
similarity index 81%
rename from lib/pleroma/application_requirements.ex
rename to lib/pleroma/application/requirements.ex
index 6ef65b263..c2c7f88cf 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application/requirements.ex
@@ -1,230 +1,261 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.ApplicationRequirements do
+defmodule Pleroma.Application.Requirements do
@moduledoc """
- The module represents the collection of validations to runs before start server.
+ Module contains collection of requirements before server start.
"""
defmodule VerifyError, do: defexception([:message])
alias Pleroma.Config
alias Pleroma.Helpers.MediaHelper
import Ecto.Query
require Logger
@spec verify!() :: :ok | VerifyError.t()
def verify! do
+ adapter = Application.get_env(:tesla, :adapter)
+
:ok
|> check_system_commands!()
|> check_confirmation_accounts!()
|> check_migrations_applied!()
|> check_welcome_message_config!()
|> check_rum!()
|> check_repo_pool_size!()
- |> handle_result()
+ |> check_otp_version!(adapter)
+ |> handle_result!()
end
- defp handle_result(:ok), do: :ok
- defp handle_result({:error, message}), do: raise(VerifyError, message: message)
+ defp handle_result!(:ok), do: :ok
+ defp handle_result!({:error, message}), do: raise(VerifyError, message: message)
defp check_welcome_message_config!(:ok) do
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
not Pleroma.Emails.Mailer.enabled?() do
Logger.error("""
To send welcome email do you need to enable mail.
\nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
""")
{:error, "The mail disabled."}
else
:ok
end
end
defp check_welcome_message_config!(result), do: result
# Checks account confirmation email
#
def check_confirmation_accounts!(:ok) do
if Pleroma.Config.get([:instance, :account_activation_required]) &&
not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
Logger.error(
"Account activation enabled, but no Mailer settings enabled.\n" <>
"Please set config :pleroma, :instance, account_activation_required: false\n" <>
"Otherwise setup and enable Mailer."
)
{:error,
"Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
else
:ok
end
end
def check_confirmation_accounts!(result), do: result
# Checks for pending migrations.
#
def check_migrations_applied!(:ok) do
unless Pleroma.Config.get(
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
false
) do
{_, res, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
down_migrations =
Ecto.Migrator.migrations(repo)
|> Enum.reject(fn
{:up, _, _} -> true
{:down, _, _} -> false
end)
if length(down_migrations) > 0 do
down_migrations_text =
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
Logger.error(
"The following migrations were not applied:\n#{down_migrations_text}" <>
"If you want to start Pleroma anyway, set\n" <>
"config :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
)
{:error, "Unapplied Migrations detected"}
else
:ok
end
end)
res
else
:ok
end
end
def check_migrations_applied!(result), do: result
# Checks for settings of RUM indexes.
#
defp check_rum!(:ok) do
{_, res, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
migrate =
from(o in "columns",
where: o.table_name == "objects",
where: o.column_name == "fts_content"
)
|> repo.exists?(prefix: "information_schema")
setting = Pleroma.Config.get([:database, :rum_enabled], false)
do_check_rum!(setting, migrate)
end)
res
end
defp check_rum!(result), do: result
defp do_check_rum!(setting, migrate) do
case {setting, migrate} do
{true, false} ->
Logger.error(
"Use `RUM` index is enabled, but were not applied migrations for it.\n" <>
"If you want to start Pleroma anyway, set\n" <>
"config :pleroma, :database, rum_enabled: false\n" <>
"Otherwise apply the following migrations:\n" <>
"`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"
)
{:error, "Unapplied RUM Migrations detected"}
{false, true} ->
Logger.error(
"Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\n" <>
"If you want to use `RUM`, set\n" <>
"config :pleroma, :database, rum_enabled: true\n" <>
"Otherwise roll `RUM` migrations back.\n" <>
"`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"
)
{:error, "RUM Migrations detected"}
_ ->
:ok
end
end
defp check_system_commands!(:ok) do
filter_commands_statuses = [
- check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
- check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
- check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
+ check_filter!(Pleroma.Upload.Filters.Exiftool, "exiftool"),
+ check_filter!(Pleroma.Upload.Filters.Mogrify, "mogrify"),
+ check_filter!(Pleroma.Upload.Filters.Mogrifun, "mogrify")
]
preview_proxy_commands_status =
if !Config.get([:media_preview_proxy, :enabled]) or
MediaHelper.missing_dependencies() == [] do
true
else
Logger.error(
"The following dependencies required by Media preview proxy " <>
"(which is currently enabled) are not installed: " <>
inspect(MediaHelper.missing_dependencies())
)
false
end
if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do
:ok
else
{:error,
"System commands missing. Check logs and see `docs/installation` for more details."}
end
end
defp check_system_commands!(result), do: result
defp check_repo_pool_size!(:ok) do
if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and
not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do
Logger.error("""
!!!CONFIG WARNING!!!
The database pool size has been altered from the recommended value of 10.
Please revert or ensure your database is tuned appropriately and then set
`config :pleroma, :dangerzone, override_repo_pool_size: true`.
If you are experiencing database timeouts, please check the "Optimizing
your PostgreSQL performance" section in the documentation. If you still
encounter issues after that, please open an issue on the tracker.
""")
{:error, "Repo.pool_size different than recommended value."}
else
:ok
end
end
defp check_repo_pool_size!(result), do: result
- defp check_filter(filter, command_required) do
+ defp check_filter!(filter, command_required) do
filters = Config.get([Pleroma.Upload, :filters])
if filter in filters and not Pleroma.Utils.command_available?(command_required) do
Logger.error(
"#{filter} is specified in list of Pleroma.Upload filters, but the " <>
"#{command_required} command is not found"
)
false
else
true
end
end
+
+ defp check_otp_version!(:ok, Tesla.Adapter.Gun) do
+ if version = Pleroma.OTPVersion.version() do
+ [major, minor] =
+ version
+ |> String.split(".")
+ |> Enum.map(&String.to_integer/1)
+ |> Enum.take(2)
+
+ if (major == 22 and minor < 2) or major < 22 do
+ Logger.error("
+ !!!OTP VERSION ERROR!!!
+ You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
+ ")
+ {:error, "OTP version error"}
+ else
+ :ok
+ end
+ else
+ Logger.error("
+ !!!OTP VERSION ERROR!!!
+ To support correct handling of unordered certificates chains - OTP version must be > 22.2.
+ ")
+ {:error, "OTP version error"}
+ end
+ end
+
+ defp check_otp_version!(result, _), do: result
end
diff --git a/lib/pleroma/application/start_up_dependencies.ex b/lib/pleroma/application/start_up_dependencies.ex
new file mode 100644
index 000000000..3eadc56c0
--- /dev/null
+++ b/lib/pleroma/application/start_up_dependencies.ex
@@ -0,0 +1,195 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Application.StartUpDependencies do
+ @moduledoc """
+ Starts common and config-dependent Pleroma dependencies.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.Web.Endpoint
+
+ require Cachex.Spec
+ require Logger
+
+ @type config_path() :: {atom(), atom()} | {atom(), atom(), [atom()]}
+ @type relation() :: {config_path(), module()}
+
+ @spec start_all(Pleroma.Application.env()) ::
+ :ok | {:error, {:already_started, pid()} | :max_children | term()}
+ def start_all(env) do
+ with :ok <- start_common_deps(env),
+ :ok <- start_config_dependent_deps(env) do
+ :ok
+ end
+ end
+
+ @spec adapter_module() :: module()
+ def adapter_module do
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
+ Pleroma.Gun.GunSupervisor
+ else
+ Pleroma.HTTP.HackneySupervisor
+ end
+ end
+
+ @spec spec(module()) :: module() | {module(), keyword()}
+ def spec(Oban), do: {Oban, Config.get(Oban)}
+
+ def spec(Pleroma.Web.StreamerRegistry) do
+ {Registry,
+ [
+ name: Pleroma.Web.Streamer.registry(),
+ keys: :duplicate,
+ partitions: System.schedulers_online()
+ ]}
+ end
+
+ def spec(child), do: child
+
+ @spec cachex_spec({String.t(), keyword()}) :: :supervisor.child_spec()
+ def cachex_spec({type, opts}) do
+ %{
+ id: String.to_atom("cachex_" <> type),
+ start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
+ type: :worker
+ }
+ end
+
+ defp start_common_deps(env) do
+ fun = fn child ->
+ DynamicSupervisor.start_child(Pleroma.Application.dynamic_supervisor(), spec(child))
+ end
+
+ [
+ Pleroma.Emoji,
+ Pleroma.Stats,
+ Pleroma.JobQueueMonitor,
+ {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
+ %{
+ id: :web_push_init,
+ start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
+ restart: :temporary
+ }
+ ]
+ |> add_cachex_deps()
+ |> maybe_add_init_internal_fetch_actor_task(env)
+ |> maybe_add_pub_sub()
+ |> maybe_add_backgroud_migrator()
+ |> start_while(fun)
+ end
+
+ defp start_config_dependent_deps(env) do
+ fun = fn child -> Pleroma.Application.ConfigDependentDeps.start_dependency(child) end
+
+ [
+ Pleroma.Web.Plugs.RateLimiter.Supervisor,
+ Oban,
+ Endpoint,
+ Pleroma.Gopher.Server
+ ]
+ |> add_http_children(env)
+ |> maybe_add_streamer(env)
+ |> maybe_add_chat_child()
+ |> start_while(fun)
+ end
+
+ defp start_while(deps, fun) do
+ Enum.reduce_while(deps, :ok, fn child, acc ->
+ case fun.(child) do
+ {:ok, _} ->
+ {:cont, acc}
+
+ # consider this behavior is normal
+ :ignore ->
+ Logger.info("#{inspect(child)} is ignored.")
+ {:cont, acc}
+
+ error ->
+ Logger.error("Child #{inspect(child)} can't be started. #{inspect(error)}")
+ {:halt, error}
+ end
+ end)
+ end
+
+ @spec cachex_deps() :: [tuple()]
+ def cachex_deps do
+ captcha_clean_up_interval =
+ [Pleroma.Captcha, :seconds_valid]
+ |> Config.get!()
+ |> :timer.seconds()
+
+ [
+ {"used_captcha", expiration: Cachex.Spec.expiration(interval: captcha_clean_up_interval)},
+ {"user", expiration: cachex_expiration(25_000, 1000), limit: 2500},
+ {"object", expiration: cachex_expiration(25_000, 1000), limit: 2500},
+ {"rich_media",
+ expiration: Cachex.Spec.expiration(default: :timer.minutes(120)), limit: 5000},
+ {"scrubber", limit: 2500},
+ {"idempotency", expiration: cachex_expiration(21_600, 60), limit: 2500},
+ {"web_resp", limit: 2500},
+ {"emoji_packs", expiration: cachex_expiration(300, 60), limit: 10},
+ {"failed_proxy_url", limit: 2500},
+ {"banned_urls",
+ expiration: Cachex.Spec.expiration(default: :timer.hours(24 * 30)), limit: 5_000},
+ {"chat_message_id_idempotency_key",
+ expiration: cachex_expiration(:timer.minutes(2), :timer.seconds(60)), limit: 500_000}
+ ]
+ end
+
+ defp add_cachex_deps(application_deps) do
+ cachex_deps()
+ |> Enum.reduce(application_deps, fn cachex_init_args, acc ->
+ [cachex_spec(cachex_init_args) | acc]
+ end)
+ end
+
+ defp cachex_expiration(default, interval) do
+ Cachex.Spec.expiration(default: :timer.seconds(default), interval: :timer.seconds(interval))
+ end
+
+ defp maybe_add_init_internal_fetch_actor_task(children, :test), do: children
+
+ defp maybe_add_init_internal_fetch_actor_task(children, _) do
+ [
+ %{
+ id: :internal_fetch_init,
+ start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
+ restart: :temporary
+ }
+ | children
+ ]
+ end
+
+ defp maybe_add_streamer(children, env) when env in [:test, :benchmark], do: children
+ defp maybe_add_streamer(children, _), do: [Pleroma.Web.Streamer.registry() | children]
+
+ defp maybe_add_background_migrator(children, env) when env in [:test, :benchmark], do: children
+
+ defp maybe_add_backgroud_migrator(children, _) do
+ [Pleroma.Migrators.HashtagsTableMigrator | children]
+ end
+
+ defp add_http_children(children, :test) do
+ [Pleroma.HTTP.HackneySupervisor, Pleroma.Gun.GunSupervisor | children]
+ end
+
+ defp add_http_children(children, _), do: [adapter_module() | children]
+
+ defp maybe_add_chat_child(children) do
+ if Config.get([:chat, :enabled]) do
+ [Pleroma.Web.ChatChannel.ChatChannelState | children]
+ else
+ children
+ end
+ end
+
+ defp maybe_add_pub_sub(children) do
+ if Config.get([:chat, :enabled]) do
+ [{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]} | children]
+ else
+ children
+ end
+ end
+end
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 24aa5993b..19868d174 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -1,218 +1,256 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
alias Pleroma.Config
require Logger
alias Pleroma.Config
@type config_namespace() :: atom() | [atom()]
@type config_map() :: {config_namespace(), config_namespace(), String.t()}
@mrf_config_map [
{[:instance, :rewrite_policy], [:mrf, :policies],
"\n* `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`"},
{[:instance, :mrf_transparency], [:mrf, :transparency],
"\n* `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`"},
{[:instance, :mrf_transparency_exclusions], [:mrf, :transparency_exclusions],
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
""")
:error
else
:ok
end
end
def warn do
with :ok <- check_hellthread_threshold(),
:ok <- check_old_mrf_config(),
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
:ok <- check_gun_pool_options(),
:ok <- check_activity_expiration_config(),
:ok <- check_remote_ip_plug_name(),
- :ok <- check_uploders_s3_public_endpoint() do
+ :ok <- check_uploders_s3_public_endpoint(),
+ :ok <- check_oban_config() do
:ok
else
_ ->
:error
end
end
def check_welcome_message_config do
instance_config = Pleroma.Config.get([:instance])
use_old_config =
Keyword.has_key?(instance_config, :welcome_user_nickname) or
Keyword.has_key?(instance_config, :welcome_message)
if use_old_config do
Logger.error("""
!!!DEPRECATION WARNING!!!
Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g.,
\n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to:
\n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`"
""")
:error
else
:ok
end
end
def check_old_mrf_config do
warning_preface = """
!!!DEPRECATION WARNING!!!
Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
"""
move_namespace_and_warn(@mrf_config_map, warning_preface)
end
- @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
+ @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error
def move_namespace_and_warn(config_map, warning_preface) do
warning =
Enum.reduce(config_map, "", fn
{old, new, err_msg}, acc ->
old_config = Config.get(old)
if old_config do
Config.put(new, old_config)
acc <> err_msg
else
acc
end
end)
if warning == "" do
:ok
else
Logger.warn(warning_preface <> warning)
:error
end
end
- @spec check_media_proxy_whitelist_config() :: :ok | nil
+ @spec check_media_proxy_whitelist_config() :: :ok | :error
def check_media_proxy_whitelist_config do
whitelist = Config.get([:media_proxy, :whitelist])
if Enum.any?(whitelist, &(not String.starts_with?(&1, "http"))) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
""")
:error
else
:ok
end
end
def check_gun_pool_options do
pool_config = Config.get(:connections_pool)
if timeout = pool_config[:await_up_timeout] do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases.
""")
Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout))
end
pools_configs = Config.get(:pools)
warning_preface = """
!!!DEPRECATION WARNING!!!
Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
"""
updated_config =
Enum.reduce(pools_configs, [], fn {pool_name, config}, acc ->
if timeout = config[:timeout] do
Keyword.put(acc, pool_name, Keyword.put_new(config, :recv_timeout, timeout))
else
acc
end
end)
if updated_config != [] do
pool_warnings =
updated_config
|> Keyword.keys()
|> Enum.map(fn pool_name ->
"\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`"
end)
Logger.warn(Enum.join([warning_preface | pool_warnings]))
Config.put(:pools, updated_config)
:error
else
:ok
end
end
- @spec check_activity_expiration_config() :: :ok | nil
+ @spec check_activity_expiration_config() :: :ok | :error
def check_activity_expiration_config do
warning_preface = """
!!!DEPRECATION WARNING!!!
Your config is using old namespace for activity expiration configuration. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later:
"""
move_namespace_and_warn(
[
{Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity,
"\n* `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"}
],
warning_preface
)
end
@spec check_remote_ip_plug_name() :: :ok | nil
def check_remote_ip_plug_name do
warning_preface = """
!!!DEPRECATION WARNING!!!
Your config is using old namespace for RemoteIp Plug. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later:
"""
move_namespace_and_warn(
[
{Pleroma.Plugs.RemoteIp, Pleroma.Web.Plugs.RemoteIp,
"\n* `config :pleroma, Pleroma.Plugs.RemoteIp` is now `config :pleroma, Pleroma.Web.Plugs.RemoteIp`"}
],
warning_preface
)
end
@spec check_uploders_s3_public_endpoint() :: :ok | nil
def check_uploders_s3_public_endpoint do
s3_config = Pleroma.Config.get([Pleroma.Uploaders.S3])
use_old_config = Keyword.has_key?(s3_config, :public_endpoint)
if use_old_config do
Logger.error("""
!!!DEPRECATION WARNING!!!
Your config is using the old setting for controlling the URL of media uploaded to your S3 bucket.\n
Please make the following change at your earliest convenience.\n
\n* `config :pleroma, Pleroma.Uploaders.S3, public_endpoint` is now equal to:
\n* `config :pleroma, Pleroma.Upload, base_url`
""")
:error
else
:ok
end
end
+
+ @spec check_oban_config() :: :ok | :error
+ def check_oban_config do
+ oban_config = Config.get(Oban)
+
+ {crontab, changed?} =
+ [
+ Pleroma.Workers.Cron.StatsWorker,
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
+ Pleroma.Workers.Cron.ClearOauthTokenWorker
+ ]
+ |> Enum.reduce({oban_config[:crontab], false}, fn removed_worker, {acc, changed?} ->
+ with acc when is_list(acc) <- acc,
+ setting when is_tuple(setting) <-
+ Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
+ """
+ !!!OBAN CONFIG WARNING!!!
+ You are using old workers in Oban crontab settings, which were removed.
+ Please, remove setting from crontab in your config file (prod.secret.exs): #{
+ inspect(setting)
+ }
+ """
+ |> Logger.warn()
+
+ {List.delete(acc, setting), true}
+ else
+ _ -> {acc, changed?}
+ end
+ end)
+
+ if changed? do
+ Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
+ :error
+ else
+ :ok
+ end
+ end
end
diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index b64d06707..4bf046bcf 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -1,59 +1,78 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do
+ @reject_groups [
+ :postgrex,
+ :tesla,
+ :phoenix,
+ :tzdata,
+ :http_signatures,
+ :web_push_encryption,
+ :floki,
+ :pbkdf2_elixir
+ ]
+
@reject_keys [
Pleroma.Repo,
Pleroma.Web.Endpoint,
+ Pleroma.InstallerWeb.Endpoint,
:env,
:configurable_from_database,
:database,
- :swarm
- ]
-
- @reject_groups [
- :postgrex,
- :tesla
+ :ecto_repos,
+ Pleroma.Gun,
+ Pleroma.ReverseProxy.Client,
+ Pleroma.Web.Auth.Authenticator
]
if Code.ensure_loaded?(Config.Reader) do
@reader Config.Reader
-
- def read(path), do: @reader.read!(path)
else
# support for Elixir less than 1.9
@reader Mix.Config
- def read(path) do
- path
- |> @reader.eval!()
- |> elem(0)
- end
end
- @spec read(Path.t()) :: keyword()
+ @spec read!(Path.t()) :: keyword()
+ def read!(path), do: @reader.read!(path)
@spec merge(keyword(), keyword()) :: keyword()
def merge(c1, c2), do: @reader.merge(c1, c2)
@spec default_config() :: keyword()
def default_config do
- "config/config.exs"
- |> read()
- |> filter()
+ config =
+ "config/config.exs"
+ |> read!()
+ |> filter()
+
+ logger_config = Application.get_all_env(:logger)
+
+ merge(config, logger: logger_config)
end
- defp filter(configs) do
+ @spec filter(keyword()) :: keyword()
+ def filter(configs) do
configs
- |> Keyword.keys()
- |> Enum.reduce([], &Keyword.put(&2, &1, filter_group(&1, configs)))
- end
+ |> Enum.reduce([], fn
+ {group, _settings}, group_acc when group in @reject_groups ->
+ group_acc
+
+ {group, settings}, group_acc ->
+ filtered_settings =
+ Enum.reduce(settings, [], fn
+ {key, _value}, settings_acc when key in @reject_keys ->
+ settings_acc
+
+ {key, _value}, settings_acc when group == :phoenix and key == :serve_endpoint ->
+ settings_acc
+
+ {key, value}, settings_acc ->
+ Keyword.put(settings_acc, key, value)
+ end)
- @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 in @reject_groups or
- (group == :phoenix and key == :serve_endpoints)
+ Keyword.put(group_acc, group, filtered_settings)
end)
end
end
diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex
deleted file mode 100644
index 3e63bca40..000000000
--- a/lib/pleroma/config/oban.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Config.Oban do
- require Logger
-
- def warn do
- oban_config = Pleroma.Config.get(Oban)
-
- crontab =
- [
- Pleroma.Workers.Cron.StatsWorker,
- Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
- Pleroma.Workers.Cron.ClearOauthTokenWorker
- ]
- |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc ->
- with acc when is_list(acc) <- acc,
- setting when is_tuple(setting) <-
- Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
- """
- !!!OBAN CONFIG WARNING!!!
- You are using old workers in Oban crontab settings, which were removed.
- Please, remove setting from crontab in your config file (prod.secret.exs): #{
- inspect(setting)
- }
- """
- |> Logger.warn()
-
- List.delete(acc, setting)
- else
- _ -> acc
- end
- end)
-
- Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
- end
-end
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
deleted file mode 100644
index aad45aab8..000000000
--- a/lib/pleroma/config/transfer_task.ex
+++ /dev/null
@@ -1,201 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Config.TransferTask do
- use Task
-
- alias Pleroma.Config
- alias Pleroma.ConfigDB
- alias Pleroma.Repo
-
- require Logger
-
- @type env() :: :test | :benchmark | :dev | :prod
-
- @reboot_time_keys [
- {:pleroma, :hackney_pools},
- {:pleroma, :chat},
- {:pleroma, Oban},
- {:pleroma, :rate_limit},
- {:pleroma, :markup},
- {:pleroma, :streamer},
- {:pleroma, :pools},
- {:pleroma, :connections_pool}
- ]
-
- @reboot_time_subkeys [
- {:pleroma, Pleroma.Captcha, [:seconds_valid]},
- {:pleroma, Pleroma.Upload, [:proxy_remote]},
- {:pleroma, :instance, [:upload_limit]},
- {:pleroma, :gopher, [:enabled]}
- ]
-
- 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
-
- @spec load_and_update_env([ConfigDB.t()], boolean()) :: :ok
- def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do
- with {_, true} <- {:configurable, Config.get(:configurable_from_database)} do
- # We need to restart applications for loaded settings take effect
-
- {logger, other} =
- (Repo.all(ConfigDB) ++ deleted_settings)
- |> Enum.map(&merge_with_default/1)
- |> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
-
- logger
- |> Enum.sort()
- |> Enum.each(&configure/1)
-
- started_applications = Application.started_applications()
-
- # TODO: some problem with prometheus after restart!
- reject = [nil, :prometheus, :postgrex]
-
- reject =
- if restart_pleroma? do
- reject
- else
- [:pleroma | reject]
- end
-
- other
- |> Enum.map(&update/1)
- |> Enum.uniq()
- |> Enum.reject(&(&1 in reject))
- |> maybe_set_pleroma_last()
- |> Enum.each(&restart(started_applications, &1, Config.get(:env)))
-
- :ok
- else
- {:configurable, false} -> Restarter.Pleroma.rebooted()
- end
- end
-
- defp maybe_set_pleroma_last(apps) do
- # to be ensured that pleroma will be restarted last
- if :pleroma in apps do
- apps
- |> List.delete(:pleroma)
- |> List.insert_at(-1, :pleroma)
- else
- Restarter.Pleroma.rebooted()
- apps
- end
- end
-
- defp merge_with_default(%{group: group, key: key, value: value} = setting) do
- default = Config.Holder.default_config(group, key)
-
- merged =
- cond do
- Ecto.get_meta(setting, :state) == :deleted -> default
- can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
- true -> value
- end
-
- {group, key, value, merged}
- end
-
- # change logger configuration in runtime, without restart
- defp configure({:quack, key, _, merged}) do
- Logger.configure_backend(Quack.Logger, [{key, merged}])
- :ok = update_env(:quack, key, merged)
- end
-
- defp configure({_, :backends, _, merged}) do
- # removing current backends
- Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
-
- Enum.each(merged, &Logger.add_backend/1)
-
- :ok = update_env(:logger, :backends, merged)
- end
-
- defp configure({_, key, _, merged}) when key in [:console, :ex_syslogger] do
- merged =
- if key == :console do
- put_in(merged[:format], merged[:format] <> "\n")
- else
- merged
- end
-
- backend =
- if key == :ex_syslogger,
- do: {ExSyslogger, :ex_syslogger},
- else: key
-
- Logger.configure_backend(backend, merged)
- :ok = update_env(:logger, key, merged)
- end
-
- defp configure({_, key, _, merged}) do
- Logger.configure([{key, merged}])
- :ok = update_env(:logger, key, merged)
- end
-
- defp update({group, key, value, merged}) do
- try do
- :ok = update_env(group, key, merged)
-
- if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
- rescue
- error ->
- error_msg =
- "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
- inspect(value)
- } error: #{inspect(error)}"
-
- Logger.warn(error_msg)
-
- nil
- end
- end
-
- defp update_env(group, key, nil), do: Application.delete_env(group, key)
- defp update_env(group, key, value), do: Application.put_env(group, key, value)
-
- @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
- def pleroma_need_restart?(group, key, value) do
- group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
- end
-
- defp group_and_key_need_reboot?(group, key) do
- Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
- end
-
- defp group_and_subkey_need_reboot?(group, key, value) do
- Keyword.keyword?(value) and
- Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
- g == group and k == key and
- Enum.any?(Keyword.keys(value), &(&1 in subkeys))
- end)
- end
-
- defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
-
- defp restart(started_applications, app, _) do
- with {^app, _, _} <- List.keyfind(started_applications, app, 0),
- :ok <- Application.stop(app) do
- :ok = Application.start(app)
- else
- nil ->
- Logger.warn("#{app} is not started.")
-
- error ->
- error
- |> inspect()
- |> Logger.warn()
- end
- end
-
- defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do
- Keyword.keyword?(val1) and Keyword.keyword?(val2)
- end
-
- defp can_be_merged?(_val1, _val2), do: false
-end
diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex
index b874e0e37..c3bcd5031 100644
--- a/lib/pleroma/config_db.ex
+++ b/lib/pleroma/config_db.ex
@@ -1,392 +1,416 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ConfigDB do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, only: [select: 3, from: 2]
import Pleroma.Web.Gettext
alias __MODULE__
alias Pleroma.Repo
@type t :: %__MODULE__{}
@full_subkey_update [
{:pleroma, :assets, :mascots},
{:pleroma, :emoji, :groups},
{:pleroma, :workers, :retries},
{:pleroma, :mrf_subchain, :match_actor},
{:pleroma, :mrf_keyword, :replace}
]
schema "config" do
field(:key, Pleroma.EctoType.Config.Atom)
field(:group, Pleroma.EctoType.Config.Atom)
field(:value, Pleroma.EctoType.Config.BinaryValue)
field(:db, {:array, :string}, virtual: true, default: [])
timestamps()
end
+ @spec all() :: [t()]
+ def all, do: Repo.all(ConfigDB)
+
@spec get_all_as_keyword() :: keyword()
def get_all_as_keyword do
ConfigDB
|> select([c], {c.group, c.key, c.value})
|> Repo.all()
|> Enum.reduce([], fn {group, key, value}, acc ->
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
end)
end
@spec get_all_by_group(atom() | String.t()) :: [t()]
def get_all_by_group(group) do
from(c in ConfigDB, where: c.group == ^group) |> Repo.all()
end
@spec get_by_group_and_key(atom() | String.t(), atom() | String.t()) :: t() | nil
def get_by_group_and_key(group, key) do
get_by_params(%{group: group, key: key})
end
@spec get_by_params(map()) :: ConfigDB.t() | nil
def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params)
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
def changeset(config, params \\ %{}) do
config
|> cast(params, [:key, :group, :value])
|> validate_required([:key, :group, :value])
|> unique_constraint(:key, name: :config_group_key_index)
end
defp create(params) do
%ConfigDB{}
|> changeset(params)
|> Repo.insert()
end
defp update(%ConfigDB{} = config, %{value: value}) do
config
|> changeset(%{value: value})
|> Repo.update()
end
@spec get_db_keys(keyword(), any()) :: [String.t()]
def get_db_keys(value, key) do
keys =
if Keyword.keyword?(value) do
Keyword.keys(value)
else
[key]
end
Enum.map(keys, &to_json_types(&1))
end
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
def merge_group(group, key, old_value, new_value) do
new_keys = to_mapset(new_value)
intersect_keys = old_value |> to_mapset() |> MapSet.intersection(new_keys) |> MapSet.to_list()
merged_value = ConfigDB.merge(old_value, new_value)
@full_subkey_update
|> Enum.map(fn
{g, k, subkey} when g == group and k == key ->
if subkey in intersect_keys, do: subkey, else: []
_ ->
[]
end)
|> List.flatten()
|> Enum.reduce(merged_value, &Keyword.put(&2, &1, new_value[&1]))
end
+ @spec merge_with_default(t(), keyword()) :: {t(), any()}
+ def merge_with_default(%ConfigDB{} = change, defaults) do
+ %{group: group, key: key, value: value} = change
+ default = defaults[group][key]
+
+ merged =
+ cond do
+ Ecto.get_meta(change, :state) == :deleted -> default
+ can_be_merged?(default, value) -> merge_group(group, key, default, value)
+ true -> value
+ end
+
+ {change, merged}
+ end
+
+ defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do
+ Keyword.keyword?(val1) and Keyword.keyword?(val2)
+ end
+
+ defp can_be_merged?(_val1, _val2), do: false
+
defp to_mapset(keyword) do
keyword
|> Keyword.keys()
|> MapSet.new()
end
@spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean()
def sub_key_full_update?(group, key, subkeys) do
Enum.any?(@full_subkey_update, fn {g, k, subkey} ->
g == group and k == key and subkey in subkeys
end)
end
@spec merge(keyword(), keyword()) :: keyword()
def merge(config1, config2) when is_list(config1) and is_list(config2) do
Keyword.merge(config1, config2, fn _, app1, app2 ->
if Keyword.keyword?(app1) and Keyword.keyword?(app2) do
Keyword.merge(app1, app2, &deep_merge/3)
else
app2
end
end)
end
defp deep_merge(_key, value1, value2) do
if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
Keyword.merge(value1, value2, &deep_merge/3)
else
value2
end
end
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
def update_or_create(params) do
params = Map.put(params, :value, to_elixir_types(params[:value]))
search_opts = Map.take(params, [:group, :key])
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
{_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
{_, true, config} <-
{:can_be_merged, is_list(params[:value]) and is_list(config.value), config} do
new_value = merge_group(config.group, config.key, config.value, params[:value])
update(config, %{value: new_value})
else
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
update(config, params)
nil ->
create(params)
end
end
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
defp only_full_update?(%ConfigDB{group: group, key: key}) do
full_key_update = [
{:pleroma, :ecto_repos},
{:quack, :meta},
{:mime, :types},
{:cors_plug, [:max_age, :methods, :expose, :headers]},
{:swarm, :node_blacklist},
{:logger, :backends}
]
Enum.any?(full_key_update, fn
{s_group, s_key} ->
group == s_group and ((is_list(s_key) and key in s_key) or key == s_key)
end)
end
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
def delete(%ConfigDB{} = config), do: Repo.delete(config)
def delete(params) do
search_opts = Map.delete(params, :subkeys)
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
keys <- Enum.map(sub_keys, &string_to_elixir_types(&1)),
{_, config, new_value} when new_value != [] <-
{:partial_remove, config, Keyword.drop(config.value, keys)} do
update(config, %{value: new_value})
else
{:partial_remove, config, []} ->
Repo.delete(config)
{config, nil} ->
Repo.delete(config)
nil ->
err =
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
{:error, err}
end
end
@spec to_json_types(term()) :: map() | list() | boolean() | String.t()
def to_json_types(entity) when is_list(entity) do
Enum.map(entity, &to_json_types/1)
end
def to_json_types(%Regex{} = entity), do: inspect(entity)
def to_json_types(entity) when is_map(entity) do
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
end
def to_json_types({:args, args}) when is_list(args) do
arguments =
Enum.map(args, fn
arg when is_tuple(arg) -> inspect(arg)
arg -> to_json_types(arg)
end)
%{"tuple" => [":args", arguments]}
end
def to_json_types({:proxy_url, {type, :localhost, port}}) do
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
end
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
ip =
host
|> :inet_parse.ntoa()
|> to_string()
%{
"tuple" => [
":proxy_url",
%{"tuple" => [to_json_types(type), ip, port]}
]
}
end
def to_json_types({:proxy_url, {type, host, port}}) do
%{
"tuple" => [
":proxy_url",
%{"tuple" => [to_json_types(type), to_string(host), port]}
]
}
end
def to_json_types({:partial_chain, entity}),
do: %{"tuple" => [":partial_chain", inspect(entity)]}
def to_json_types(entity) when is_tuple(entity) do
value =
entity
|> Tuple.to_list()
|> to_json_types()
%{"tuple" => value}
end
def to_json_types(entity) when is_binary(entity), do: entity
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
entity
end
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
":#{entity}"
end
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
arguments =
Enum.map(args, fn arg ->
if String.contains?(arg, ["{", "}"]) do
{elem, []} = Code.eval_string(arg)
elem
else
to_elixir_types(arg)
end
end)
{:args, arguments}
end
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
{:proxy_url, {string_to_elixir_types(type), parse_host(host), port}}
end
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
{partial_chain, []} =
entity
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|> Code.eval_string()
{:partial_chain, partial_chain}
end
def to_elixir_types(%{"tuple" => entity}) do
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
end
def to_elixir_types(entity) when is_map(entity) do
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
end
def to_elixir_types(entity) when is_list(entity) do
Enum.map(entity, &to_elixir_types/1)
end
def to_elixir_types(entity) when is_binary(entity) do
entity
|> String.trim()
|> string_to_elixir_types()
end
def to_elixir_types(entity), do: entity
@spec string_to_elixir_types(String.t()) ::
atom() | Regex.t() | module() | String.t() | no_return()
def string_to_elixir_types("~r" <> _pattern = regex) do
pattern =
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
Regex.named_captures(pattern, regex),
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
result
end
end
def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
def string_to_elixir_types(value) do
if module_name?(value) do
String.to_existing_atom("Elixir." <> value)
else
value
end
end
defp parse_host("localhost"), do: :localhost
defp parse_host(host) do
charlist = to_charlist(host)
case :inet.parse_address(charlist) do
{:error, :einval} ->
charlist
{:ok, ip} ->
ip
end
end
defp find_valid_delimiter([], _string, _) do
raise(ArgumentError, message: "valid delimiter for Regex expression not found")
end
defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter)
when is_tuple(delimiter) do
if String.contains?(pattern, closing) do
find_valid_delimiter(others, pattern, regex_delimiter)
else
{:ok, {leading, closing}}
end
end
defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do
if String.contains?(pattern, delimiter) do
find_valid_delimiter(others, pattern, regex_delimiter)
else
{:ok, {delimiter, delimiter}}
end
end
@spec module_name?(String.t()) :: boolean()
def module_name?(string) do
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
string in ["Oban", "Ueberauth", "ExSyslogger"]
end
end
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
index f22432ea4..42ea15549 100644
--- a/lib/pleroma/docs/json.ex
+++ b/lib/pleroma/docs/json.ex
@@ -1,38 +1,38 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Docs.JSON do
@behaviour Pleroma.Docs.Generator
@external_resource "config/description.exs"
- @raw_config Pleroma.Config.Loader.read("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
descriptions =
Pleroma.Web.ActivityPub.MRF.config_descriptions()
|> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(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
with path <- "docs/generated_config.json",
{:ok, file} <- File.open(path, [:write, :utf8]),
formatted_descriptions <-
Pleroma.Docs.Generator.convert_to_strings(descriptions),
json <- Jason.encode!(formatted_descriptions),
:ok <- IO.write(file, json),
:ok <- File.close(file) do
{:ok, path}
end
end
end
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index 1b85c49f5..2fa85ef66 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -1,162 +1,169 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gopher.Server do
use GenServer
require Logger
def start_link(_) do
config = Pleroma.Config.get(:gopher, [])
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
port = Keyword.get(config, :port, 1234)
if Keyword.get(config, :enabled, false) do
- GenServer.start_link(__MODULE__, [ip, port], [])
+ GenServer.start_link(__MODULE__, [ip, port])
else
Logger.info("Gopher server disabled")
:ignore
end
end
+ @impl true
def init([ip, port]) do
Logger.info("Starting gopher server on #{port}")
:ranch.start_listener(
:gopher,
100,
:ranch_tcp,
[ip: ip, port: port],
__MODULE__.ProtocolHandler,
[]
)
+ Process.flag(:trap_exit, true)
{:ok, %{ip: ip, port: port}}
end
+
+ @impl true
+ def terminate(_reason, _state) do
+ :ranch.stop_listener(:gopher)
+ end
end
defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
{:ok, pid}
end
def init(ref, socket, transport, [] = _Opts) do
:ok = :ranch.accept_ack(ref)
loop(socket, transport)
end
def info(text) do
text = String.replace(text, ~r/[\t\n]/, "")
String.split(text, "\r")
|> Enum.map(fn text ->
"i#{text}\tfake\t(NULL)\t0\r\n"
end)
|> Enum.join("")
end
def link(name, selector, type \\ 1) do
address = Pleroma.Web.Endpoint.host()
port = Pleroma.Config.get([:gopher, :port], 1234)
dstport = Pleroma.Config.get([:gopher, :dstport], port)
"#{type}#{name}\t#{selector}\t#{address}\t#{dstport}\r\n"
end
def render_activities(activities) do
activities
|> Enum.reverse()
|> Enum.map(fn activity ->
user = User.get_cached_by_ap_id(activity.data["actor"])
object = Object.normalize(activity, fetch: false)
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info("#{like_count} likes, #{announcement_count} repeats") <>
"i\tfake\t(NULL)\t0\r\n" <>
info(HTML.strip_tags(String.replace(object.data["content"], "<br>", "\r")))
end)
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
end
def response("") do
info("Welcome to #{Pleroma.Config.get([:instance, :name], "Pleroma")}!") <>
link("Public Timeline", "/main/public") <>
link("Federated Timeline", "/main/all") <> ".\r\n"
end
def response("/main/public") do
posts =
%{type: ["Create"], local_only: true}
|> ActivityPub.fetch_public_activities()
|> render_activities()
info("Welcome to the Public Timeline!") <> posts <> ".\r\n"
end
def response("/main/all") do
posts =
%{type: ["Create"]}
|> ActivityPub.fetch_public_activities()
|> render_activities()
info("Welcome to the Federated Timeline!") <> posts <> ".\r\n"
end
def response("/notices/" <> id) do
with %Activity{} = activity <- Activity.get_by_id(id),
true <- Visibility.is_public?(activity) do
activities =
ActivityPub.fetch_activities_for_context(activity.data["context"])
|> render_activities
user = User.get_cached_by_ap_id(activity.data["actor"])
info("Post #{activity.id} by #{user.nickname}") <>
link("More posts by #{user.nickname}", "/users/#{user.nickname}") <> activities <> ".\r\n"
else
_e ->
info("Not public") <> ".\r\n"
end
end
def response("/users/" <> nickname) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
params = %{
type: ["Create"],
actor_id: user.ap_id
}
activities =
params
|> ActivityPub.fetch_public_activities()
|> render_activities()
info("Posts by #{user.nickname}") <> activities <> ".\r\n"
else
_e ->
info("No such user") <> ".\r\n"
end
end
def loop(socket, transport) do
case transport.recv(socket, 0, 5000) do
{:ok, data} ->
data = String.trim_trailing(data, "\r\n")
transport.send(socket, response(data))
:ok = transport.close(socket)
_ ->
:ok = transport.close(socket)
end
end
end
diff --git a/lib/pleroma/gun/gun_supervisor.ex b/lib/pleroma/gun/gun_supervisor.ex
new file mode 100644
index 000000000..c72dfdd24
--- /dev/null
+++ b/lib/pleroma/gun/gun_supervisor.ex
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Gun.GunSupervisor do
+ use Supervisor
+
+ def start_link(_) do
+ Supervisor.start_link(__MODULE__, :no_args)
+ end
+
+ def init(_) do
+ children =
+ Pleroma.Gun.ConnectionPool.children() ++
+ [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+end
diff --git a/lib/pleroma/http/hackney_supervisor.ex b/lib/pleroma/http/hackney_supervisor.ex
new file mode 100644
index 000000000..c230f2564
--- /dev/null
+++ b/lib/pleroma/http/hackney_supervisor.ex
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.HackneySupervisor do
+ use Supervisor
+
+ def start_link(_) do
+ Supervisor.start_link(__MODULE__, :no_arg)
+ end
+
+ def init(_) do
+ pools = [:federation, :media]
+
+ pools =
+ if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
+ [:upload | pools]
+ else
+ pools
+ end
+
+ children =
+ for pool <- pools do
+ options = Pleroma.Config.get([:hackney_pools, pool])
+
+ :hackney_pool.child_spec(pool, options)
+ end
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index 839ac1a8d..daa4c451c 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -1,463 +1,463 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, fetch_integer_param: 3]
alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.ModerationLog
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.ModerationLogView
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
@users_page_size 50
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:accounts"]}
when action in [:right_get, :show_user_credentials, :create_backup]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write:accounts"]}
when action in [
:get_password_reset,
:force_password_reset,
:tag_users,
:untag_users,
:right_add,
:right_add_multiple,
:right_delete,
:disable_mfa,
:right_delete_multiple,
:update_user_credentials
]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:statuses"]}
when action in [:list_user_statuses, :list_instance_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:chats"]}
when action in [:list_user_chats]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:read"]}
when action in [
:list_log,
:stats,
:need_reboot
]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write"]}
when action in [
:restart,
:resend_confirmation_email,
:confirm_email,
:reload_emoji
]
)
action_fallback(AdminAPI.FallbackController)
def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
exclude_reblogs: not with_reblogs,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
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, for: admin) do
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_user_activities(user, nil, %{
limit: page_size,
offset: (page - 1) * page_size,
godmode: godmode,
exclude_reblogs: not with_reblogs,
pagination_type: :offset,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
else
_ -> {:error, :not_found}
end
end
def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
chats =
Pleroma.Chat.for_user_query(user_id)
|> Pleroma.Repo.all()
conn
|> put_view(AdminAPI.ChatView)
|> render("index.json", chats: chats)
else
_ -> {:error, :not_found}
end
end
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
actor: admin,
nicknames: nicknames,
tags: tags,
action: "tag"
})
json_response(conn, :no_content, "")
end
end
def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.untag(nicknames, tags) do
ModerationLog.insert_log(%{
actor: admin,
nicknames: nicknames,
tags: tags,
action: "untag"
})
json_response(conn, :no_content, "")
end
end
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nicknames" => nicknames
})
when permission_group in ["moderator", "admin"] do
update = %{:"is_#{permission_group}" => true}
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
for u <- users, do: User.admin_api_update(u, update)
ModerationLog.insert_log(%{
action: "grant",
actor: admin,
subject: users,
permission: permission_group
})
json(conn, update)
end
def right_add_multiple(conn, _) do
render_error(conn, :not_found, "No such permission_group")
end
def right_add(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nickname" => nickname
})
when permission_group in ["moderator", "admin"] do
fields = %{:"is_#{permission_group}" => true}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
|> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "grant",
actor: admin,
subject: [user],
permission: permission_group
})
json(conn, fields)
end
def right_add(conn, _) do
render_error(conn, :not_found, "No such permission_group")
end
def right_get(conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
conn
|> json(%{
is_moderator: user.is_moderator,
is_admin: user.is_admin
})
end
def right_delete_multiple(
%{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
%{
"permission_group" => permission_group,
"nicknames" => nicknames
}
)
when permission_group in ["moderator", "admin"] do
with false <- Enum.member?(nicknames, admin_nickname) do
update = %{:"is_#{permission_group}" => false}
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
for u <- users, do: User.admin_api_update(u, update)
ModerationLog.insert_log(%{
action: "revoke",
actor: admin,
subject: users,
permission: permission_group
})
json(conn, update)
else
_ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
end
end
def right_delete_multiple(conn, _) do
render_error(conn, :not_found, "No such permission_group")
end
def right_delete(
%{assigns: %{user: admin}} = conn,
%{
"permission_group" => permission_group,
"nickname" => nickname
}
)
when permission_group in ["moderator", "admin"] do
fields = %{:"is_#{permission_group}" => false}
{:ok, user} =
nickname
|> User.get_cached_by_nickname()
|> User.admin_api_update(fields)
ModerationLog.insert_log(%{
action: "revoke",
actor: admin,
subject: [user],
permission: permission_group
})
json(conn, fields)
end
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
render_error(conn, :forbidden, "You can't revoke your own admin status.")
end
@doc "Get a password reset token (base64 string) for given nickname"
def get_password_reset(conn, %{"nickname" => nickname}) do
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
conn
|> json(%{
token: token.token,
link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
})
end
@doc "Force password reset for a given user"
def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
Enum.each(users, &User.force_password_reset_async/1)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "force_password_reset"
})
json_response(conn, :no_content, "")
end
@doc "Disable mfa for user's account."
def disable_mfa(conn, %{"nickname" => nickname}) do
case User.get_by_nickname(nickname) do
%User{} = user ->
MFA.disable(user)
json(conn, nickname)
_ ->
{:error, :not_found}
end
end
@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, for: admin) do
conn
|> put_view(AccountView)
|> render("credentials.json", %{user: user, for: admin})
else
_ -> {:error, :not_found}
end
end
@doc "Updates a given user"
def update_user_credentials(
%{assigns: %{user: admin}} = conn,
%{"nickname" => nickname} = params
) do
with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
{:ok, _user} <-
User.update_as_admin(user, params) do
ModerationLog.insert_log(%{
actor: admin,
subject: [user],
action: "updated_users"
})
if params["password"] do
User.force_password_reset_async(user)
end
ModerationLog.insert_log(%{
actor: admin,
subject: [user],
action: "force_password_reset"
})
json(conn, %{status: "success"})
else
{:error, changeset} ->
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
{:errors, errors}
_ ->
{:error, :not_found}
end
end
def list_log(conn, params) do
{page, page_size} = page_params(params)
log =
ModerationLog.get_all(%{
page: page,
page_size: page_size,
start_date: params["start_date"],
end_date: params["end_date"],
user_id: params["user_id"],
search: params["search"]
})
conn
|> put_view(ModerationLogView)
|> render("index.json", %{log: log})
end
def restart(conn, _params) do
with :ok <- configurable_from_database() do
- Restarter.Pleroma.restart(Config.get(:env), 50)
+ Task.start(Pleroma.Application.ConfigDependentDeps, :restart_dependencies, [])
json(conn, %{})
end
end
def need_reboot(conn, _params) do
- json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
+ json(conn, %{need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()})
end
defp configurable_from_database do
if Config.get(:configurable_from_database) do
:ok
else
{:error, "You must enable configurable_from_database in your config file."}
end
end
def reload_emoji(conn, _params) do
Pleroma.Emoji.reload()
json(conn, "ok")
end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
User.confirm(users)
ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
json(conn, "")
end
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users =
Enum.map(nicknames, fn nickname ->
nickname
|> User.get_cached_by_nickname()
|> User.send_confirmation_email()
end)
ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
json(conn, "")
end
def stats(conn, params) do
counters = Stats.get_status_visibility_count(params["instance"])
json(conn, %{"status_visibility" => counters})
end
def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_by_nickname(nickname),
{:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
json(conn, "")
end
end
defp page_params(params) do
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @users_page_size)
}
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index a718d7b8d..8ff118ce3 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -1,150 +1,138 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigController do
use Pleroma.Web, :controller
alias Pleroma.Config
alias Pleroma.ConfigDB
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update)
plug(
OAuthScopesPlug,
%{scopes: ["admin:read"]}
when action in [:show, :descriptions]
)
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
def descriptions(conn, _params) do
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
json(conn, descriptions)
end
def show(conn, %{only_db: true}) do
with :ok <- configurable_from_database() do
configs = Pleroma.Repo.all(ConfigDB)
render(conn, "index.json", %{
configs: configs,
- need_reboot: Restarter.Pleroma.need_reboot?()
+ need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()
})
end
end
def show(conn, _params) do
with :ok <- configurable_from_database() do
configs = ConfigDB.get_all_as_keyword()
merged =
Config.Holder.default_config()
|> ConfigDB.merge(configs)
|> Enum.map(fn {group, values} ->
Enum.map(values, fn {key, value} ->
db =
if configs[group][key] do
ConfigDB.get_db_keys(configs[group][key], key)
end
db_value = configs[group][key]
merged_value =
if not is_nil(db_value) and Keyword.keyword?(db_value) and
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
ConfigDB.merge_group(group, key, value, db_value)
else
value
end
%ConfigDB{
group: group,
key: key,
value: merged_value
}
|> Pleroma.Maps.put_if_present(:db, db)
end)
end)
|> List.flatten()
render(conn, "index.json", %{
configs: merged,
- need_reboot: Restarter.Pleroma.need_reboot?()
+ need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()
})
end
end
def update(%{body_params: %{configs: configs}} = conn, _) do
with :ok <- configurable_from_database() do
results =
configs
|> Enum.filter(&whitelisted_config?/1)
|> Enum.map(fn
%{group: group, key: key, delete: true} = params ->
ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
%{group: group, key: key, value: value} ->
ConfigDB.update_or_create(%{group: group, key: key, value: value})
end)
|> Enum.reject(fn {result, _} -> result == :error end)
-
- {deleted, updated} =
- results
|> Enum.map(fn {:ok, %{key: key, value: value} = config} ->
Map.put(config, :db, ConfigDB.get_db_keys(value, key))
end)
- |> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted))
-
- Config.TransferTask.load_and_update_env(deleted, false)
-
- if not Restarter.Pleroma.need_reboot?() do
- changed_reboot_settings? =
- (updated ++ deleted)
- |> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value))
- if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
- end
+ Pleroma.Application.Environment.update(results, only_update: true)
render(conn, "index.json", %{
- configs: updated,
- need_reboot: Restarter.Pleroma.need_reboot?()
+ configs: Enum.reject(results, &(Ecto.get_meta(&1, :state) == :deleted)),
+ need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()
})
end
end
defp configurable_from_database do
if Config.get(:configurable_from_database) do
:ok
else
{:error, "You must enable configurable_from_database in your config file."}
end
end
defp whitelisted_config?(group, key) do
if whitelisted_configs = Config.get(:database_config_whitelist) do
Enum.any?(whitelisted_configs, fn
{whitelisted_group} ->
group == inspect(whitelisted_group)
{whitelisted_group, whitelisted_key} ->
group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
end)
else
true
end
end
defp whitelisted_config?(%{group: group, key: key}) do
whitelisted_config?(group, key)
end
defp whitelisted_config?(%{group: group} = config) do
whitelisted_config?(group, config[:key])
end
end
diff --git a/mix.exs b/mix.exs
index 87ee01073..472e7228f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,364 +1,363 @@
defmodule Pleroma.Mixfile do
use Mix.Project
def project do
[
app: :pleroma,
version: version("2.3.50"),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: ["coveralls.html": :test],
# Docs
name: "Pleroma",
homepage_url: "https://pleroma.social/",
source_url: "https://git.pleroma.social/pleroma/pleroma",
docs: [
source_url_pattern:
"https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}",
logo: "priv/static/images/logo.png",
extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"),
groups_for_extras: [
"Installation manuals": Path.wildcard("docs/installation/*.md"),
Configuration: Path.wildcard("docs/config/*.md"),
Administration: Path.wildcard("docs/admin/*.md"),
"Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/api/*.md")
],
main: "readme",
output: "priv/static/doc"
],
releases: [
pleroma: [
include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, nil}]
]
]
]
end
def put_otp_version(%{path: target_path} = release) do
File.write!(
Path.join([target_path, "OTP_VERSION"]),
Pleroma.OTPVersion.version()
)
release
end
def copy_files(%{path: target_path} = release) do
File.cp_r!("./rel/files", target_path)
release
end
def copy_nginx_config(%{path: target_path} = release) do
File.cp!(
"./installation/pleroma.nginx",
Path.join([target_path, "installation", "pleroma.nginx"])
)
release
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {Pleroma.Application, []},
extra_applications: [
:logger,
:runtime_tools,
:comeonin,
:quack,
:fast_sanitize,
:ssl
],
included_applications: [:ex_syslogger]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"]
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false
defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies.
defp oauth_deps do
oauth_strategy_packages =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|> to_string()
|> String.split()
|> Enum.map(fn strategy_entry ->
with [_strategy, dependency] <- String.split(strategy_entry, ":") do
dependency
else
[strategy] -> "ueberauth_#{strategy}"
end
end)
for s <- oauth_strategy_packages, do: {String.to_atom(s), ">= 0.0.0"}
end
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix, "~> 1.5.5"},
{:tzdata, "~> 1.0.3"},
{:plug_cowboy, "~> 2.3"},
{:phoenix_pubsub, "~> 2.0"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"},
{:ecto_explain, "~> 0.1.2"},
{:ecto_sql, "~> 3.4.4"},
{:postgrex, ">= 0.15.5"},
{:oban, "~> 2.3.4"},
{:gettext, "~> 0.18"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"},
{:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 2.14"},
{:calendar, "~> 1.0"},
{:cachex, "~> 3.2"},
{:poison, "~> 3.0", override: true},
{:tesla, "~> 1.4.0", override: true},
{:castore, "~> 0.1"},
{:cowlib, "~> 2.9", override: true},
{:gun,
github: "ninenines/gun", ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", override: true},
{:jason, "~> 1.2"},
{:mogrify, "~> 0.7.4"},
{:ex_aws, "~> 2.1.6"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "1.4.3"},
{:bbcode_pleroma, "~> 0.2.0"},
{:crypt,
git: "https://git.pleroma.social/pleroma/elixir-libraries/crypt.git",
ref: "cf2aa3f11632e8b0634810a15b3e612c7526f6a3"},
{:cors_plug, "~> 2.0"},
{:web_push_encryption, "~> 0.3"},
{:swoosh, "~> 1.0"},
{:phoenix_swoosh, "~> 0.3"},
{:gen_smtp, "~> 0.13"},
{:ex_syslogger, "~> 1.4"},
{:floki, "~> 0.27"},
{:timex, "~> 3.6"},
{:ueberauth, "~> 0.4"},
{:linkify, "~> 0.5.0"},
{:http_signatures, "~> 0.1.0"},
{:telemetry, "~> 0.3"},
{:poolboy, "~> 1.5"},
{:prometheus, "~> 4.6"},
{:prometheus_ex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git",
ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5",
override: true},
{:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.3"},
# Note: once `prometheus_phx` is integrated into `prometheus_phoenix`, remove the former:
{:prometheus_phx,
git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git",
branch: "no-logging"},
{:prometheus_ecto, "~> 1.4"},
{:recon, "~> 2.5"},
{:quack, "~> 0.1.1"},
{:joken, "~> 2.0"},
{:benchee, "~> 1.0"},
{:pot, "~> 0.11"},
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"},
{:flake_id, "~> 0.1.0"},
{:concurrent_limiter,
git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git",
ref: "d81be41024569330f296fc472e24198d7499ba78"},
{:remote_ip,
git: "https://git.pleroma.social/pleroma/remote_ip.git",
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
{:captcha,
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
- {:restarter, path: "./restarter"},
{:majic,
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
{:open_api_spex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
## dev & test
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:ex_machina, "~> 2.4", only: :test},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.5", only: :test},
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
{:excoveralls, "0.12.3", only: :test},
{:hackney,
git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git",
ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e",
override: true},
{:mox, "~> 1.0", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
] ++ oauth_deps()
end
# Aliases are shortcuts or tasks specific to the current project.
# For example, to create, migrate and run the seeds file at once:
#
# $ mix ecto.setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
"ecto.migrate": ["pleroma.ecto.migrate"],
"ecto.rollback": ["pleroma.ecto.rollback"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"],
docs: ["pleroma.docs", "docs"],
analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"],
copyright: &add_copyright/1,
"copyright.bump": &bump_copyright/1
]
end
# Builds a version string made of:
# * the application version
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
# * branch name
# * build metadata:
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
# * the mix environment if different than prod
defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i
git_available? = match?({_output, 0}, System.cmd("sh", ["-c", "command -v git"]))
git_pre_release =
if git_available? do
{tag, tag_err} =
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
{describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
{commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
# Pre-release version, denoted from patch version with a hyphen
cond do
tag_err == 0 and describe_err == 0 ->
describe
|> String.trim()
|> String.replace(String.trim(tag), "")
|> String.trim_leading("-")
|> String.trim()
commit_hash_err == 0 ->
"0-g" <> String.trim(commit_hash)
true ->
nil
end
end
# Branch name as pre-release version component, denoted with a dot
branch_name =
with true <- git_available?,
{branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
true <-
!Enum.any?(["master", "HEAD", "release/", "stable"], fn name ->
String.starts_with?(name, branch_name)
end) do
branch_name =
branch_name
|> String.trim()
|> String.replace(identifier_filter, "-")
branch_name
else
_ -> ""
end
build_name =
cond do
name = Application.get_env(:pleroma, :build_name) -> name
name = System.get_env("PLEROMA_BUILD_NAME") -> name
true -> nil
end
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
env_override = System.get_env("PLEROMA_BUILD_ENV")
env_name =
case env_override do
nil -> env_name
env_override when env_override in ["", "prod"] -> nil
env_override -> env_override
end
# Pre-release version, denoted by appending a hyphen
# and a series of dot separated identifiers
pre_release =
[git_pre_release, branch_name]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join(".")
|> (fn
"" -> nil
string -> "-" <> String.replace(string, identifier_filter, "-")
end).()
# Build metadata, denoted with a plus sign
build_metadata =
[build_name, env_name]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join(".")
|> (fn
"" -> nil
string -> "+" <> String.replace(string, identifier_filter, "-")
end).()
[version, pre_release, build_metadata]
|> Enum.filter(fn string -> string && string != "" end)
|> Enum.join()
end
defp add_copyright(_) do
year = NaiveDateTime.utc_now().year
template = ~s[\
# Pleroma: A lightweight social networking server
# Copyright © 2017-#{year} Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
] |> String.replace("\n", "\\n")
find = "find lib test priv -type f \\( -name '*.ex' -or -name '*.exs' \\) -exec "
grep = "grep -L '# Copyright © [0-9\-]* Pleroma' {} \\;"
xargs = "xargs -n1 sed -i'' '1s;^;#{template};'"
:os.cmd(String.to_charlist("#{find}#{grep} | #{xargs}"))
end
defp bump_copyright(_) do
year = NaiveDateTime.utc_now().year
find = "find lib test priv -type f \\( -name '*.ex' -or -name '*.exs' \\)"
xargs =
"xargs sed -i'' 's;# Copyright © [0-9\-]* Pleroma.*$;# Copyright © 2017-#{year} Pleroma Authors <https://pleroma.social/>;'"
:os.cmd(String.to_charlist("#{find} | #{xargs}"))
end
end
diff --git a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs
index 096ab4ce5..739ef7220 100644
--- a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs
+++ b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs
@@ -1,30 +1,30 @@
defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do
use Ecto.Migration
import Ecto.Query, only: [from: 2]
def change do
- Pleroma.Config.Oban.warn()
+ Pleroma.Config.DeprecationWarnings.check_oban_config()
Application.ensure_all_started(:oban)
Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}],
strategy: :one_for_one,
name: Pleroma.Supervisor
)
from(e in "activity_expirations",
select: %{id: e.id, activity_id: e.activity_id, scheduled_at: e.scheduled_at}
)
|> Pleroma.Repo.stream()
|> Stream.each(fn expiration ->
with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
activity_id: FlakeId.to_string(expiration.activity_id),
expires_at: expires_at
})
end
end)
|> Stream.run()
end
end
diff --git a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs
index 725c5ab0b..78d966766 100644
--- a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs
+++ b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs
@@ -1,40 +1,40 @@
defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
use Ecto.Migration
import Ecto.Query, only: [from: 2]
def change do
- Pleroma.Config.Oban.warn()
+ Pleroma.Config.DeprecationWarnings.check_oban_config()
Application.ensure_all_started(:oban)
Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}],
strategy: :one_for_one,
name: Pleroma.Supervisor
)
if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now())
|> Pleroma.Repo.stream()
|> Stream.each(fn token ->
Pleroma.Workers.PurgeExpiredToken.enqueue(%{
token_id: token.id,
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
mod: Pleroma.Web.OAuth.Token
})
end)
|> Stream.run()
end
from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now())
|> Pleroma.Repo.stream()
|> Stream.each(fn token ->
Pleroma.Workers.PurgeExpiredToken.enqueue(%{
token_id: token.id,
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
mod: Pleroma.MFA.Token
})
end)
|> Stream.run()
end
end
diff --git a/restarter/lib/pleroma.ex b/restarter/lib/pleroma.ex
deleted file mode 100644
index 149a569ce..000000000
--- a/restarter/lib/pleroma.ex
+++ /dev/null
@@ -1,94 +0,0 @@
-defmodule Restarter.Pleroma do
- use GenServer
-
- require Logger
-
- @init_state %{need_reboot: false, rebooted: false, after_boot: false}
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, [], name: __MODULE__)
- end
-
- def init(_), do: {:ok, @init_state}
-
- def rebooted? do
- GenServer.call(__MODULE__, :rebooted?)
- end
-
- def rebooted do
- GenServer.cast(__MODULE__, :rebooted)
- end
-
- def need_reboot? do
- GenServer.call(__MODULE__, :need_reboot?)
- end
-
- def need_reboot do
- GenServer.cast(__MODULE__, :need_reboot)
- end
-
- def refresh do
- GenServer.cast(__MODULE__, :refresh)
- end
-
- def restart(env, delay) do
- GenServer.cast(__MODULE__, {:restart, env, delay})
- end
-
- def restart_after_boot(env) do
- GenServer.cast(__MODULE__, {:after_boot, env})
- end
-
- def handle_call(:rebooted?, _from, state) do
- {:reply, state[:rebooted], state}
- end
-
- def handle_call(:need_reboot?, _from, state) do
- {:reply, state[:need_reboot], state}
- end
-
- def handle_cast(:rebooted, state) do
- {:noreply, Map.put(state, :rebooted, true)}
- end
-
- def handle_cast(:need_reboot, %{need_reboot: true} = state), do: {:noreply, state}
-
- def handle_cast(:need_reboot, state) do
- {:noreply, Map.put(state, :need_reboot, true)}
- end
-
- def handle_cast(:refresh, _state) do
- {:noreply, @init_state}
- end
-
- def handle_cast({:restart, :test, _}, state) do
- Logger.debug("pleroma manually restarted")
- {:noreply, Map.put(state, :need_reboot, false)}
- end
-
- def handle_cast({:restart, _, delay}, state) do
- Process.sleep(delay)
- do_restart(:pleroma)
- {:noreply, Map.put(state, :need_reboot, false)}
- end
-
- def handle_cast({:after_boot, _}, %{after_boot: true} = state), do: {:noreply, state}
-
- def handle_cast({:after_boot, :test}, state) do
- Logger.debug("pleroma restarted after boot")
- state = %{state | after_boot: true, rebooted: true}
- {:noreply, state}
- end
-
- def handle_cast({:after_boot, _}, state) do
- do_restart(:pleroma)
- state = %{state | after_boot: true, rebooted: true}
- {:noreply, state}
- end
-
- defp do_restart(app) do
- :ok = Application.ensure_started(app)
- :ok = Application.stop(app)
- :ok = Application.start(app)
- end
-end
diff --git a/restarter/lib/restarter.ex b/restarter/lib/restarter.ex
deleted file mode 100644
index eadd86f89..000000000
--- a/restarter/lib/restarter.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule Restarter do
- use Application
-
- def start(_, _) do
- opts = [strategy: :one_for_one, name: Restarter.Supervisor]
- Supervisor.start_link([Restarter.Pleroma], opts)
- end
-end
diff --git a/restarter/mix.exs b/restarter/mix.exs
deleted file mode 100644
index b0908aece..000000000
--- a/restarter/mix.exs
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Restarter.MixProject do
- use Mix.Project
-
- def project do
- [
- app: :restarter,
- version: "0.1.0",
- elixir: "~> 1.8",
- start_permanent: Mix.env() == :prod,
- deps: deps()
- ]
- end
-
- def application do
- [
- mod: {Restarter, []}
- ]
- end
-
- defp deps, do: []
-end
diff --git a/test/fixtures/config/temp.secret.exs b/test/fixtures/config/temp.secret.exs
index 4b3af39ec..ce93fad25 100644
--- a/test/fixtures/config/temp.secret.exs
+++ b/test/fixtures/config/temp.secret.exs
@@ -1,17 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
use Mix.Config
config :pleroma, :first_setting, key: "value", key2: [Pleroma.Repo]
config :pleroma, :second_setting, key: "value2", key2: ["Activity"]
config :quack, level: :info
config :pleroma, Pleroma.Repo, pool: Ecto.Adapters.SQL.Sandbox
-config :postgrex, :json_library, Poison
+config :pleroma, Pleroma.Web.Endpoint, key: :val
+
+config :pleroma, Pleroma.InstallerWeb.Endpoint, key: :val
+
+config :pleroma, env: :test
config :pleroma, :database, rum_enabled: true
+
+config :pleroma, configurable_from_database: false
+
+config :pleroma, ecto_repos: [Pleroma.Repo]
+
+config :pleroma, Pleroma.Gun, Pleroma.GunMock
+
+config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.Client
+
+config :postgrex, :json_library, Poison
+
+config :tesla, adapter: Tesla.Mock
+
+config :tzdata, http_client: Pleroma.HTTP
+
+config :http_signatures, key: :val
+
+config :web_push_encryption, key: :val
+
+config :floki, key: :val
diff --git a/test/pleroma/application/config_dependent_deps_test.exs b/test/pleroma/application/config_dependent_deps_test.exs
new file mode 100644
index 000000000..620da16d9
--- /dev/null
+++ b/test/pleroma/application/config_dependent_deps_test.exs
@@ -0,0 +1,149 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Application.ConfigDependentDepsTest do
+ use ExUnit.Case
+
+ alias Pleroma.Application.ConfigDependentDeps
+
+ setup do
+ {:ok, _} =
+ DynamicSupervisor.start_link(
+ strategy: :one_for_one,
+ name: Pleroma.Application.DynamicSupervisorTest
+ )
+
+ {:ok, pid} =
+ Pleroma.Application.ConfigDependentDeps.start_link(
+ dynamic_supervisor: Pleroma.Application.DynamicSupervisorTest,
+ name: Pleroma.Application.ConfigDependentDepsTesting,
+ relations: [
+ {{:pleroma, :dummy_module1}, Pleroma.DummyModule1},
+ {{:pleroma, :dummy_module2}, Pleroma.DummyModule2},
+ {:dummy_group1, :dummy_group1},
+ {:ex_aws, :ex_aws},
+ {:not_started_app, :not_started_app}
+ ]
+ )
+
+ [pid: pid]
+ end
+
+ test "start_dependency/2", %{pid: pid} do
+ {:ok, pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
+ assert Process.alive?(pid)
+ end
+
+ describe "need_reboot?/1" do
+ test "apps and paths", %{pid: pid} do
+ changes = [
+ %Pleroma.ConfigDB{group: :dummy_group1},
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
+ ]
+
+ assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
+ {:pleroma, :dummy_module1},
+ :dummy_group1
+ ]
+
+ assert ConfigDependentDeps.need_reboot?(pid)
+ end
+
+ test "app and path are not duplicated", %{pid: pid} do
+ changes = [
+ %Pleroma.ConfigDB{group: :dummy_group1},
+ %Pleroma.ConfigDB{group: :dummy_group1},
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1},
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
+ ]
+
+ assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
+ {:pleroma, :dummy_module1},
+ :dummy_group1
+ ]
+
+ assert ConfigDependentDeps.need_reboot?(pid)
+ end
+ end
+
+ describe "restart_dependencies/1" do
+ test "started dependency", %{pid: pid} do
+ {:ok, dummy_pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
+
+ changes = [
+ %Pleroma.ConfigDB{group: :ex_aws},
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
+ ]
+
+ assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
+ {:pleroma, :dummy_module1},
+ :ex_aws
+ ]
+
+ assert :ok == ConfigDependentDeps.restart_dependencies(pid)
+
+ restarted = Process.whereis(Pleroma.DummyModule1)
+
+ refute dummy_pid == restarted
+ end
+
+ test "not started process and app", %{pid: pid} do
+ changes = [
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1},
+ %Pleroma.ConfigDB{group: :not_started_app}
+ ]
+
+ assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
+ :not_started_app,
+ {:pleroma, :dummy_module1}
+ ]
+
+ assert :ok == ConfigDependentDeps.restart_dependencies(pid)
+
+ started = Process.whereis(Pleroma.DummyModule1)
+
+ assert Process.alive?(started)
+ end
+
+ test "ignored dependency", %{pid: pid} do
+ changes = [
+ %Pleroma.ConfigDB{group: :pleroma, key: :dummy_module2}
+ ]
+
+ assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
+ {:pleroma, :dummy_module2}
+ ]
+
+ assert :ok == ConfigDependentDeps.restart_dependencies(pid)
+
+ refute Process.whereis(Pleroma.DummyModule2)
+ end
+ end
+
+ test "process goes down", %{pid: pid} do
+ {:ok, dummy_pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
+
+ Process.exit(dummy_pid, :kill)
+
+ Process.sleep(10)
+ restarted = Process.whereis(Pleroma.DummyModule1)
+ refute restarted == dummy_pid
+ end
+end
+
+defmodule Pleroma.DummyModule1 do
+ use Agent
+
+ def start_link(_) do
+ Agent.start_link(fn -> nil end, name: __MODULE__)
+ end
+end
+
+defmodule Pleroma.DummyModule2 do
+ use Agent
+
+ def start_link(_) do
+ :ignore
+ end
+end
diff --git a/test/pleroma/application/environment_test.exs b/test/pleroma/application/environment_test.exs
new file mode 100644
index 000000000..d7f9af5cf
--- /dev/null
+++ b/test/pleroma/application/environment_test.exs
@@ -0,0 +1,243 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Application.EnvironmentTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Application.Environment
+
+ setup do: clear_config(:configurable_from_database, true)
+
+ describe "load_from_db_and_update/0" do
+ test "transfer config values from db to env" do
+ refute Application.get_env(:pleroma, :test_key)
+ refute Application.get_env(:idna, :test_key)
+ refute Application.get_env(:quack, :test_key)
+ refute Application.get_env(:postgrex, :test_key)
+ initial = Application.get_env(:logger, :level)
+
+ insert(:config, key: :test_key, value: [live: 2, com: 3])
+ insert(:config, group: :idna, key: :test_key, value: [live: 15, com: 35])
+
+ insert(:config,
+ group: :quack,
+ key: :test_key,
+ value: [key1: :test_value1, key2: :test_value2]
+ )
+
+ insert(:config, group: :logger, key: :level, value: :debug)
+
+ Environment.load_from_db_and_update()
+
+ assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
+ assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
+ assert Application.get_env(:quack, :test_key) == [key1: :test_value1, key2: :test_value2]
+ assert Application.get_env(:logger, :level) == :debug
+
+ on_exit(fn ->
+ Application.delete_env(:pleroma, :test_key)
+ Application.delete_env(:idna, :test_key)
+ Application.delete_env(:quack, :test_key)
+ Application.delete_env(:postgrex, :test_key)
+ Application.put_env(:logger, :level, initial)
+ end)
+ end
+
+ test "transfer config values for 1 group and some keys" do
+ quack_env = Application.get_all_env(:quack)
+
+ insert(:config, group: :quack, key: :level, value: :info)
+ insert(:config, group: :quack, key: :meta, value: [:none])
+
+ Environment.load_from_db_and_update()
+
+ assert Application.get_env(:quack, :level) == :info
+ assert Application.get_env(:quack, :meta) == [:none]
+ default = Pleroma.Config.Holder.default_config(:quack, :webhook_url)
+ assert Application.get_env(:quack, :webhook_url) == default
+
+ on_exit(fn ->
+ Application.put_all_env(quack: quack_env)
+ end)
+ end
+
+ test "transfer config values with full subkey update" do
+ clear_config(:emoji)
+ clear_config(:assets)
+
+ insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
+ insert(:config, key: :assets, value: [mascots: [a: 1, b: 2]])
+
+ Environment.load_from_db_and_update()
+
+ emoji_env = Application.get_env(:pleroma, :emoji)
+ assert emoji_env[:groups] == [a: 1, b: 2]
+ assets_env = Application.get_env(:pleroma, :assets)
+ assert assets_env[:mascots] == [a: 1, b: 2]
+ end
+ end
+
+ describe "update/2 :ex_syslogger" do
+ setup do
+ initial = Application.get_env(:logger, :ex_syslogger)
+
+ config =
+ insert(:config,
+ group: :logger,
+ key: :ex_syslogger,
+ value: [
+ level: :warn,
+ ident: "pleroma",
+ format: "$metadata[$level] $message",
+ metadata: [:request_id, :key]
+ ]
+ )
+
+ on_exit(fn -> Application.put_env(:logger, :ex_syslogger, initial) end)
+ [config: config, initial: initial]
+ end
+
+ test "changing", %{config: config} do
+ assert Environment.update([config]) == :ok
+
+ env = Application.get_env(:logger, :ex_syslogger)
+ assert env[:level] == :warn
+ assert env[:metadata] == [:request_id, :key]
+ end
+
+ test "deletion", %{config: config, initial: initial} do
+ assert Environment.update([config]) == :ok
+
+ {:ok, config} = Pleroma.ConfigDB.delete(config)
+ assert Environment.update([config]) == :ok
+
+ env = Application.get_env(:logger, :ex_syslogger)
+
+ assert env == initial
+ end
+ end
+
+ describe "update/2 :console" do
+ setup do
+ initial = Application.get_env(:logger, :console)
+
+ config =
+ insert(:config,
+ group: :logger,
+ key: :console,
+ value: [
+ level: :info,
+ format: "$time $metadata[$level]",
+ metadata: [:request_id, :key]
+ ]
+ )
+
+ on_exit(fn -> Application.put_env(:logger, :console, initial) end)
+ [config: config, initial: initial]
+ end
+
+ test "change", %{config: config} do
+ assert Environment.update([config]) == :ok
+ env = Application.get_env(:logger, :console)
+ assert env[:level] == :info
+ assert env[:format] == "$time $metadata[$level]"
+ assert env[:metadata] == [:request_id, :key]
+ end
+
+ test "deletion", %{config: config, initial: initial} do
+ assert Environment.update([config]) == :ok
+ {:ok, config} = Pleroma.ConfigDB.delete(config)
+ assert Environment.update([config]) == :ok
+
+ env = Application.get_env(:logger, :console)
+ assert env == initial
+ end
+ end
+
+ describe "update/2 :backends" do
+ setup do
+ initial = Application.get_all_env(:logger)
+
+ config = insert(:config, group: :logger, key: :backends, value: [:console, :ex_syslogger])
+
+ on_exit(fn -> Application.put_all_env(logger: initial) end)
+
+ [config: config, initial: initial]
+ end
+
+ test "change", %{config: config} do
+ assert Environment.update([config]) == :ok
+ env = Application.get_all_env(:logger)
+ assert env[:backends] == [:console, :ex_syslogger]
+ end
+
+ test "deletion", %{config: config, initial: initial} do
+ assert Environment.update([config]) == :ok
+ {:ok, config} = Pleroma.ConfigDB.delete(config)
+ assert Environment.update([config])
+
+ env = Application.get_all_env(:logger)
+ assert env == initial
+ end
+ end
+
+ test "update/2 logger settings" do
+ initial = Application.get_all_env(:logger)
+
+ config1 =
+ insert(:config,
+ group: :logger,
+ key: :console,
+ value: [
+ level: :info,
+ format: "$time $metadata[$level]",
+ metadata: [:request_id, :key]
+ ]
+ )
+
+ config2 =
+ insert(:config,
+ group: :logger,
+ key: :ex_syslogger,
+ value: [
+ level: :warn,
+ ident: "pleroma",
+ format: "$metadata[$level] $message",
+ metadata: [:request_id, :key]
+ ]
+ )
+
+ config3 = insert(:config, group: :logger, key: :backends, value: [:console, :ex_syslogger])
+
+ on_exit(fn -> Application.put_all_env(logger: initial) end)
+
+ assert Environment.update([config1, config2, config3]) == :ok
+
+ env =
+ :logger
+ |> Application.get_all_env()
+ |> Keyword.take([:backends, :console, :ex_syslogger])
+
+ assert env[:console] == config1.value
+ assert env[:ex_syslogger] == config2.value
+ assert env[:backends] == config3.value
+ end
+
+ test "update/2 for change without key :cors_plug" do
+ initial = Application.get_all_env(:cors_plug)
+ config1 = insert(:config, group: :cors_plug, key: :max_age, value: 300)
+ config2 = insert(:config, group: :cors_plug, key: :methods, value: ["GET"])
+
+ assert Environment.update([config1, config2]) == :ok
+
+ env = Application.get_all_env(:cors_plug)
+
+ assert env[:max_age] == 300
+ assert env[:methods] == ["GET"]
+
+ on_exit(fn -> Application.put_all_env(cors_plug: initial) end)
+ end
+end
diff --git a/test/pleroma/application_requirements_test.exs b/test/pleroma/application/requirements_test.exs
similarity index 69%
rename from test/pleroma/application_requirements_test.exs
rename to test/pleroma/application/requirements_test.exs
index 683ac8c96..1e73798ab 100644
--- a/test/pleroma/application_requirements_test.exs
+++ b/test/pleroma/application/requirements_test.exs
@@ -1,168 +1,167 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.ApplicationRequirementsTest do
+defmodule Pleroma.Application.RequirementsTest do
use Pleroma.DataCase
import ExUnit.CaptureLog
import Mock
- alias Pleroma.ApplicationRequirements
- alias Pleroma.Repo
+ alias Pleroma.Application.Requirements
+ alias Pleroma.Emails.Mailer
describe "check_repo_pool_size!/1" do
test "raises if the pool size is unexpected" do
clear_config([Pleroma.Repo, :pool_size], 11)
clear_config([:dangerzone, :override_repo_pool_size], false)
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ assert_raise Requirements.VerifyError,
"Repo.pool_size different than recommended value.",
fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ capture_log(&Requirements.verify!/0)
end
end
test "doesn't raise if the pool size is unexpected but the respective flag is set" do
clear_config([Pleroma.Repo, :pool_size], 11)
clear_config([:dangerzone, :override_repo_pool_size], true)
- assert Pleroma.ApplicationRequirements.verify!() == :ok
+ assert Requirements.verify!() == :ok
end
end
describe "check_welcome_message_config!/1" do
setup do: clear_config([:welcome])
- setup do: clear_config([Pleroma.Emails.Mailer])
+ setup do: clear_config([Mailer])
test "raises if welcome email enabled but mail disabled" do
clear_config([:welcome, :email, :enabled], true)
- clear_config([Pleroma.Emails.Mailer, :enabled], false)
+ clear_config([Mailer, :enabled], false)
- assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ assert_raise Requirements.VerifyError, "The mail disabled.", fn ->
+ capture_log(&Requirements.verify!/0)
end
end
end
describe "check_confirmation_accounts!" do
setup_with_mocks([
- {Pleroma.ApplicationRequirements, [:passthrough],
+ {Requirements, [:passthrough],
[
check_migrations_applied!: fn _ -> :ok end
]}
]) do
:ok
end
setup do: clear_config([:instance, :account_activation_required])
test "raises if account confirmation is required but mailer isn't enable" do
clear_config([:instance, :account_activation_required], true)
- clear_config([Pleroma.Emails.Mailer, :enabled], false)
+ clear_config([Mailer, :enabled], false)
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ assert_raise Requirements.VerifyError,
"Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ capture_log(&Requirements.verify!/0)
end
end
test "doesn't do anything if account confirmation is disabled" do
clear_config([:instance, :account_activation_required], false)
- clear_config([Pleroma.Emails.Mailer, :enabled], false)
- assert Pleroma.ApplicationRequirements.verify!() == :ok
+ clear_config([Mailer, :enabled], false)
+ assert Requirements.verify!() == :ok
end
test "doesn't do anything if account confirmation is required and mailer is enabled" do
clear_config([:instance, :account_activation_required], true)
- clear_config([Pleroma.Emails.Mailer, :enabled], true)
- assert Pleroma.ApplicationRequirements.verify!() == :ok
+ clear_config([Mailer, :enabled], true)
+ assert Requirements.verify!() == :ok
end
end
describe "check_rum!" do
setup_with_mocks([
- {Pleroma.ApplicationRequirements, [:passthrough],
- [check_migrations_applied!: fn _ -> :ok end]}
+ {Requirements, [:passthrough], [check_migrations_applied!: fn _ -> :ok end]}
]) do
:ok
end
setup do: clear_config([:database, :rum_enabled])
test "raises if rum is enabled and detects unapplied rum migrations" do
clear_config([:database, :rum_enabled], true)
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
- assert_raise ApplicationRequirements.VerifyError,
+ assert_raise Requirements.VerifyError,
"Unapplied RUM Migrations detected",
fn ->
- capture_log(&ApplicationRequirements.verify!/0)
+ capture_log(&Requirements.verify!/0)
end
end
end
test "raises if rum is disabled and detects rum migrations" do
clear_config([:database, :rum_enabled], false)
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
- assert_raise ApplicationRequirements.VerifyError,
+ assert_raise Requirements.VerifyError,
"RUM Migrations detected",
fn ->
- capture_log(&ApplicationRequirements.verify!/0)
+ capture_log(&Requirements.verify!/0)
end
end
end
test "doesn't do anything if rum enabled and applied migrations" do
clear_config([:database, :rum_enabled], true)
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
- assert ApplicationRequirements.verify!() == :ok
+ assert Requirements.verify!() == :ok
end
end
test "doesn't do anything if rum disabled" do
clear_config([:database, :rum_enabled], false)
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
- assert ApplicationRequirements.verify!() == :ok
+ assert Requirements.verify!() == :ok
end
end
end
- describe "check_migrations_applied!" do
+ describe "check_migrations_applied" do
setup_with_mocks([
{Ecto.Migrator, [],
[
with_repo: fn repo, fun -> passthrough([repo, fun]) end,
migrations: fn Repo ->
[
{:up, 20_191_128_153_944, "fix_missing_following_count"},
{:up, 20_191_203_043_610, "create_report_notes"},
{:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
]
end
]}
]) do
:ok
end
setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
test "raises if it detects unapplied migrations" do
- assert_raise ApplicationRequirements.VerifyError,
+ assert_raise Requirements.VerifyError,
"Unapplied Migrations detected",
fn ->
- capture_log(&ApplicationRequirements.verify!/0)
+ capture_log(&Requirements.verify!/0)
end
end
test "doesn't do anything if disabled" do
clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
- assert :ok == ApplicationRequirements.verify!()
+ assert :ok == Requirements.verify!()
end
end
end
diff --git a/test/pleroma/config/loader_test.exs b/test/pleroma/config/loader_test.exs
index b34fd70da..9c4b42e8a 100644
--- a/test/pleroma/config/loader_test.exs
+++ b/test/pleroma/config/loader_test.exs
@@ -1,29 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.LoaderTest do
use ExUnit.Case, async: true
alias Pleroma.Config.Loader
test "read/1" do
- config = Loader.read("test/fixtures/config/temp.secret.exs")
+ config = Loader.read!("test/fixtures/config/temp.secret.exs")
assert config[:pleroma][:first_setting][:key] == "value"
assert config[:pleroma][:first_setting][:key2] == [Pleroma.Repo]
assert config[:quack][:level] == :info
end
- test "filter_group/2" do
- assert Loader.filter_group(:pleroma,
- pleroma: [
- {Pleroma.Repo, [a: 1, b: 2]},
- {Pleroma.Upload, [a: 1, b: 2]},
- {Pleroma.Web.Endpoint, []},
- env: :test,
- configurable_from_database: true,
- database: []
- ]
- ) == [{Pleroma.Upload, [a: 1, b: 2]}]
+ test "filter/1" do
+ config = Loader.read!("test/fixtures/config/temp.secret.exs")
+
+ filtered_config = Loader.filter(config)
+
+ refute filtered_config[:postgrex]
+ refute filtered_config[:tesla]
+ refute filtered_config[:phoenix]
+ refute filtered_config[:tz_data]
+ refute filtered_config[:http_signatures]
+ refute filtered_config[:web_push_encryption]
+ refute filtered_config[:floki]
+
+ refute filtered_config[:pleroma][Pleroma.Repo]
+ refute filtered_config[:pleroma][Pleroma.Web.Endpoint]
+ refute filtered_config[:pleroma][Pleroma.InstallerWeb.Endpoint]
+ refute filtered_config[:pleroma][:env]
+ refute filtered_config[:pleroma][:configurable_from_database]
+ refute filtered_config[:pleroma][:database]
+ refute filtered_config[:pleroma][:ecto_repos]
+ refute filtered_config[:pleroma][Pleroma.Gun]
+ refute filtered_config[:pleroma][Pleroma.ReverseProxy.Client]
+
+ assert config[:pleroma][:first_setting][:key] == "value"
+ assert config[:pleroma][:first_setting][:key2] == [Pleroma.Repo]
+ assert config[:quack][:level] == :info
+ assert config[:pleroma][:second_setting][:key] == "value2"
+ assert config[:pleroma][:second_setting][:key2] == ["Activity"]
end
end
diff --git a/test/pleroma/config/transfer_task_test.exs b/test/pleroma/config/transfer_task_test.exs
deleted file mode 100644
index 8ae5d3b81..000000000
--- a/test/pleroma/config/transfer_task_test.exs
+++ /dev/null
@@ -1,120 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Config.TransferTaskTest do
- use Pleroma.DataCase
-
- import ExUnit.CaptureLog
- import Pleroma.Factory
-
- alias Pleroma.Config.TransferTask
-
- setup do: clear_config(:configurable_from_database, true)
-
- test "transfer config values from db to env" do
- refute Application.get_env(:pleroma, :test_key)
- refute Application.get_env(:idna, :test_key)
- refute Application.get_env(:quack, :test_key)
- refute Application.get_env(:postgrex, :test_key)
- initial = Application.get_env(:logger, :level)
-
- insert(:config, key: :test_key, value: [live: 2, com: 3])
- insert(:config, group: :idna, key: :test_key, value: [live: 15, com: 35])
- insert(:config, group: :quack, key: :test_key, value: [:test_value1, :test_value2])
- insert(:config, group: :postgrex, key: :test_key, value: :value)
- insert(:config, group: :logger, key: :level, value: :debug)
-
- TransferTask.start_link([])
-
- assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
- assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
- assert Application.get_env(:quack, :test_key) == [:test_value1, :test_value2]
- assert Application.get_env(:logger, :level) == :debug
- assert Application.get_env(:postgrex, :test_key) == :value
-
- on_exit(fn ->
- Application.delete_env(:pleroma, :test_key)
- Application.delete_env(:idna, :test_key)
- Application.delete_env(:quack, :test_key)
- Application.delete_env(:postgrex, :test_key)
- Application.put_env(:logger, :level, initial)
- end)
- end
-
- test "transfer config values for 1 group and some keys" do
- level = Application.get_env(:quack, :level)
- meta = Application.get_env(:quack, :meta)
-
- insert(:config, group: :quack, key: :level, value: :info)
- insert(:config, group: :quack, key: :meta, value: [:none])
-
- TransferTask.start_link([])
-
- assert Application.get_env(:quack, :level) == :info
- assert Application.get_env(:quack, :meta) == [:none]
- default = Pleroma.Config.Holder.default_config(:quack, :webhook_url)
- assert Application.get_env(:quack, :webhook_url) == default
-
- on_exit(fn ->
- Application.put_env(:quack, :level, level)
- Application.put_env(:quack, :meta, meta)
- end)
- end
-
- test "transfer config values with full subkey update" do
- clear_config(:emoji)
- clear_config(:assets)
-
- insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
- insert(:config, key: :assets, value: [mascots: [a: 1, b: 2]])
-
- TransferTask.start_link([])
-
- emoji_env = Application.get_env(:pleroma, :emoji)
- assert emoji_env[:groups] == [a: 1, b: 2]
- assets_env = Application.get_env(:pleroma, :assets)
- assert assets_env[:mascots] == [a: 1, b: 2]
- end
-
- describe "pleroma restart" do
- setup do
- on_exit(fn -> Restarter.Pleroma.refresh() end)
- end
-
- test "don't restart if no reboot time settings were changed" do
- clear_config(:emoji)
- insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
-
- refute String.contains?(
- capture_log(fn -> TransferTask.start_link([]) end),
- "pleroma restarted"
- )
- end
-
- test "on reboot time key" do
- clear_config(:chat)
- insert(:config, key: :chat, value: [enabled: false])
- assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
- end
-
- test "on reboot time subkey" do
- clear_config(Pleroma.Captcha)
- insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
- assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
- end
-
- test "don't restart pleroma on reboot time key and subkey if there is false flag" do
- clear_config(:chat)
- clear_config(Pleroma.Captcha)
-
- insert(:config, key: :chat, value: [enabled: false])
- insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
-
- refute String.contains?(
- capture_log(fn -> TransferTask.load_and_update_env([], false) end),
- "pleroma restarted"
- )
- end
- end
-end
diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
index 8cd9f939b..efccb28dc 100644
--- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
@@ -1,1008 +1,983 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
- import ExUnit.CaptureLog
import Pleroma.Factory
import Swoosh.TestAssertions
alias Pleroma.Activity
alias Pleroma.MFA
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.CommonAPI
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:admin_token], "password123")
user = insert(:user)
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
assert json_response(conn, 200)
end
test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
%{admin: admin} do
user = insert(:user)
url = "/api/pleroma/admin/users/#{user.nickname}"
good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
bad_token3 = nil
for good_token <- [good_token1, good_token2, good_token3] do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, 200)
end
for good_token <- [good_token1, good_token2, good_token3] do
conn =
build_conn()
|> assign(:user, nil)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, :forbidden)
end
for bad_token <- [bad_token1, bad_token2, bad_token3] do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, bad_token)
|> get(url)
assert json_response(conn, :forbidden)
end
end
describe "PUT /api/pleroma/admin/users/tag" do
setup %{conn: conn} do
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y"]})
user3 = insert(:user, %{tags: ["unchanged"]})
conn =
conn
|> put_req_header("accept", "application/json")
|> put(
"/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
"#{user2.nickname}&tags[]=foo&tags[]=bar"
)
%{conn: conn, user1: user1, user2: user2, user3: user3}
end
test "it appends specified tags to users with specified nicknames", %{
conn: conn,
admin: admin,
user1: user1,
user2: user2
} do
assert empty_json_response(conn)
assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
log_entry = Repo.one(ModerationLog)
users =
[user1.nickname, user2.nickname]
|> Enum.map(&"@#{&1}")
|> Enum.join(", ")
tags = ["foo", "bar"] |> Enum.join(", ")
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} added tags: #{tags} to users: #{users}"
end
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
assert empty_json_response(conn)
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end
end
describe "DELETE /api/pleroma/admin/users/tag" do
setup %{conn: conn} do
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y", "z"]})
user3 = insert(:user, %{tags: ["unchanged"]})
conn =
conn
|> put_req_header("accept", "application/json")
|> delete(
"/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
"#{user2.nickname}&tags[]=x&tags[]=z"
)
%{conn: conn, user1: user1, user2: user2, user3: user3}
end
test "it removes specified tags from users with specified nicknames", %{
conn: conn,
admin: admin,
user1: user1,
user2: user2
} do
assert empty_json_response(conn)
assert User.get_cached_by_id(user1.id).tags == []
assert User.get_cached_by_id(user2.id).tags == ["y"]
log_entry = Repo.one(ModerationLog)
users =
[user1.nickname, user2.nickname]
|> Enum.map(&"@#{&1}")
|> Enum.join(", ")
tags = ["x", "z"] |> Enum.join(", ")
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} removed tags: #{tags} from users: #{users}"
end
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
assert empty_json_response(conn)
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end
end
describe "/api/pleroma/admin/users/:nickname/permission_group" do
test "GET is giving user_info", %{admin: admin, conn: conn} do
conn =
conn
|> put_req_header("accept", "application/json")
|> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
assert json_response(conn, 200) == %{
"is_admin" => true,
"is_moderator" => false
}
end
test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
assert json_response(conn, 200) == %{
"is_admin" => true
}
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} made @#{user.nickname} admin"
end
test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users/permission_group/admin", %{
nicknames: [user_one.nickname, user_two.nickname]
})
assert json_response(conn, 200) == %{"is_admin" => true}
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
end
test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do
user = insert(:user, is_admin: true)
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
assert json_response(conn, 200) == %{"is_admin" => false}
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} revoked admin role from @#{user.nickname}"
end
test "/:right DELETE, can remove from a permission group (multiple)", %{
admin: admin,
conn: conn
} do
user_one = insert(:user, is_admin: true)
user_two = insert(:user, is_admin: true)
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users/permission_group/admin", %{
nicknames: [user_one.nickname, user_two.nickname]
})
assert json_response(conn, 200) == %{"is_admin" => false}
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{
user_two.nickname
}"
end
end
test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
resp = json_response(conn, 200)
assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
end
describe "PUT disable_mfa" do
test "returns 200 and disable 2fa", %{conn: conn} do
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
}
)
response =
conn
|> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
|> json_response(200)
assert response == user.nickname
mfa_settings = refresh_record(user).multi_factor_authentication_settings
refute mfa_settings.enabled
refute mfa_settings.totp.confirmed
end
test "returns 404 if user not found", %{conn: conn} do
response =
conn
|> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
|> json_response(404)
assert response == %{"error" => "Not found"}
end
end
describe "GET /api/pleroma/admin/restart" do
setup do: clear_config(:configurable_from_database, true)
test "pleroma restarts", %{conn: conn} do
- capture_log(fn ->
- assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
- end) =~ "pleroma restarted"
-
- refute Restarter.Pleroma.need_reboot?()
+ assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+ refute Pleroma.Application.ConfigDependentDeps.need_reboot?()
end
end
- test "need_reboot flag", %{conn: conn} do
- assert conn
- |> get("/api/pleroma/admin/need_reboot")
- |> json_response(200) == %{"need_reboot" => false}
-
- Restarter.Pleroma.need_reboot()
-
- assert conn
- |> get("/api/pleroma/admin/need_reboot")
- |> json_response(200) == %{"need_reboot" => true}
-
- on_exit(fn -> Restarter.Pleroma.refresh() end)
- end
-
describe "GET /api/pleroma/admin/users/:nickname/statuses" do
setup do
user = insert(:user)
insert(:note_activity, user: user)
insert(:note_activity, user: user)
insert(:note_activity, user: user)
%{user: user}
end
test "renders user's statuses", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
assert %{"total" => 3, "activities" => activities} = json_response(conn, 200)
assert length(activities) == 3
end
test "renders user's statuses with pagination", %{conn: conn, user: user} do
%{"total" => 3, "activities" => [activity1]} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
|> json_response(200)
%{"total" => 3, "activities" => [activity2]} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
|> json_response(200)
refute activity1 == activity2
end
test "doesn't return private statuses by default", %{conn: conn, user: user} do
{:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
{:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
%{"total" => 4, "activities" => activities} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses")
|> json_response(200)
assert length(activities) == 4
end
test "returns private statuses with godmode on", %{conn: conn, user: user} do
{:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
{:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
%{"total" => 5, "activities" => activities} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
|> json_response(200)
assert length(activities) == 5
end
test "excludes reblogs by default", %{conn: conn, user: user} do
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{status: "."})
{:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
assert %{"total" => 0, "activities" => []} ==
conn
|> get("/api/pleroma/admin/users/#{other_user.nickname}/statuses")
|> json_response(200)
assert %{"total" => 1, "activities" => [_]} =
conn
|> get(
"/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true"
)
|> json_response(200)
end
end
describe "GET /api/pleroma/admin/users/:nickname/chats" do
setup do
user = insert(:user)
recipients = insert_list(3, :user)
Enum.each(recipients, fn recipient ->
CommonAPI.post_chat_message(user, recipient, "yo")
end)
%{user: user}
end
test "renders user's chats", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
assert json_response(conn, 200) |> length() == 3
end
end
describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
setup do
user = insert(:user)
recipient = insert(:user)
CommonAPI.post_chat_message(user, recipient, "yo")
%{conn: conn} = oauth_access(["read:chats"])
%{conn: conn, user: user}
end
test "returns 403", %{conn: conn, user: user} do
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|> json_response(403)
end
end
describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do
setup do
user = insert(:user)
recipient = insert(:user)
CommonAPI.post_chat_message(user, recipient, "yo")
%{conn: build_conn(), user: user}
end
test "returns 403", %{conn: conn, user: user} do
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/chats")
|> json_response(403)
end
end
describe "GET /api/pleroma/admin/moderation_log" do
setup do
moderator = insert(:user, is_moderator: true)
%{moderator: moderator}
end
test "returns the log", %{conn: conn, admin: admin} do
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_follow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
})
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_unfollow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
})
conn = get(conn, "/api/pleroma/admin/moderation_log")
response = json_response(conn, 200)
[first_entry, second_entry] = response["items"]
assert response["total"] == 2
assert first_entry["data"]["action"] == "relay_unfollow"
assert first_entry["message"] ==
"@#{admin.nickname} unfollowed relay: https://example.org/relay"
assert second_entry["data"]["action"] == "relay_follow"
assert second_entry["message"] ==
"@#{admin.nickname} followed relay: https://example.org/relay"
end
test "returns the log with pagination", %{conn: conn, admin: admin} do
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_follow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
})
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_unfollow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
})
conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
response1 = json_response(conn1, 200)
[first_entry] = response1["items"]
assert response1["total"] == 2
assert response1["items"] |> length() == 1
assert first_entry["data"]["action"] == "relay_unfollow"
assert first_entry["message"] ==
"@#{admin.nickname} unfollowed relay: https://example.org/relay"
conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
response2 = json_response(conn2, 200)
[second_entry] = response2["items"]
assert response2["total"] == 2
assert response2["items"] |> length() == 1
assert second_entry["data"]["action"] == "relay_follow"
assert second_entry["message"] ==
"@#{admin.nickname} followed relay: https://example.org/relay"
end
test "filters log by date", %{conn: conn, admin: admin} do
first_date = "2017-08-15T15:47:06Z"
second_date = "2017-08-20T15:47:06Z"
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_follow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.from_iso8601!(first_date)
})
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_unfollow",
target: "https://example.org/relay"
},
inserted_at: NaiveDateTime.from_iso8601!(second_date)
})
conn1 =
get(
conn,
"/api/pleroma/admin/moderation_log?start_date=#{second_date}"
)
response1 = json_response(conn1, 200)
[first_entry] = response1["items"]
assert response1["total"] == 1
assert first_entry["data"]["action"] == "relay_unfollow"
assert first_entry["message"] ==
"@#{admin.nickname} unfollowed relay: https://example.org/relay"
end
test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_follow",
target: "https://example.org/relay"
}
})
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => moderator.id,
"nickname" => moderator.nickname,
"type" => "user"
},
action: "relay_unfollow",
target: "https://example.org/relay"
}
})
conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
response1 = json_response(conn1, 200)
[first_entry] = response1["items"]
assert response1["total"] == 1
assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
end
test "returns log filtered by search", %{conn: conn, moderator: moderator} do
ModerationLog.insert_log(%{
actor: moderator,
action: "relay_follow",
target: "https://example.org/relay"
})
ModerationLog.insert_log(%{
actor: moderator,
action: "relay_unfollow",
target: "https://example.org/relay"
})
conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
response1 = json_response(conn1, 200)
[first_entry] = response1["items"]
assert response1["total"] == 1
assert get_in(first_entry, ["data", "message"]) ==
"@#{moderator.nickname} unfollowed relay: https://example.org/relay"
end
end
test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated",
%{conn: conn} do
clear_config(Pleroma.Config.get([:instance, :limit_to_local_content]), :unauthenticated)
user = insert(:user, %{local: false, nickname: "u@peer1.com"})
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
assert json_response(conn, 200)
end
describe "GET /users/:nickname/credentials" do
test "gets the user credentials", %{conn: conn} do
user = insert(:user)
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
response = assert json_response(conn, 200)
assert response["email"] == user.email
end
test "returns 403 if requested by a non-admin" do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
assert json_response(conn, :forbidden)
end
end
describe "PATCH /users/:nickname/credentials" do
setup do
user = insert(:user)
[user: user]
end
test "changes password and email", %{conn: conn, admin: admin, user: user} do
assert user.password_reset_pending == false
conn =
patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"password" => "new_password",
"email" => "new_email@example.com",
"name" => "new_name"
})
assert json_response(conn, 200) == %{"status" => "success"}
ObanHelpers.perform_all()
updated_user = User.get_by_id(user.id)
assert updated_user.email == "new_email@example.com"
assert updated_user.name == "new_name"
assert updated_user.password_hash != user.password_hash
assert updated_user.password_reset_pending == true
[log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
assert ModerationLog.get_log_entry_message(log_entry1) ==
"@#{admin.nickname} updated users: @#{user.nickname}"
assert ModerationLog.get_log_entry_message(log_entry2) ==
"@#{admin.nickname} forced password reset for users: @#{user.nickname}"
end
test "returns 403 if requested by a non-admin", %{user: user} do
conn =
build_conn()
|> assign(:user, user)
|> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"password" => "new_password",
"email" => "new_email@example.com",
"name" => "new_name"
})
assert json_response(conn, :forbidden)
end
test "changes actor type from permitted list", %{conn: conn, user: user} do
assert user.actor_type == "Person"
assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"actor_type" => "Service"
})
|> json_response(200) == %{"status" => "success"}
updated_user = User.get_by_id(user.id)
assert updated_user.actor_type == "Service"
assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"actor_type" => "Application"
})
|> json_response(400) == %{"errors" => %{"actor_type" => "is invalid"}}
end
test "update non existing user", %{conn: conn} do
assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{
"password" => "new_password"
})
|> json_response(404) == %{"error" => "Not found"}
end
end
describe "PATCH /users/:nickname/force_password_reset" do
test "sets password_reset_pending to true", %{conn: conn} do
user = insert(:user)
assert user.password_reset_pending == false
conn =
patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
assert empty_json_response(conn) == ""
ObanHelpers.perform_all()
assert User.get_by_id(user.id).password_reset_pending == true
end
end
describe "instances" do
test "GET /instances/:instance/statuses", %{conn: conn} do
user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
insert_pair(:note_activity, user: user)
activity = insert(:note_activity, user: user2)
%{"total" => 2, "activities" => activities} =
conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200)
assert length(activities) == 2
%{"total" => 1, "activities" => [_]} =
conn |> get("/api/pleroma/admin/instances/test.com/statuses") |> json_response(200)
%{"total" => 0, "activities" => []} =
conn |> get("/api/pleroma/admin/instances/nonexistent.com/statuses") |> json_response(200)
CommonAPI.repeat(activity.id, user)
%{"total" => 2, "activities" => activities} =
conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200)
assert length(activities) == 2
%{"total" => 3, "activities" => activities} =
conn
|> get("/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
|> json_response(200)
assert length(activities) == 3
end
end
describe "PATCH /confirm_email" do
test "it confirms emails of two users", %{conn: conn, admin: admin} do
[first_user, second_user] = insert_pair(:user, is_confirmed: false)
refute first_user.is_confirmed
refute second_user.is_confirmed
ret_conn =
patch(conn, "/api/pleroma/admin/users/confirm_email", %{
nicknames: [
first_user.nickname,
second_user.nickname
]
})
assert ret_conn.status == 200
first_user = User.get_by_id(first_user.id)
second_user = User.get_by_id(second_user.id)
assert first_user.is_confirmed
assert second_user.is_confirmed
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
second_user.nickname
}"
end
end
describe "PATCH /resend_confirmation_email" do
test "it resend emails for two users", %{conn: conn, admin: admin} do
[first_user, second_user] = insert_pair(:user, is_confirmed: false)
ret_conn =
patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{
nicknames: [
first_user.nickname,
second_user.nickname
]
})
assert ret_conn.status == 200
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
second_user.nickname
}"
ObanHelpers.perform_all()
Pleroma.Emails.UserEmail.account_confirmation_email(first_user)
# temporary hackney fix until hackney max_connections bug is fixed
# https://git.pleroma.social/pleroma/pleroma/-/issues/2101
|> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|> assert_email_sent()
end
end
describe "/api/pleroma/admin/stats" do
test "status visibility count", %{conn: conn} do
user = insert(:user)
CommonAPI.post(user, %{visibility: "public", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
response =
conn
|> get("/api/pleroma/admin/stats")
|> json_response(200)
assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
response["status_visibility"]
end
test "by instance", %{conn: conn} do
user1 = insert(:user)
instance2 = "instance2.tld"
user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
CommonAPI.post(user1, %{visibility: "public", status: "hey"})
CommonAPI.post(user2, %{visibility: "unlisted", status: "hey"})
CommonAPI.post(user2, %{visibility: "private", status: "hey"})
response =
conn
|> get("/api/pleroma/admin/stats", instance: instance2)
|> json_response(200)
assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} =
response["status_visibility"]
end
end
describe "/api/pleroma/backups" do
test "it creates a backup", %{conn: conn} do
admin = %{id: admin_id, nickname: admin_nickname} = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
user = %{id: user_id, nickname: user_nickname} = insert(:user)
assert "" ==
conn
|> assign(:user, admin)
|> assign(:token, token)
|> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
|> json_response(200)
assert [backup] = Repo.all(Pleroma.User.Backup)
ObanHelpers.perform_all()
email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup, admin.id)
assert String.contains?(email.html_body, "Admin @#{admin.nickname} requested a full backup")
assert_email_sent(to: {user.name, user.email}, html_body: email.html_body)
log_message = "@#{admin_nickname} requested account backup for @#{user_nickname}"
assert [
%{
data: %{
"action" => "create_backup",
"actor" => %{
"id" => ^admin_id,
"nickname" => ^admin_nickname
},
"message" => ^log_message,
"subject" => %{
"id" => ^user_id,
"nickname" => ^user_nickname
}
}
}
] = Pleroma.ModerationLog |> Repo.all()
end
test "it doesn't limit admins", %{conn: conn} do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
user = insert(:user)
assert "" ==
conn
|> assign(:user, admin)
|> assign(:token, token)
|> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
|> json_response(200)
assert [_backup] = Repo.all(Pleroma.User.Backup)
assert "" ==
conn
|> assign(:user, admin)
|> assign(:token, token)
|> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
|> json_response(200)
assert Repo.aggregate(Pleroma.User.Backup, :count) == 2
end
end
end
-
-# Needed for testing
-defmodule Pleroma.Web.Endpoint.NotReal do
-end
-
-defmodule Pleroma.Captcha.NotReal do
-end
diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
index 578a4c914..86099eb1a 100644
--- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
@@ -1,1456 +1,1453 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
use Pleroma.Web.ConnCase
import ExUnit.CaptureLog
import Pleroma.Factory
alias Pleroma.ConfigDB
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
describe "GET /api/pleroma/admin/config" do
setup do: clear_config(:configurable_from_database, true)
test "when configuration from database is off", %{conn: conn} do
clear_config(:configurable_from_database, false)
conn = get(conn, "/api/pleroma/admin/config")
assert json_response_and_validate_schema(conn, 400) ==
%{
"error" => "You must enable configurable_from_database in your config file."
}
end
test "with settings only in db", %{conn: conn} do
config1 = insert(:config)
config2 = insert(:config)
conn = get(conn, "/api/pleroma/admin/config?only_db=true")
%{
"configs" => [
%{
"group" => ":pleroma",
"key" => key1,
"value" => _
},
%{
"group" => ":pleroma",
"key" => key2,
"value" => _
}
]
} = json_response_and_validate_schema(conn, 200)
assert key1 == inspect(config1.key)
assert key2 == inspect(config2.key)
end
test "db is added to settings that are in db", %{conn: conn} do
_config = insert(:config, key: ":instance", value: [name: "Some name"])
%{"configs" => configs} =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
[instance_config] =
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
group == ":pleroma" and key == ":instance"
end)
assert instance_config["db"] == [":name"]
end
test "merged default setting with db settings", %{conn: conn} do
config1 = insert(:config)
config2 = insert(:config)
config3 =
insert(:config,
value: [k1: :v1, k2: :v2]
)
%{"configs" => configs} =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
assert length(configs) > 3
saved_configs = [config1, config2, config3]
keys = Enum.map(saved_configs, &inspect(&1.key))
received_configs =
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
group == ":pleroma" and key in keys
end)
assert length(received_configs) == 3
db_keys =
config3.value
|> Keyword.keys()
|> ConfigDB.to_json_types()
keys = Enum.map(saved_configs -- [config3], &inspect(&1.key))
values = Enum.map(saved_configs, &ConfigDB.to_json_types(&1.value))
mapset_keys = MapSet.new(keys ++ db_keys)
Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
db = MapSet.new(db)
assert MapSet.subset?(db, mapset_keys)
assert value in values
end)
end
test "subkeys with full update right merge", %{conn: conn} do
insert(:config,
key: ":emoji",
value: [groups: [a: 1, b: 2], key: [a: 1]]
)
insert(:config,
key: ":assets",
value: [mascots: [a: 1, b: 2], key: [a: 1]]
)
%{"configs" => configs} =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
vals =
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
group == ":pleroma" and key in [":emoji", ":assets"]
end)
emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
emoji_val = ConfigDB.to_elixir_types(emoji["value"])
assets_val = ConfigDB.to_elixir_types(assets["value"])
assert emoji_val[:groups] == [a: 1, b: 2]
assert assets_val[:mascots] == [a: 1, b: 2]
end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:admin_token], "password123")
build_conn()
|> get("/api/pleroma/admin/config?admin_token=password123")
|> json_response_and_validate_schema(200)
end
end
test "POST /api/pleroma/admin/config with configdb disabled", %{conn: conn} do
clear_config(:configurable_from_database, false)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{"configs" => []})
assert json_response_and_validate_schema(conn, 400) ==
%{"error" => "You must enable configurable_from_database in your config file."}
end
describe "POST /api/pleroma/admin/config" do
setup do
http = Application.get_env(:pleroma, :http)
on_exit(fn ->
Application.delete_env(:pleroma, :key1)
Application.delete_env(:pleroma, :key2)
Application.delete_env(:pleroma, :key3)
Application.delete_env(:pleroma, :key4)
Application.delete_env(:pleroma, :keyaa1)
Application.delete_env(:pleroma, :keyaa2)
Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
Application.put_env(:pleroma, :http, http)
Application.put_env(:tesla, :adapter, Tesla.Mock)
- Restarter.Pleroma.refresh()
end)
end
setup do: clear_config(:configurable_from_database, true)
@tag capture_log: true
test "create new config setting in db", %{conn: conn} do
ueberauth = Application.get_env(:ueberauth, Ueberauth)
on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{group: ":pleroma", key: ":key1", value: "value1"},
%{
group: ":ueberauth",
key: "Ueberauth",
value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
},
%{
group: ":pleroma",
key: ":key2",
value: %{
":nested_1" => "nested_value1",
":nested_2" => [
%{":nested_22" => "nested_value222"},
%{":nested_33" => %{":nested_44" => "nested_444"}}
]
}
},
%{
group: ":pleroma",
key: ":key3",
value: [
%{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
%{"nested_4" => true}
]
},
%{
group: ":pleroma",
key: ":key4",
value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
},
%{
group: ":idna",
key: ":key5",
value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => "value1",
"db" => [":key1"]
},
%{
"group" => ":ueberauth",
"key" => "Ueberauth",
"value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
"db" => [":consumer_secret"]
},
%{
"group" => ":pleroma",
"key" => ":key2",
"value" => %{
":nested_1" => "nested_value1",
":nested_2" => [
%{":nested_22" => "nested_value222"},
%{":nested_33" => %{":nested_44" => "nested_444"}}
]
},
"db" => [":key2"]
},
%{
"group" => ":pleroma",
"key" => ":key3",
"value" => [
%{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
%{"nested_4" => true}
],
"db" => [":key3"]
},
%{
"group" => ":pleroma",
"key" => ":key4",
"value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
"db" => [":key4"]
},
%{
"group" => ":idna",
"key" => ":key5",
"value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
"db" => [":key5"]
}
],
"need_reboot" => false
}
assert Application.get_env(:pleroma, :key1) == "value1"
assert Application.get_env(:pleroma, :key2) == %{
nested_1: "nested_value1",
nested_2: [
%{nested_22: "nested_value222"},
%{nested_33: %{nested_44: "nested_444"}}
]
}
assert Application.get_env(:pleroma, :key3) == [
%{"nested_3" => :nested_3, "nested_33" => "nested_33"},
%{"nested_4" => true}
]
assert Application.get_env(:pleroma, :key4) == %{
"endpoint" => "https://example.com",
nested_5: :upload
}
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
end
test "save configs setting without explicit key", %{conn: conn} do
level = Application.get_env(:quack, :level)
meta = Application.get_env(:quack, :meta)
webhook_url = Application.get_env(:quack, :webhook_url)
on_exit(fn ->
Application.put_env(:quack, :level, level)
Application.put_env(:quack, :meta, meta)
Application.put_env(:quack, :webhook_url, webhook_url)
end)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":quack",
key: ":level",
value: ":info"
},
%{
group: ":quack",
key: ":meta",
value: [":none"]
},
%{
group: ":quack",
key: ":webhook_url",
value: "https://hooks.slack.com/services/KEY"
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":quack",
"key" => ":level",
"value" => ":info",
"db" => [":level"]
},
%{
"group" => ":quack",
"key" => ":meta",
"value" => [":none"],
"db" => [":meta"]
},
%{
"group" => ":quack",
"key" => ":webhook_url",
"value" => "https://hooks.slack.com/services/KEY",
"db" => [":webhook_url"]
}
],
"need_reboot" => false
}
assert Application.get_env(:quack, :level) == :info
assert Application.get_env(:quack, :meta) == [:none]
assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
end
test "saving config with partial update", %{conn: conn} do
insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{"tuple" => [":key1", 1]},
%{"tuple" => [":key2", 2]},
%{"tuple" => [":key3", 3]}
],
"db" => [":key1", ":key2", ":key3"]
}
],
"need_reboot" => false
}
end
test "saving config which need pleroma reboot", %{conn: conn} do
clear_config([:chat, :enabled], true)
assert conn
|> put_req_header("content-type", "application/json")
|> post(
"/api/pleroma/admin/config",
%{
configs: [
%{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
]
}
)
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"db" => [":enabled"],
"group" => ":pleroma",
"key" => ":chat",
"value" => [%{"tuple" => [":enabled", true]}]
}
],
"need_reboot" => true
}
configs =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
assert configs["need_reboot"]
capture_log(fn ->
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
%{}
end) =~ "pleroma restarted"
configs =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
assert configs["need_reboot"] == false
end
test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
clear_config([:chat, :enabled], true)
assert conn
|> put_req_header("content-type", "application/json")
|> post(
"/api/pleroma/admin/config",
%{
configs: [
%{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
]
}
)
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"db" => [":enabled"],
"group" => ":pleroma",
"key" => ":chat",
"value" => [%{"tuple" => [":enabled", true]}]
}
],
"need_reboot" => true
}
assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
]
})
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{"tuple" => [":key3", 3]}
],
"db" => [":key3"]
}
],
"need_reboot" => true
}
capture_log(fn ->
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
%{}
end) =~ "pleroma restarted"
configs =
conn
|> get("/api/pleroma/admin/config")
|> json_response_and_validate_schema(200)
assert configs["need_reboot"] == false
end
test "saving config with nested merge", %{conn: conn} do
insert(:config, key: :key1, value: [key1: 1, key2: [k1: 1, k2: 2]])
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: ":key1",
value: [
%{"tuple" => [":key3", 3]},
%{
"tuple" => [
":key2",
[
%{"tuple" => [":k2", 1]},
%{"tuple" => [":k3", 3]}
]
]
}
]
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{"tuple" => [":key1", 1]},
%{"tuple" => [":key3", 3]},
%{
"tuple" => [
":key2",
[
%{"tuple" => [":k1", 1]},
%{"tuple" => [":k2", 1]},
%{"tuple" => [":k3", 3]}
]
]
}
],
"db" => [":key1", ":key3", ":key2"]
}
],
"need_reboot" => false
}
end
test "saving special atoms", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{
"tuple" => [
":ssl_options",
[%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
]
}
]
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{
"tuple" => [
":ssl_options",
[%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
]
}
],
"db" => [":ssl_options"]
}
],
"need_reboot" => false
}
assert Application.get_env(:pleroma, :key1) == [
ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
]
end
test "saving full setting if value is in full_key_update list", %{conn: conn} do
backends = Application.get_env(:logger, :backends)
on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
insert(:config,
group: :logger,
key: :backends,
value: []
)
- Pleroma.Config.TransferTask.load_and_update_env([], false)
+ Pleroma.Application.Environment.load_from_db_and_update()
assert Application.get_env(:logger, :backends) == []
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":logger",
key: ":backends",
value: [":console"]
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":logger",
"key" => ":backends",
"value" => [
":console"
],
"db" => [":backends"]
}
],
"need_reboot" => false
}
assert Application.get_env(:logger, :backends) == [
:console
]
end
test "saving full setting if value is not keyword", %{conn: conn} do
insert(:config,
group: :tesla,
key: :adapter,
value: Tesla.Adapter.Hackey
)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{group: ":tesla", key: ":adapter", value: "Tesla.Adapter.Httpc"}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":tesla",
"key" => ":adapter",
"value" => "Tesla.Adapter.Httpc",
"db" => [":adapter"]
}
],
"need_reboot" => false
}
end
test "update config setting & delete with fallback to default value", %{
- conn: conn,
- admin: admin,
- token: token
+ conn: conn
} do
ueberauth = Application.get_env(:ueberauth, Ueberauth)
- insert(:config, key: :keyaa1)
- insert(:config, key: :keyaa2)
- config3 =
- insert(:config,
- group: :ueberauth,
- key: Ueberauth
- )
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/pleroma/admin/config", %{
- configs: [
- %{group: ":pleroma", key: ":keyaa1", value: "another_value"},
- %{group: ":pleroma", key: ":keyaa2", value: "another_value"}
- ]
- })
-
- assert json_response_and_validate_schema(conn, 200) == %{
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/config", %{
+ configs: [
+ %{group: ":pleroma", key: ":keyaa1", value: "another_value"},
+ %{group: ":pleroma", key: ":keyaa2", value: "another_value"},
+ %{group: ":ueberauth", key: "Ueberauth", value: "another_value"}
+ ]
+ })
+ |> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":keyaa1",
"value" => "another_value",
"db" => [":keyaa1"]
},
%{
"group" => ":pleroma",
"key" => ":keyaa2",
"value" => "another_value",
"db" => [":keyaa2"]
+ },
+ %{
+ "db" => ["Ueberauth"],
+ "group" => ":ueberauth",
+ "key" => "Ueberauth",
+ "value" => "another_value"
}
],
"need_reboot" => false
}
assert Application.get_env(:pleroma, :keyaa1) == "another_value"
assert Application.get_env(:pleroma, :keyaa2) == "another_value"
- assert Application.get_env(:ueberauth, Ueberauth) == config3.value
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> put_req_header("content-type", "application/json")
- |> post("/api/pleroma/admin/config", %{
- configs: [
- %{group: ":pleroma", key: ":keyaa2", delete: true},
- %{
- group: ":ueberauth",
- key: "Ueberauth",
- delete: true
- }
- ]
- })
+ assert Application.get_env(:ueberauth, Ueberauth) == "another_value"
- assert json_response_and_validate_schema(conn, 200) == %{
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/config", %{
+ configs: [
+ %{group: ":pleroma", key: ":keyaa2", delete: true},
+ %{
+ group: ":ueberauth",
+ key: "Ueberauth",
+ delete: true
+ }
+ ]
+ })
+ |> json_response_and_validate_schema(200) == %{
"configs" => [],
"need_reboot" => false
}
assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
end
test "common config example", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
"group" => ":pleroma",
"key" => "Pleroma.Captcha.NotReal",
"value" => [
%{"tuple" => [":enabled", false]},
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]},
%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
%{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
%{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
%{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
%{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
%{"tuple" => [":name", "Pleroma"]}
]
}
]
})
assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => "Pleroma.Captcha.NotReal",
"value" => [
%{"tuple" => [":enabled", false]},
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]},
%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
%{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
%{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
%{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
%{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
%{"tuple" => [":name", "Pleroma"]}
],
"db" => [
":enabled",
":method",
":seconds_valid",
":path",
":key1",
":partial_chain",
":regex1",
":regex2",
":regex3",
":regex4",
":name"
]
}
],
"need_reboot" => false
}
end
test "tuples with more than two values", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
"group" => ":pleroma",
"key" => "Pleroma.Web.Endpoint.NotReal",
"value" => [
%{
"tuple" => [
":http",
[
%{
"tuple" => [
":key2",
[
%{
"tuple" => [
":_",
[
%{
"tuple" => [
"/api/v1/streaming",
"Pleroma.Web.MastodonAPI.WebsocketHandler",
[]
]
},
%{
"tuple" => [
"/websocket",
"Phoenix.Endpoint.CowboyWebSocket",
%{
"tuple" => [
"Phoenix.Transports.WebSocket",
%{
"tuple" => [
"Pleroma.Web.Endpoint",
"Pleroma.Web.UserSocket",
[]
]
}
]
}
]
},
%{
"tuple" => [
":_",
"Phoenix.Endpoint.Cowboy2Handler",
%{"tuple" => ["Pleroma.Web.Endpoint", []]}
]
}
]
]
}
]
]
}
]
]
}
]
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => "Pleroma.Web.Endpoint.NotReal",
"value" => [
%{
"tuple" => [
":http",
[
%{
"tuple" => [
":key2",
[
%{
"tuple" => [
":_",
[
%{
"tuple" => [
"/api/v1/streaming",
"Pleroma.Web.MastodonAPI.WebsocketHandler",
[]
]
},
%{
"tuple" => [
"/websocket",
"Phoenix.Endpoint.CowboyWebSocket",
%{
"tuple" => [
"Phoenix.Transports.WebSocket",
%{
"tuple" => [
"Pleroma.Web.Endpoint",
"Pleroma.Web.UserSocket",
[]
]
}
]
}
]
},
%{
"tuple" => [
":_",
"Phoenix.Endpoint.Cowboy2Handler",
%{"tuple" => ["Pleroma.Web.Endpoint", []]}
]
}
]
]
}
]
]
}
]
]
}
],
"db" => [":http"]
}
],
"need_reboot" => false
}
end
test "settings with nesting map", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{"tuple" => [":key2", "some_val"]},
%{
"tuple" => [
":key3",
%{
":max_options" => 20,
":max_option_chars" => 200,
":min_expiration" => 0,
":max_expiration" => 31_536_000,
"nested" => %{
":max_options" => 20,
":max_option_chars" => 200,
":min_expiration" => 0,
":max_expiration" => 31_536_000
}
}
]
}
]
}
]
})
assert json_response_and_validate_schema(conn, 200) ==
%{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => [
%{"tuple" => [":key2", "some_val"]},
%{
"tuple" => [
":key3",
%{
":max_expiration" => 31_536_000,
":max_option_chars" => 200,
":max_options" => 20,
":min_expiration" => 0,
"nested" => %{
":max_expiration" => 31_536_000,
":max_option_chars" => 200,
":max_options" => 20,
":min_expiration" => 0
}
}
]
}
],
"db" => [":key2", ":key3"]
}
],
"need_reboot" => false
}
end
test "value as map", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => %{"key" => "some_val"}
}
]
})
assert json_response_and_validate_schema(conn, 200) ==
%{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":key1",
"value" => %{"key" => "some_val"},
"db" => [":key1"]
}
],
"need_reboot" => false
}
end
test "queues key as atom", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
"group" => ":oban",
"key" => ":queues",
"value" => [
%{"tuple" => [":federator_incoming", 50]},
%{"tuple" => [":federator_outgoing", 50]},
%{"tuple" => [":web_push", 50]},
%{"tuple" => [":mailer", 10]},
%{"tuple" => [":transmogrifier", 20]},
%{"tuple" => [":scheduled_activities", 10]},
%{"tuple" => [":background", 5]}
]
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":oban",
"key" => ":queues",
"value" => [
%{"tuple" => [":federator_incoming", 50]},
%{"tuple" => [":federator_outgoing", 50]},
%{"tuple" => [":web_push", 50]},
%{"tuple" => [":mailer", 10]},
%{"tuple" => [":transmogrifier", 20]},
%{"tuple" => [":scheduled_activities", 10]},
%{"tuple" => [":background", 5]}
],
"db" => [
":federator_incoming",
":federator_outgoing",
":web_push",
":mailer",
":transmogrifier",
":scheduled_activities",
":background"
]
}
],
"need_reboot" => false
}
end
test "delete part of settings by atom subkeys", %{conn: conn} do
insert(:config,
key: :keyaa1,
value: [subkey1: "val1", subkey2: "val2", subkey3: "val3"]
)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: ":keyaa1",
subkeys: [":subkey1", ":subkey3"],
delete: true
}
]
})
assert json_response_and_validate_schema(conn, 200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":keyaa1",
"value" => [%{"tuple" => [":subkey2", "val2"]}],
"db" => [":subkey2"]
}
],
"need_reboot" => false
}
end
test "proxy tuple localhost", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: ":http",
value: [
%{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
]
}
]
})
assert %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":http",
"value" => value,
"db" => db
}
]
} = json_response_and_validate_schema(conn, 200)
assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
assert ":proxy_url" in db
end
test "proxy tuple domain", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: ":http",
value: [
%{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
]
}
]
})
assert %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":http",
"value" => value,
"db" => db
}
]
} = json_response_and_validate_schema(conn, 200)
assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
assert ":proxy_url" in db
end
test "proxy tuple ip", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: ":http",
value: [
%{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
]
}
]
})
assert %{
"configs" => [
%{
"group" => ":pleroma",
"key" => ":http",
"value" => value,
"db" => db
}
]
} = json_response_and_validate_schema(conn, 200)
assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
assert ":proxy_url" in db
end
@tag capture_log: true
test "doesn't set keys not in the whitelist", %{conn: conn} do
clear_config(:database_config_whitelist, [
{:pleroma, :key1},
{:pleroma, :key2},
{:pleroma, Pleroma.Captcha.NotReal},
{:not_real}
])
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{group: ":pleroma", key: ":key1", value: "value1"},
%{group: ":pleroma", key: ":key2", value: "value2"},
%{group: ":pleroma", key: ":key3", value: "value3"},
%{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
%{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
%{group: ":not_real", key: ":anything", value: "value6"}
]
})
assert Application.get_env(:pleroma, :key1) == "value1"
assert Application.get_env(:pleroma, :key2) == "value2"
assert Application.get_env(:pleroma, :key3) == nil
assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
assert Application.get_env(:not_real, :anything) == "value6"
end
test "args for Pleroma.Upload.Filter.Mogrify with custom tuples", %{conn: conn} do
clear_config(Pleroma.Upload.Filter.Mogrify)
assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: "Pleroma.Upload.Filter.Mogrify",
value: [
%{"tuple" => [":args", ["auto-orient", "strip"]]}
]
}
]
})
|> json_response_and_validate_schema(200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => "Pleroma.Upload.Filter.Mogrify",
"value" => [
%{"tuple" => [":args", ["auto-orient", "strip"]]}
],
"db" => [":args"]
}
],
"need_reboot" => false
}
assert Config.get(Pleroma.Upload.Filter.Mogrify) == [args: ["auto-orient", "strip"]]
assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
%{
group: ":pleroma",
key: "Pleroma.Upload.Filter.Mogrify",
value: [
%{
"tuple" => [
":args",
[
"auto-orient",
"strip",
"{\"implode\", \"1\"}",
"{\"resize\", \"3840x1080>\"}"
]
]
}
]
}
]
})
|> json_response(200) == %{
"configs" => [
%{
"group" => ":pleroma",
"key" => "Pleroma.Upload.Filter.Mogrify",
"value" => [
%{
"tuple" => [
":args",
[
"auto-orient",
"strip",
"{\"implode\", \"1\"}",
"{\"resize\", \"3840x1080>\"}"
]
]
}
],
"db" => [":args"]
}
],
"need_reboot" => false
}
assert Config.get(Pleroma.Upload.Filter.Mogrify) == [
args: ["auto-orient", "strip", {"implode", "1"}, {"resize", "3840x1080>"}]
]
end
test "enables the welcome messages", %{conn: conn} do
clear_config([:welcome])
params = %{
"group" => ":pleroma",
"key" => ":welcome",
"value" => [
%{
"tuple" => [
":direct_message",
[
%{"tuple" => [":enabled", true]},
%{"tuple" => [":message", "Welcome to Pleroma!"]},
%{"tuple" => [":sender_nickname", "pleroma"]}
]
]
},
%{
"tuple" => [
":chat_message",
[
%{"tuple" => [":enabled", true]},
%{"tuple" => [":message", "Welcome to Pleroma!"]},
%{"tuple" => [":sender_nickname", "pleroma"]}
]
]
},
%{
"tuple" => [
":email",
[
%{"tuple" => [":enabled", true]},
%{"tuple" => [":sender", %{"tuple" => ["pleroma@dev.dev", "Pleroma"]}]},
%{"tuple" => [":subject", "Welcome to <%= instance_name %>!"]},
%{"tuple" => [":html", "Welcome to <%= instance_name %>!"]},
%{"tuple" => [":text", "Welcome to <%= instance_name %>!"]}
]
]
}
]
}
refute Pleroma.User.WelcomeEmail.enabled?()
refute Pleroma.User.WelcomeMessage.enabled?()
refute Pleroma.User.WelcomeChatMessage.enabled?()
res =
assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{"configs" => [params]})
|> json_response_and_validate_schema(200)
assert Pleroma.User.WelcomeEmail.enabled?()
assert Pleroma.User.WelcomeMessage.enabled?()
assert Pleroma.User.WelcomeChatMessage.enabled?()
assert res == %{
"configs" => [
%{
"db" => [":direct_message", ":chat_message", ":email"],
"group" => ":pleroma",
"key" => ":welcome",
"value" => params["value"]
}
],
"need_reboot" => false
}
end
end
describe "GET /api/pleroma/admin/config/descriptions" do
test "structure", %{conn: conn} do
conn = get(conn, "/api/pleroma/admin/config/descriptions")
assert [child | _others] = json_response_and_validate_schema(conn, 200)
assert child["children"]
assert child["key"]
assert String.starts_with?(child["group"], ":")
assert child["description"]
end
test "filters by database configuration whitelist", %{conn: conn} do
clear_config(:database_config_whitelist, [
{:pleroma, :instance},
{:pleroma, :activitypub},
{:pleroma, Pleroma.Upload},
{:esshd}
])
conn = get(conn, "/api/pleroma/admin/config/descriptions")
children = json_response_and_validate_schema(conn, 200)
assert length(children) == 4
assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
assert instance["children"]
activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
assert activitypub["children"]
web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
assert web_endpoint["children"]
esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
assert esshd["children"]
end
end
end
+
+# Needed for testing
+defmodule Pleroma.Web.Endpoint.NotReal do
+end
+
+defmodule Pleroma.Captcha.NotReal do
+end
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index 0ee2aa4a2..d4998a79c 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -1,144 +1,136 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.DataCase do
@moduledoc """
This module defines the setup for tests requiring
access to the application's data layer.
You may define functions here to be used as helpers in
your tests.
Finally, if the test case interacts with the database,
it cannot be async. For this reason, every test runs
inside a transaction which is reset at the beginning
of the test unless the test case is marked as async.
"""
use ExUnit.CaseTemplate
import Pleroma.Tests.Helpers, only: [clear_config: 2]
using do
quote do
alias Pleroma.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Pleroma.DataCase
use Pleroma.Tests.Helpers
# Sets up OAuth access with specified scopes
defp oauth_access(scopes, opts \\ []) do
user =
Keyword.get_lazy(opts, :user, fn ->
Pleroma.Factory.insert(:user)
end)
token =
Keyword.get_lazy(opts, :oauth_token, fn ->
Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes)
end)
%{user: user, token: token}
end
end
end
def clear_cachex do
- Pleroma.Supervisor
- |> Supervisor.which_children()
- |> Enum.each(fn
- {name, _, _, [Cachex]} ->
- name
- |> to_string
- |> String.trim_leading("cachex_")
- |> Kernel.<>("_cache")
- |> String.to_existing_atom()
- |> Cachex.clear()
-
- _ ->
- nil
+ Pleroma.Application.StartUpDependencies.cachex_deps()
+ |> Enum.each(fn {name, _} ->
+ "#{name}_cache"
+ |> String.to_existing_atom()
+ |> Cachex.clear()
end)
end
def setup_multi_process_mode(tags) do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
if tags[:async] do
Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache)
Mox.set_mox_private()
else
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
Mox.set_mox_global()
Mox.stub_with(Pleroma.CachexMock, Pleroma.CachexProxy)
clear_cachex()
end
:ok
end
def setup_streamer(tags) do
if tags[:needs_streamer] do
start_supervised(%{
id: Pleroma.Web.Streamer.registry(),
start:
{Registry, :start_link, [[keys: :duplicate, name: Pleroma.Web.Streamer.registry()]]}
})
end
:ok
end
setup tags do
setup_multi_process_mode(tags)
setup_streamer(tags)
stub_pipeline()
Mox.verify_on_exit!()
:ok
end
def stub_pipeline do
Mox.stub_with(Pleroma.Web.ActivityPub.SideEffectsMock, Pleroma.Web.ActivityPub.SideEffects)
Mox.stub_with(
Pleroma.Web.ActivityPub.ObjectValidatorMock,
Pleroma.Web.ActivityPub.ObjectValidator
)
Mox.stub_with(Pleroma.Web.ActivityPub.MRFMock, Pleroma.Web.ActivityPub.MRF)
Mox.stub_with(Pleroma.Web.ActivityPub.ActivityPubMock, Pleroma.Web.ActivityPub.ActivityPub)
Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
end
def ensure_local_uploader(context) do
test_uploader = Map.get(context, :uploader) || Pleroma.Uploaders.Local
clear_config([Pleroma.Upload, :uploader], test_uploader)
clear_config([Pleroma.Upload, :filters], [])
:ok
end
@doc """
A helper that transform changeset errors to a map of messages.
changeset = Accounts.create_user(%{password: "short"})
assert "password is too short" in errors_on(changeset).password
assert %{password: ["password is too short"]} = errors_on(changeset)
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Enum.reduce(opts, message, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Aug 8, 6:38 AM (15 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1722962
Default Alt Text
(243 KB)

Event Timeline