aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/pleroma.ex24
-rw-r--r--lib/mix/tasks/pleroma/config.ex8
-rw-r--r--lib/mix/tasks/pleroma/notification_settings.ex18
-rw-r--r--lib/mix/tasks/pleroma/release_env.ex76
-rw-r--r--lib/pleroma/application.ex15
-rw-r--r--lib/pleroma/application_requirements.ex36
-rw-r--r--lib/pleroma/captcha/kocaptcha.ex3
-rw-r--r--lib/pleroma/captcha/native.ex3
-rw-r--r--lib/pleroma/config.ex41
-rw-r--r--lib/pleroma/config/config_db.ex1
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex33
-rw-r--r--lib/pleroma/config/helpers.ex17
-rw-r--r--lib/pleroma/docs/generator.ex31
-rw-r--r--lib/pleroma/docs/json.ex21
-rw-r--r--lib/pleroma/docs/markdown.ex5
-rw-r--r--lib/pleroma/emails/admin_email.ex15
-rw-r--r--lib/pleroma/emails/user_email.ex17
-rw-r--r--lib/pleroma/following_relationship.ex6
-rw-r--r--lib/pleroma/formatter.ex26
-rw-r--r--lib/pleroma/gopher/server.ex19
-rw-r--r--lib/pleroma/gun/api.ex3
-rw-r--r--lib/pleroma/gun/conn.ex85
-rw-r--r--lib/pleroma/gun/connection_pool.ex82
-rw-r--r--lib/pleroma/gun/connection_pool/reclaimer.ex85
-rw-r--r--lib/pleroma/gun/connection_pool/worker.ex133
-rw-r--r--lib/pleroma/gun/connection_pool/worker_supervisor.ex45
-rw-r--r--lib/pleroma/http/adapter_helper.ex129
-rw-r--r--lib/pleroma/http/adapter_helper/default.ex14
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex63
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex3
-rw-r--r--lib/pleroma/http/connection.ex124
-rw-r--r--lib/pleroma/http/http.ex84
-rw-r--r--lib/pleroma/http/request_builder.ex6
-rw-r--r--lib/pleroma/moderation_log.ex11
-rw-r--r--lib/pleroma/notification.ex39
-rw-r--r--lib/pleroma/object/fetcher.ex4
-rw-r--r--lib/pleroma/plugs/admin_secret_authentication_plug.ex27
-rw-r--r--lib/pleroma/plugs/frontend_static.ex54
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex47
-rw-r--r--lib/pleroma/plugs/instance_static.ex15
-rw-r--r--lib/pleroma/plugs/user_is_admin_plug.ex25
-rw-r--r--lib/pleroma/pool/connections.ex283
-rw-r--r--lib/pleroma/pool/pool.ex22
-rw-r--r--lib/pleroma/pool/request.ex65
-rw-r--r--lib/pleroma/pool/supervisor.ex42
-rw-r--r--lib/pleroma/reverse_proxy/client/tesla.ex18
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex3
-rw-r--r--lib/pleroma/telemetry/logger.ex76
-rw-r--r--lib/pleroma/tesla/middleware/follow_redirects.ex110
-rw-r--r--lib/pleroma/user.ex141
-rw-r--r--lib/pleroma/user/notification_setting.ex14
-rw-r--r--lib/pleroma/user/query.ex5
-rw-r--r--lib/pleroma/user/welcome_chat_message.ex45
-rw-r--r--lib/pleroma/user/welcome_email.ex62
-rw-r--r--lib/pleroma/user/welcome_message.ex41
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex27
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex5
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex21
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex47
-rw-r--r--lib/pleroma/web/activity_pub/mrf/tag_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex7
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex13
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex7
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex20
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex14
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex9
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex47
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex4
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex6
-rw-r--r--lib/pleroma/web/api_spec/helpers.ex4
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex17
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/config_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/invite_operation.ex4
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/relay_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/report_operation.ex7
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/status_operation.ex7
-rw-r--r--lib/pleroma/web/api_spec/operations/chat_operation.ex4
-rw-r--r--lib/pleroma/web/api_spec/operations/domain_block_operation.ex9
-rw-r--r--lib/pleroma/web/api_spec/schemas/account.ex14
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat_message.ex35
-rw-r--r--lib/pleroma/web/chat_channel.ex6
-rw-r--r--lib/pleroma/web/endpoint.ex11
-rw-r--r--lib/pleroma/web/feed/user_controller.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex31
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/search_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex25
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex24
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy.ex26
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex58
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/chat_controller.ex15
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex4
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex3
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex9
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat_view.ex17
-rw-r--r--lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex2
-rw-r--r--lib/pleroma/web/push/impl.ex2
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex40
-rw-r--r--lib/pleroma/web/rich_media/parser.ex20
-rw-r--r--lib/pleroma/web/rich_media/parsers/oembed_parser.ex2
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex2
-rw-r--r--lib/pleroma/web/templates/o_auth/mfa/totp.html.eex2
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex14
-rw-r--r--lib/pleroma/web/views/masto_fe_view.ex32
120 files changed, 2038 insertions, 1157 deletions
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 9f0bf6ecb..074492a46 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -24,8 +24,10 @@ defmodule Mix.Pleroma do
Application.put_env(:logger, :console, level: :debug)
end
+ adapter = Application.get_env(:tesla, :adapter)
+
apps =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
+ if adapter == Tesla.Adapter.Gun do
[:gun | @apps]
else
[:hackney | @apps]
@@ -33,11 +35,14 @@ defmodule Mix.Pleroma do
Enum.each(apps, &Application.ensure_all_started/1)
- children = [
- Pleroma.Repo,
- {Pleroma.Config.TransferTask, false},
- Pleroma.Web.Endpoint
- ]
+ children =
+ [
+ Pleroma.Repo,
+ {Pleroma.Config.TransferTask, false},
+ Pleroma.Web.Endpoint,
+ {Oban, Pleroma.Config.get(Oban)}
+ ] ++
+ http_children(adapter)
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
@@ -115,4 +120,11 @@ defmodule Mix.Pleroma do
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 d5129d410..904c5a74b 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -83,7 +83,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp migrate_from_db(opts) do
if Pleroma.Config.get([:configurable_from_database]) do
- env = opts[:env] || "prod"
+ env = opts[:env] || Pleroma.Config.get(:env)
config_path =
if Pleroma.Config.get(:release) do
@@ -105,6 +105,10 @@ defmodule Mix.Tasks.Pleroma.Config do
:ok = File.close(file)
System.cmd("mix", ["format", config_path])
+
+ shell_info(
+ "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
+ )
else
migration_error()
end
@@ -112,7 +116,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp migration_error do
shell_error(
- "Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true."
+ "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
)
end
diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex
index 7d65f0587..00f5ba7bf 100644
--- a/lib/mix/tasks/pleroma/notification_settings.ex
+++ b/lib/mix/tasks/pleroma/notification_settings.ex
@@ -3,8 +3,8 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
@moduledoc """
Example:
- > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user
- > mix pleroma.notification_settings --privacy-option=true # set true for all users
+ > mix pleroma.notification_settings --hide-notification-contents=false --nickname-users="parallel588" # set false only for parallel588 user
+ > mix pleroma.notification_settings --hide-notification-contents=true # set true for all users
"""
@@ -19,16 +19,16 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
OptionParser.parse(
args,
strict: [
- privacy_option: :boolean,
+ hide_notification_contents: :boolean,
email_users: :string,
nickname_users: :string
]
)
- privacy_option = Keyword.get(options, :privacy_option)
+ hide_notification_contents = Keyword.get(options, :hide_notification_contents)
- if not is_nil(privacy_option) do
- privacy_option
+ if not is_nil(hide_notification_contents) do
+ hide_notification_contents
|> build_query(options)
|> Pleroma.Repo.update_all([])
end
@@ -36,15 +36,15 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
shell_info("Done")
end
- defp build_query(privacy_option, options) do
+ defp build_query(hide_notification_contents, options) do
query =
from(u in Pleroma.User,
update: [
set: [
notification_settings:
fragment(
- "jsonb_set(notification_settings, '{privacy_option}', ?)",
- ^privacy_option
+ "jsonb_set(notification_settings, '{hide_notification_contents}', ?)",
+ ^hide_notification_contents
)
]
]
diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex
new file mode 100644
index 000000000..9da74ffcf
--- /dev/null
+++ b/lib/mix/tasks/pleroma/release_env.ex
@@ -0,0 +1,76 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.ReleaseEnv do
+ use Mix.Task
+ import Mix.Pleroma
+
+ @shortdoc "Generate Pleroma environment file."
+ @moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
+
+ def run(["gen" | rest]) do
+ {options, [], []} =
+ OptionParser.parse(
+ rest,
+ strict: [
+ force: :boolean,
+ path: :string
+ ],
+ aliases: [
+ p: :path,
+ f: :force
+ ]
+ )
+
+ file_path =
+ get_option(
+ options,
+ :path,
+ "Environment file path",
+ "./config/pleroma.env"
+ )
+
+ env_path = Path.expand(file_path)
+
+ proceed? =
+ if File.exists?(env_path) do
+ get_option(
+ options,
+ :force,
+ "Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
+ "n"
+ ) === "y"
+ else
+ true
+ end
+
+ if proceed? do
+ case do_generate(env_path) do
+ {:error, reason} ->
+ shell_error(
+ File.Error.message(%{action: "write to file", reason: reason, path: env_path})
+ )
+
+ _ ->
+ shell_info("\nThe file generated: #{env_path}.\n")
+
+ shell_info("""
+ WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
+ Example:
+ chmod 0444 #{file_path}
+ chattr +i #{file_path}
+ """)
+ end
+ else
+ shell_info("\nThe file is exist. #{env_path}.\n")
+ end
+ end
+
+ def do_generate(path) do
+ content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
+
+ File.mkdir_p!(Path.dirname(path))
+ File.write(path, content)
+ end
+end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 84f3aa82d..0ffb55358 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -35,6 +35,11 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
+ # 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)
+ Pleroma.Telemetry.Logger.attach()
Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn()
@@ -42,6 +47,7 @@ defmodule Pleroma.Application do
Pleroma.ApplicationRequirements.verify!()
setup_instrumenters()
load_custom_modules()
+ Pleroma.Docs.JSON.compile()
adapter = Application.get_env(:tesla, :adapter)
@@ -218,9 +224,7 @@ defmodule Pleroma.Application do
# start hackney and gun pools in tests
defp http_children(_, :test) do
- hackney_options = Config.get([:hackney_pools, :federation])
- hackney_pool = :hackney_pool.child_spec(:federation, hackney_options)
- [hackney_pool, Pleroma.Pool.Supervisor]
+ http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
end
defp http_children(Tesla.Adapter.Hackney, _) do
@@ -239,7 +243,10 @@ defmodule Pleroma.Application do
end
end
- defp http_children(Tesla.Adapter.Gun, _), do: [Pleroma.Pool.Supervisor]
+ 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/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 88575a498..16f62b6f5 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -16,7 +16,9 @@ defmodule Pleroma.ApplicationRequirements do
@spec verify!() :: :ok | VerifyError.t()
def verify! do
:ok
+ |> check_confirmation_accounts!
|> check_migrations_applied!()
+ |> check_welcome_message_config!()
|> check_rum!()
|> handle_result()
end
@@ -24,6 +26,40 @@ defmodule Pleroma.ApplicationRequirements do
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.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise 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
diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex
index 6bc2fa158..337506647 100644
--- a/lib/pleroma/captcha/kocaptcha.ex
+++ b/lib/pleroma/captcha/kocaptcha.ex
@@ -21,7 +21,8 @@ defmodule Pleroma.Captcha.Kocaptcha do
type: :kocaptcha,
token: json_resp["token"],
url: endpoint <> json_resp["url"],
- answer_data: json_resp["md5"]
+ answer_data: json_resp["md5"],
+ seconds_valid: Pleroma.Config.get([Pleroma.Captcha, :seconds_valid])
}
end
end
diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex
index a90631d61..8d604d2b2 100644
--- a/lib/pleroma/captcha/native.ex
+++ b/lib/pleroma/captcha/native.ex
@@ -17,7 +17,8 @@ defmodule Pleroma.Captcha.Native do
type: :native,
token: token(),
url: "data:image/png;base64," <> Base.encode64(img_binary),
- answer_data: answer_data
+ answer_data: answer_data,
+ seconds_valid: Pleroma.Config.get([Pleroma.Captcha, :seconds_valid])
}
end
end
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index cc80deff5..a8329cc1e 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -11,12 +11,10 @@ defmodule Pleroma.Config do
def get([key], default), do: get(key, default)
- def get([parent_key | keys], default) do
- case :pleroma
- |> Application.get_env(parent_key)
- |> get_in(keys) do
- nil -> default
- any -> any
+ def get([_ | _] = path, default) do
+ case fetch(path) do
+ {:ok, value} -> value
+ :error -> default
end
end
@@ -34,6 +32,24 @@ defmodule Pleroma.Config do
end
end
+ def fetch(key) when is_atom(key), do: fetch([key])
+
+ def fetch([root_key | keys]) do
+ Enum.reduce_while(keys, Application.fetch_env(:pleroma, root_key), fn
+ key, {:ok, config} when is_map(config) or is_list(config) ->
+ case Access.fetch(config, key) do
+ :error ->
+ {:halt, :error}
+
+ value ->
+ {:cont, value}
+ end
+
+ _key, _config ->
+ {:halt, :error}
+ end)
+ end
+
def put([key], value), do: put(key, value)
def put([parent_key | keys], value) do
@@ -50,12 +66,15 @@ defmodule Pleroma.Config do
def delete([key]), do: delete(key)
- def delete([parent_key | keys]) do
- {_, parent} =
- Application.get_env(:pleroma, parent_key)
- |> get_and_update_in(keys, fn _ -> :pop end)
+ def delete([parent_key | keys] = path) do
+ with {:ok, _} <- fetch(path) do
+ {_, parent} =
+ parent_key
+ |> get()
+ |> get_and_update_in(keys, fn _ -> :pop end)
- Application.put_env(:pleroma, parent_key, parent)
+ Application.put_env(:pleroma, parent_key, parent)
+ end
end
def delete(key) do
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex
index 1a89d8895..e5b7811aa 100644
--- a/lib/pleroma/config/config_db.ex
+++ b/lib/pleroma/config/config_db.ex
@@ -156,7 +156,6 @@ defmodule Pleroma.ConfigDB do
{:quack, :meta},
{:mime, :types},
{:cors_plug, [:max_age, :methods, :expose, :headers]},
- {:auto_linker, :opts},
{:swarm, :node_blacklist},
{:logger, :backends}
]
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 0a6c724fb..0f52eb210 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -54,6 +54,25 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_hellthread_threshold()
mrf_user_allowlist()
check_old_mrf_config()
+ check_media_proxy_whitelist_config()
+ check_welcome_message_config()
+ 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 change to the new namespace:
+ \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname`
+ \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message`
+ """)
+ end
end
def check_old_mrf_config do
@@ -65,7 +84,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
move_namespace_and_warn(@mrf_config_map, warning_preface)
end
- @spec move_namespace_and_warn([config_map()], String.t()) :: :ok
+ @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
def move_namespace_and_warn(config_map, warning_preface) do
warning =
Enum.reduce(config_map, "", fn
@@ -84,4 +103,16 @@ defmodule Pleroma.Config.DeprecationWarnings do
Logger.warn(warning_preface <> warning)
end
end
+
+ @spec check_media_proxy_whitelist_config() :: :ok | nil
+ 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.
+ """)
+ end
+ end
end
diff --git a/lib/pleroma/config/helpers.ex b/lib/pleroma/config/helpers.ex
new file mode 100644
index 000000000..3dce40ea0
--- /dev/null
+++ b/lib/pleroma/config/helpers.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Config.Helpers do
+ alias Pleroma.Config
+
+ def instance_name, do: Config.get([:instance, :name])
+
+ defp instance_notify_email do
+ Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
+ end
+
+ def sender do
+ {instance_name(), instance_notify_email()}
+ end
+end
diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex
index e0fc8cd02..a671a6278 100644
--- a/lib/pleroma/docs/generator.ex
+++ b/lib/pleroma/docs/generator.ex
@@ -6,16 +6,21 @@ defmodule Pleroma.Docs.Generator do
implementation.process(descriptions)
end
- @spec list_modules_in_dir(String.t(), String.t()) :: [module()]
- def list_modules_in_dir(dir, start) do
- with {:ok, files} <- File.ls(dir) do
- files
- |> Enum.filter(&String.ends_with?(&1, ".ex"))
- |> Enum.map(fn filename ->
- module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
- String.to_atom(start <> module)
- end)
- end
+ @spec list_behaviour_implementations(behaviour :: module()) :: [module()]
+ def list_behaviour_implementations(behaviour) do
+ :code.all_loaded()
+ |> Enum.filter(fn {module, _} ->
+ # This shouldn't be needed as all modules are expected to have module_info/1,
+ # but in test enviroments some transient modules `:elixir_compiler_XX`
+ # are loaded for some reason (where XX is a random integer).
+ if function_exported?(module, :module_info, 1) do
+ module.module_info(:attributes)
+ |> Keyword.get_values(:behaviour)
+ |> List.flatten()
+ |> Enum.member?(behaviour)
+ end
+ end)
+ |> Enum.map(fn {module, _} -> module end)
end
@doc """
@@ -87,6 +92,12 @@ defmodule Pleroma.Docs.Generator do
else: string
end
+ defp format_suggestions({:list_behaviour_implementations, behaviour}) do
+ behaviour
+ |> list_behaviour_implementations()
+ |> format_suggestions()
+ end
+
defp format_suggestions([]), do: []
defp format_suggestions([suggestion | tail]) do
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
index d1cf1f487..feeb4320e 100644
--- a/lib/pleroma/docs/json.ex
+++ b/lib/pleroma/docs/json.ex
@@ -1,5 +1,19 @@
defmodule Pleroma.Docs.JSON do
@behaviour Pleroma.Docs.Generator
+ @external_resource "config/description.exs"
+ @raw_config Pleroma.Config.Loader.read("config/description.exs")
+ @raw_descriptions @raw_config[:pleroma][:config_description]
+ @term __MODULE__.Compiled
+
+ @spec compile :: :ok
+ def compile do
+ :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
+ end
+
+ @spec compiled_descriptions :: Map.t()
+ def compiled_descriptions do
+ :persistent_term.get(@term)
+ end
@spec process(keyword()) :: {:ok, String.t()}
def process(descriptions) do
@@ -13,11 +27,4 @@ defmodule Pleroma.Docs.JSON do
{:ok, path}
end
end
-
- def compile do
- with config <- Pleroma.Config.Loader.read("config/description.exs") do
- config[:pleroma][:config_description]
- |> Pleroma.Docs.Generator.convert_to_strings()
- end
- end
end
diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex
index 68b106499..da3f20f43 100644
--- a/lib/pleroma/docs/markdown.ex
+++ b/lib/pleroma/docs/markdown.ex
@@ -68,6 +68,11 @@ defmodule Pleroma.Docs.Markdown do
IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n")
end
+ defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do
+ suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour)
+ print_suggestions(file, suggestions)
+ end
+
defp print_suggestions(_file, nil), do: nil
defp print_suggestions(_file, ""), do: nil
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index aa0b2a66b..c27ad1065 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do
import Swoosh.Email
alias Pleroma.Config
+ alias Pleroma.HTML
alias Pleroma.Web.Router.Helpers
defp instance_config, do: Config.get(:instance)
@@ -82,4 +83,18 @@ defmodule Pleroma.Emails.AdminEmail do
|> subject("#{instance_name()} Report")
|> html_body(html_body)
end
+
+ def new_unapproved_registration(to, account) do
+ html_body = """
+ <p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
+ <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
+ <a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a>
+ """
+
+ new()
+ |> to({to.name, to.email})
+ |> from({instance_name(), instance_notify_email()})
+ |> subject("New account up for review on #{instance_name()} (@#{account.nickname})")
+ |> html_body(html_body)
+ end
end
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index dfadc10b3..313533859 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -12,17 +12,22 @@ defmodule Pleroma.Emails.UserEmail do
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
- defp instance_name, do: Config.get([:instance, :name])
-
- defp sender do
- email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
- {instance_name(), email}
- end
+ import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
defp recipient(email, nil), do: email
defp recipient(email, name), do: {name, email}
defp recipient(%User{} = user), do: recipient(user.email, user.name)
+ @spec welcome(User.t(), map()) :: Swoosh.Email.t()
+ def welcome(user, opts \\ %{}) do
+ new()
+ |> to(recipient(user))
+ |> from(Map.get(opts, :sender, sender()))
+ |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
+ |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
+ |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
+ end
+
def password_reset_email(user, token) when is_binary(token) do
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index c2020d30a..83b366dd4 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -95,7 +95,11 @@ defmodule Pleroma.FollowingRelationship do
|> where([r], r.state == ^:follow_accept)
end
- def followers_ap_ids(%User{} = user, from_ap_ids \\ nil) do
+ def followers_ap_ids(user, from_ap_ids \\ nil)
+
+ def followers_ap_ids(_, []), do: []
+
+ def followers_ap_ids(%User{} = user, from_ap_ids) do
query =
user
|> followers_query()
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 02a93a8dc..0c450eae4 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -10,11 +10,15 @@ defmodule Pleroma.Formatter do
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
- @auto_linker_config hashtag: true,
- hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
- mention: true,
- mention_handler: &Pleroma.Formatter.mention_handler/4,
- scheme: true
+ defp linkify_opts do
+ Pleroma.Config.get(Pleroma.Formatter) ++
+ [
+ hashtag: true,
+ hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
+ mention: true,
+ mention_handler: &Pleroma.Formatter.mention_handler/4
+ ]
+ end
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do
@@ -80,19 +84,19 @@ defmodule Pleroma.Formatter do
@spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do
- options = options ++ @auto_linker_config
+ options = linkify_opts() ++ options
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options)
- {text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options)
+ {text_mentions, %{mentions: mentions}} = Linkify.link_map(mentions, acc, options)
+ {text_rest, %{tags: tags}} = Linkify.link_map(rest, acc, options)
{text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}
else
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
+ {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options)
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end
@@ -111,9 +115,9 @@ defmodule Pleroma.Formatter do
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
- AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options)
+ Linkify.link(mentions, options) <> Linkify.link(rest, options)
else
- AutoLinker.link(text, options)
+ Linkify.link(text, options)
end
end
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index 3d56d50a9..e9f54c4c0 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -96,16 +96,18 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
def response("/main/public") do
posts =
- ActivityPub.fetch_public_activities(%{"type" => ["Create"], "local_only" => true})
- |> render_activities
+ %{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 =
- ActivityPub.fetch_public_activities(%{"type" => ["Create"]})
- |> render_activities
+ %{type: ["Create"]}
+ |> ActivityPub.fetch_public_activities()
+ |> render_activities()
info("Welcome to the Federated Timeline!") <> posts <> ".\r\n"
end
@@ -130,13 +132,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
def response("/users/" <> nickname) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
params = %{
- "type" => ["Create"],
- "actor_id" => user.ap_id
+ type: ["Create"],
+ actor_id: user.ap_id
}
activities =
- ActivityPub.fetch_public_activities(params)
- |> render_activities
+ params
+ |> ActivityPub.fetch_public_activities()
+ |> render_activities()
info("Posts by #{user.nickname}") <> activities <> ".\r\n"
else
diff --git a/lib/pleroma/gun/api.ex b/lib/pleroma/gun/api.ex
index f51cd7db8..09be74392 100644
--- a/lib/pleroma/gun/api.ex
+++ b/lib/pleroma/gun/api.ex
@@ -19,7 +19,8 @@ defmodule Pleroma.Gun.API do
:tls_opts,
:tcp_opts,
:socks_opts,
- :ws_opts
+ :ws_opts,
+ :supervise
]
@impl Gun
diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex
index cd25a2e74..a3f75a4bb 100644
--- a/lib/pleroma/gun/conn.ex
+++ b/lib/pleroma/gun/conn.ex
@@ -3,85 +3,33 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Gun.Conn do
- @moduledoc """
- Struct for gun connection data
- """
alias Pleroma.Gun
- alias Pleroma.Pool.Connections
require Logger
- @type gun_state :: :up | :down
- @type conn_state :: :active | :idle
-
- @type t :: %__MODULE__{
- conn: pid(),
- gun_state: gun_state(),
- conn_state: conn_state(),
- used_by: [pid()],
- last_reference: pos_integer(),
- crf: float(),
- retries: pos_integer()
- }
-
- defstruct conn: nil,
- gun_state: :open,
- conn_state: :init,
- used_by: [],
- last_reference: 0,
- crf: 1,
- retries: 0
-
- @spec open(String.t() | URI.t(), atom(), keyword()) :: :ok | nil
- def open(url, name, opts \\ [])
- def open(url, name, opts) when is_binary(url), do: open(URI.parse(url), name, opts)
-
- def open(%URI{} = uri, name, opts) do
+ def open(%URI{} = uri, opts) do
pool_opts = Pleroma.Config.get([:connections_pool], [])
opts =
opts
|> Enum.into(%{})
- |> Map.put_new(:retry, pool_opts[:retry] || 1)
- |> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 1000)
|> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
+ |> Map.put_new(:supervise, false)
|> maybe_add_tls_opts(uri)
- key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
-
- max_connections = pool_opts[:max_connections] || 250
-
- conn_pid =
- if Connections.count(name) < max_connections do
- do_open(uri, opts)
- else
- close_least_used_and_do_open(name, uri, opts)
- end
-
- if is_pid(conn_pid) do
- conn = %Pleroma.Gun.Conn{
- conn: conn_pid,
- gun_state: :up,
- conn_state: :active,
- last_reference: :os.system_time(:second)
- }
-
- :ok = Gun.set_owner(conn_pid, Process.whereis(name))
- Connections.add_conn(name, key, conn)
- end
+ do_open(uri, opts)
end
defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
- defp maybe_add_tls_opts(opts, %URI{scheme: "https", host: host}) do
+ defp maybe_add_tls_opts(opts, %URI{scheme: "https"}) do
tls_opts = [
verify: :verify_peer,
cacertfile: CAStore.file_path(),
depth: 20,
reuse_sessions: false,
- verify_fun:
- {&:ssl_verify_hostname.verify_fun/3,
- [check_hostname: Pleroma.HTTP.Connection.format_host(host)]}
+ log_level: :warning,
+ customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
]
tls_opts =
@@ -105,7 +53,7 @@ defmodule Pleroma.Gun.Conn do
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]),
stream <- Gun.connect(conn, connect_opts),
{:response, :fin, 200, _} <- Gun.await(conn, stream) do
- conn
+ {:ok, conn}
else
error ->
Logger.warn(
@@ -141,7 +89,7 @@ defmodule Pleroma.Gun.Conn do
with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
- conn
+ {:ok, conn}
else
error ->
Logger.warn(
@@ -155,11 +103,11 @@ defmodule Pleroma.Gun.Conn do
end
defp do_open(%URI{host: host, port: port} = uri, opts) do
- host = Pleroma.HTTP.Connection.parse_host(host)
+ host = Pleroma.HTTP.AdapterHelper.parse_host(host)
with {:ok, conn} <- Gun.open(host, port, opts),
{:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
- conn
+ {:ok, conn}
else
error ->
Logger.warn(
@@ -171,7 +119,7 @@ defmodule Pleroma.Gun.Conn do
end
defp destination_opts(%URI{host: host, port: port}) do
- host = Pleroma.HTTP.Connection.parse_host(host)
+ host = Pleroma.HTTP.AdapterHelper.parse_host(host)
%{host: host, port: port}
end
@@ -181,17 +129,6 @@ defmodule Pleroma.Gun.Conn do
defp add_http2_opts(opts, _, _), do: opts
- defp close_least_used_and_do_open(name, uri, opts) do
- with [{key, conn} | _conns] <- Connections.get_unused_conns(name),
- :ok <- Gun.close(conn.conn) do
- Connections.remove_conn(name, key)
-
- do_open(uri, opts)
- else
- [] -> {:error, :pool_overflowed}
- end
- end
-
def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
"#{scheme}://#{host}#{path}"
end
diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex
new file mode 100644
index 000000000..f34602b73
--- /dev/null
+++ b/lib/pleroma/gun/connection_pool.ex
@@ -0,0 +1,82 @@
+defmodule Pleroma.Gun.ConnectionPool do
+ @registry __MODULE__
+
+ alias Pleroma.Gun.ConnectionPool.WorkerSupervisor
+
+ def children do
+ [
+ {Registry, keys: :unique, name: @registry},
+ Pleroma.Gun.ConnectionPool.WorkerSupervisor
+ ]
+ end
+
+ @spec get_conn(URI.t(), keyword()) :: {:ok, pid()} | {:error, term()}
+ def get_conn(uri, opts) do
+ key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
+
+ case Registry.lookup(@registry, key) do
+ # The key has already been registered, but connection is not up yet
+ [{worker_pid, nil}] ->
+ get_gun_pid_from_worker(worker_pid, true)
+
+ [{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] ->
+ GenServer.call(worker_pid, :add_client)
+ {:ok, gun_pid}
+
+ [] ->
+ # :gun.set_owner fails in :connected state for whatevever reason,
+ # so we open the connection in the process directly and send it's pid back
+ # We trust gun to handle timeouts by itself
+ case WorkerSupervisor.start_worker([key, uri, opts, self()]) do
+ {:ok, worker_pid} ->
+ get_gun_pid_from_worker(worker_pid, false)
+
+ {:error, {:already_started, worker_pid}} ->
+ get_gun_pid_from_worker(worker_pid, true)
+
+ err ->
+ err
+ end
+ end
+ end
+
+ defp get_gun_pid_from_worker(worker_pid, register) do
+ # GenServer.call will block the process for timeout length if
+ # the server crashes on startup (which will happen if gun fails to connect)
+ # so instead we use cast + monitor
+
+ ref = Process.monitor(worker_pid)
+ if register, do: GenServer.cast(worker_pid, {:add_client, self()})
+
+ receive do
+ {:conn_pid, pid} ->
+ Process.demonitor(ref)
+ {:ok, pid}
+
+ {:DOWN, ^ref, :process, ^worker_pid, reason} ->
+ case reason do
+ {:shutdown, {:error, _} = error} -> error
+ {:shutdown, error} -> {:error, error}
+ _ -> {:error, reason}
+ end
+ end
+ end
+
+ @spec release_conn(pid()) :: :ok
+ def release_conn(conn_pid) do
+ # :ets.fun2ms(fn {_, {worker_pid, {gun_pid, _, _, _}}} when gun_pid == conn_pid ->
+ # worker_pid end)
+ query_result =
+ Registry.select(@registry, [
+ {{:_, :"$1", {:"$2", :_, :_, :_}}, [{:==, :"$2", conn_pid}], [:"$1"]}
+ ])
+
+ case query_result do
+ [worker_pid] ->
+ GenServer.call(worker_pid, :remove_client)
+
+ [] ->
+ :ok
+ end
+ end
+end
diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex
new file mode 100644
index 000000000..cea800882
--- /dev/null
+++ b/lib/pleroma/gun/connection_pool/reclaimer.ex
@@ -0,0 +1,85 @@
+defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
+ use GenServer, restart: :temporary
+
+ @registry Pleroma.Gun.ConnectionPool
+
+ def start_monitor do
+ pid =
+ case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do
+ {:ok, pid} ->
+ pid
+
+ {:error, {:already_registered, pid}} ->
+ pid
+ end
+
+ {pid, Process.monitor(pid)}
+ end
+
+ @impl true
+ def init(_) do
+ {:ok, nil, {:continue, :reclaim}}
+ end
+
+ @impl true
+ def handle_continue(:reclaim, _) do
+ max_connections = Pleroma.Config.get([:connections_pool, :max_connections])
+
+ reclaim_max =
+ [:connections_pool, :reclaim_multiplier]
+ |> Pleroma.Config.get()
+ |> Kernel.*(max_connections)
+ |> round
+ |> max(1)
+
+ :telemetry.execute([:pleroma, :connection_pool, :reclaim, :start], %{}, %{
+ max_connections: max_connections,
+ reclaim_max: reclaim_max
+ })
+
+ # :ets.fun2ms(
+ # fn {_, {worker_pid, {_, used_by, crf, last_reference}}} when used_by == [] ->
+ # {worker_pid, crf, last_reference} end)
+ unused_conns =
+ Registry.select(
+ @registry,
+ [
+ {{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
+ ]
+ )
+
+ case unused_conns do
+ [] ->
+ :telemetry.execute(
+ [:pleroma, :connection_pool, :reclaim, :stop],
+ %{reclaimed_count: 0},
+ %{
+ max_connections: max_connections
+ }
+ )
+
+ {:stop, :no_unused_conns, nil}
+
+ unused_conns ->
+ reclaimed =
+ unused_conns
+ |> Enum.sort(fn {_pid1, crf1, last_reference1}, {_pid2, crf2, last_reference2} ->
+ crf1 <= crf2 and last_reference1 <= last_reference2
+ end)
+ |> Enum.take(reclaim_max)
+
+ reclaimed
+ |> Enum.each(fn {pid, _, _} ->
+ DynamicSupervisor.terminate_child(Pleroma.Gun.ConnectionPool.WorkerSupervisor, pid)
+ end)
+
+ :telemetry.execute(
+ [:pleroma, :connection_pool, :reclaim, :stop],
+ %{reclaimed_count: Enum.count(reclaimed)},
+ %{max_connections: max_connections}
+ )
+
+ {:stop, :normal, nil}
+ end
+ end
+end
diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex
new file mode 100644
index 000000000..fec9d0efa
--- /dev/null
+++ b/lib/pleroma/gun/connection_pool/worker.ex
@@ -0,0 +1,133 @@
+defmodule Pleroma.Gun.ConnectionPool.Worker do
+ alias Pleroma.Gun
+ use GenServer, restart: :temporary
+
+ @registry Pleroma.Gun.ConnectionPool
+
+ def start_link([key | _] = opts) do
+ GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}})
+ end
+
+ @impl true
+ def init([_key, _uri, _opts, _client_pid] = opts) do
+ {:ok, nil, {:continue, {:connect, opts}}}
+ end
+
+ @impl true
+ def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
+ with {:ok, conn_pid} <- Gun.Conn.open(uri, opts),
+ Process.link(conn_pid) do
+ time = :erlang.monotonic_time(:millisecond)
+
+ {_, _} =
+ Registry.update_value(@registry, key, fn _ ->
+ {conn_pid, [client_pid], 1, time}
+ end)
+
+ send(client_pid, {:conn_pid, conn_pid})
+
+ {:noreply,
+ %{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}},
+ :hibernate}
+ else
+ err ->
+ {:stop, {:shutdown, err}, nil}
+ end
+ end
+
+ @impl true
+ def handle_cast({:add_client, client_pid}, state) do
+ case handle_call(:add_client, {client_pid, nil}, state) do
+ {:reply, conn_pid, state, :hibernate} ->
+ send(client_pid, {:conn_pid, conn_pid})
+ {:noreply, state, :hibernate}
+ end
+ end
+
+ @impl true
+ def handle_cast({:remove_client, client_pid}, state) do
+ case handle_call(:remove_client, {client_pid, nil}, state) do
+ {:reply, _, state, :hibernate} ->
+ {:noreply, state, :hibernate}
+ end
+ end
+
+ @impl true
+ def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do
+ time = :erlang.monotonic_time(:millisecond)
+
+ {{conn_pid, _, _, _}, _} =
+ Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+ {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
+ end)
+
+ state =
+ if state.timer != nil do
+ Process.cancel_timer(state[:timer])
+ %{state | timer: nil}
+ else
+ state
+ end
+
+ ref = Process.monitor(client_pid)
+
+ state = put_in(state.client_monitors[client_pid], ref)
+ {:reply, conn_pid, state, :hibernate}
+ end
+
+ @impl true
+ def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
+ {{_conn_pid, used_by, _crf, _last_reference}, _} =
+ Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
+ {conn_pid, List.delete(used_by, client_pid), crf, last_reference}
+ end)
+
+ {ref, state} = pop_in(state.client_monitors[client_pid])
+ Process.demonitor(ref)
+
+ timer =
+ if used_by == [] do
+ max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
+ Process.send_after(self(), :idle_close, max_idle)
+ else
+ nil
+ end
+
+ {:reply, :ok, %{state | timer: timer}, :hibernate}
+ end
+
+ @impl true
+ def handle_info(:idle_close, state) do
+ # Gun monitors the owner process, and will close the connection automatically
+ # when it's terminated
+ {:stop, :normal, state}
+ end
+
+ # Gracefully shutdown if the connection got closed without any streams left
+ @impl true
+ def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do
+ {:stop, :normal, state}
+ end
+
+ # Otherwise, shutdown with an error
+ @impl true
+ def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams} = down_message, state) do
+ {:stop, {:error, down_message}, state}
+ end
+
+ @impl true
+ def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
+ :telemetry.execute(
+ [:pleroma, :connection_pool, :client_death],
+ %{client_pid: pid, reason: reason},
+ %{key: state.key}
+ )
+
+ handle_cast({:remove_client, pid}, state)
+ end
+
+ # LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
+ defp crf(time_delta, prev_crf) do
+ 1 + :math.pow(0.5, 0.0001 * time_delta) * prev_crf
+ end
+end
diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex
new file mode 100644
index 000000000..39615c956
--- /dev/null
+++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex
@@ -0,0 +1,45 @@
+defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
+ @moduledoc "Supervisor for pool workers. Does not do anything except enforce max connection limit"
+
+ use DynamicSupervisor
+
+ def start_link(opts) do
+ DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ def init(_opts) do
+ DynamicSupervisor.init(
+ strategy: :one_for_one,
+ max_children: Pleroma.Config.get([:connections_pool, :max_connections])
+ )
+ end
+
+ def start_worker(opts, retry \\ false) do
+ case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do
+ {:error, :max_children} ->
+ if retry or free_pool() == :error do
+ :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts})
+ {:error, :pool_full}
+ else
+ start_worker(opts, true)
+ end
+
+ res ->
+ res
+ end
+ end
+
+ defp free_pool do
+ wait_for_reclaimer_finish(Pleroma.Gun.ConnectionPool.Reclaimer.start_monitor())
+ end
+
+ defp wait_for_reclaimer_finish({pid, mon}) do
+ receive do
+ {:DOWN, ^mon, :process, ^pid, :no_unused_conns} ->
+ :error
+
+ {:DOWN, ^mon, :process, ^pid, :normal} ->
+ :ok
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index 510722ff9..9ec3836b0 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -3,32 +3,30 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper do
- alias Pleroma.HTTP.Connection
+ @moduledoc """
+ Configure Tesla.Client with default and customized adapter options.
+ """
+ @defaults [pool: :federation]
+
+ @type proxy_type() :: :socks4 | :socks5
+ @type host() :: charlist() | :inet.ip_address()
+
+ alias Pleroma.Config
+ alias Pleroma.HTTP.AdapterHelper
+ require Logger
@type proxy ::
{Connection.host(), pos_integer()}
| {Connection.proxy_type(), Connection.host(), pos_integer()}
@callback options(keyword(), URI.t()) :: keyword()
- @callback after_request(keyword()) :: :ok
-
- @spec options(keyword(), URI.t()) :: keyword()
- def options(opts, _uri) do
- proxy = Pleroma.Config.get([:http, :proxy_url], nil)
- maybe_add_proxy(opts, format_proxy(proxy))
- end
-
- @spec maybe_get_conn(URI.t(), keyword()) :: keyword()
- def maybe_get_conn(_uri, opts), do: opts
-
- @spec after_request(keyword()) :: :ok
- def after_request(_opts), do: :ok
+ @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()}
@spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
def format_proxy(nil), do: nil
def format_proxy(proxy_url) do
- case Connection.parse_proxy(proxy_url) do
+ case parse_proxy(proxy_url) do
{:ok, host, port} -> {host, port}
{:ok, type, host, port} -> {type, host, port}
_ -> nil
@@ -38,4 +36,105 @@ defmodule Pleroma.HTTP.AdapterHelper do
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
def maybe_add_proxy(opts, nil), do: opts
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
+
+ @doc """
+ Merge default connection & adapter options with received ones.
+ """
+
+ @spec options(URI.t(), keyword()) :: keyword()
+ def options(%URI{} = uri, opts \\ []) do
+ @defaults
+ |> put_timeout()
+ |> Keyword.merge(opts)
+ |> adapter_helper().options(uri)
+ end
+
+ # For Hackney, this is the time a connection can stay idle in the pool.
+ # For Gun, this is the timeout to receive a message from Gun.
+ defp put_timeout(opts) do
+ {config_key, default} =
+ if adapter() == Tesla.Adapter.Gun do
+ {:pools, Config.get([:pools, :default, :timeout], 5_000)}
+ else
+ {:hackney_pools, 10_000}
+ end
+
+ timeout = Config.get([config_key, opts[:pool], :timeout], default)
+
+ Keyword.merge(opts, timeout: timeout)
+ end
+
+ def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts)
+ defp adapter, do: Application.get_env(:tesla, :adapter)
+
+ defp adapter_helper do
+ case adapter() do
+ Tesla.Adapter.Gun -> AdapterHelper.Gun
+ Tesla.Adapter.Hackney -> AdapterHelper.Hackney
+ _ -> AdapterHelper.Default
+ end
+ end
+
+ @spec parse_proxy(String.t() | tuple() | nil) ::
+ {:ok, host(), pos_integer()}
+ | {:ok, proxy_type(), host(), pos_integer()}
+ | {:error, atom()}
+ | nil
+
+ def parse_proxy(nil), do: nil
+
+ def parse_proxy(proxy) when is_binary(proxy) do
+ with [host, port] <- String.split(proxy, ":"),
+ {port, ""} <- Integer.parse(port) do
+ {:ok, parse_host(host), port}
+ else
+ {_, _} ->
+ Logger.warn("Parsing port failed #{inspect(proxy)}")
+ {:error, :invalid_proxy_port}
+
+ :error ->
+ Logger.warn("Parsing port failed #{inspect(proxy)}")
+ {:error, :invalid_proxy_port}
+
+ _ ->
+ Logger.warn("Parsing proxy failed #{inspect(proxy)}")
+ {:error, :invalid_proxy}
+ end
+ end
+
+ def parse_proxy(proxy) when is_tuple(proxy) do
+ with {type, host, port} <- proxy do
+ {:ok, type, parse_host(host), port}
+ else
+ _ ->
+ Logger.warn("Parsing proxy failed #{inspect(proxy)}")
+ {:error, :invalid_proxy}
+ end
+ end
+
+ @spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address()
+ def parse_host(host) when is_list(host), do: host
+ def parse_host(host) when is_atom(host), do: to_charlist(host)
+
+ def parse_host(host) when is_binary(host) do
+ host = to_charlist(host)
+
+ case :inet.parse_address(host) do
+ {:error, :einval} -> host
+ {:ok, ip} -> ip
+ end
+ end
+
+ @spec format_host(String.t()) :: charlist()
+ def format_host(host) do
+ host_charlist = to_charlist(host)
+
+ case :inet.parse_address(host_charlist) do
+ {:error, :einval} ->
+ :idna.encode(host_charlist)
+
+ {:ok, _ip} ->
+ host_charlist
+ end
+ end
end
diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex
new file mode 100644
index 000000000..e13441316
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper/default.ex
@@ -0,0 +1,14 @@
+defmodule Pleroma.HTTP.AdapterHelper.Default do
+ alias Pleroma.HTTP.AdapterHelper
+
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(opts, _uri) do
+ proxy = Pleroma.Config.get([:http, :proxy_url], nil)
+ AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
+ end
+
+ @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()}
+ def get_conn(_uri, opts), do: {:ok, opts}
+end
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
index ead7cdc6b..b4ff8306c 100644
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.HTTP.AdapterHelper.Gun do
@behaviour Pleroma.HTTP.AdapterHelper
+ alias Pleroma.Gun.ConnectionPool
alias Pleroma.HTTP.AdapterHelper
- alias Pleroma.Pool.Connections
require Logger
@@ -14,7 +14,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
connect_timeout: 5_000,
domain_lookup_timeout: 5_000,
tls_handshake_timeout: 5_000,
- retry: 1,
+ retry: 0,
retry_timeout: 1000,
await_up_timeout: 5_000
]
@@ -31,16 +31,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|> Keyword.merge(config_opts)
|> add_scheme_opts(uri)
|> AdapterHelper.maybe_add_proxy(proxy)
- |> maybe_get_conn(uri, incoming_opts)
- end
-
- @spec after_request(keyword()) :: :ok
- def after_request(opts) do
- if opts[:conn] && opts[:body_as] != :chunks do
- Connections.checkout(opts[:conn], self(), :gun_connections)
- end
-
- :ok
+ |> Keyword.merge(incoming_opts)
end
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
@@ -48,30 +39,40 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
defp add_scheme_opts(opts, %{scheme: "https"}) do
opts
|> Keyword.put(:certificates_verification, true)
- |> Keyword.put(:tls_opts, log_level: :warning)
end
- defp maybe_get_conn(adapter_opts, uri, incoming_opts) do
- {receive_conn?, opts} =
- adapter_opts
- |> Keyword.merge(incoming_opts)
- |> Keyword.pop(:receive_conn, true)
-
- if Connections.alive?(:gun_connections) and receive_conn? do
- checkin_conn(uri, opts)
- else
- opts
+ @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()}
+ def get_conn(uri, opts) do
+ case ConnectionPool.get_conn(uri, opts) do
+ {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)}
+ err -> err
end
end
- defp checkin_conn(uri, opts) do
- case Connections.checkin(uri, :gun_connections) do
- nil ->
- Task.start(Pleroma.Gun.Conn, :open, [uri, :gun_connections, opts])
- opts
+ @prefix Pleroma.Gun.ConnectionPool
+ def limiter_setup do
+ wait = Pleroma.Config.get([:connections_pool, :connection_acquisition_wait])
+ retries = Pleroma.Config.get([:connections_pool, :connection_acquisition_retries])
- conn when is_pid(conn) ->
- Keyword.merge(opts, conn: conn, close_conn: false)
- end
+ :pools
+ |> Pleroma.Config.get([])
+ |> Enum.each(fn {name, opts} ->
+ max_running = Keyword.get(opts, :size, 50)
+ max_waiting = Keyword.get(opts, :max_waiting, 10)
+
+ result =
+ ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting,
+ wait: wait,
+ max_retries: retries
+ )
+
+ case result do
+ :ok -> :ok
+ {:error, :existing} -> :ok
+ e -> raise e
+ end
+ end)
+
+ :ok
end
end
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
index 3972a03a9..cd569422b 100644
--- a/lib/pleroma/http/adapter_helper/hackney.ex
+++ b/lib/pleroma/http/adapter_helper/hackney.ex
@@ -24,5 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
defp add_scheme_opts(opts, _), do: opts
- def after_request(_), do: :ok
+ @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()}
+ def get_conn(_uri, opts), do: {:ok, opts}
end
diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex
deleted file mode 100644
index ebacf7902..000000000
--- a/lib/pleroma/http/connection.ex
+++ /dev/null
@@ -1,124 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.HTTP.Connection do
- @moduledoc """
- Configure Tesla.Client with default and customized adapter options.
- """
-
- alias Pleroma.Config
- alias Pleroma.HTTP.AdapterHelper
-
- require Logger
-
- @defaults [pool: :federation]
-
- @type ip_address :: ipv4_address() | ipv6_address()
- @type ipv4_address :: {0..255, 0..255, 0..255, 0..255}
- @type ipv6_address ::
- {0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535, 0..65_535}
- @type proxy_type() :: :socks4 | :socks5
- @type host() :: charlist() | ip_address()
-
- @doc """
- Merge default connection & adapter options with received ones.
- """
-
- @spec options(URI.t(), keyword()) :: keyword()
- def options(%URI{} = uri, opts \\ []) do
- @defaults
- |> pool_timeout()
- |> Keyword.merge(opts)
- |> adapter_helper().options(uri)
- end
-
- defp pool_timeout(opts) do
- {config_key, default} =
- if adapter() == Tesla.Adapter.Gun do
- {:pools, Config.get([:pools, :default, :timeout])}
- else
- {:hackney_pools, 10_000}
- end
-
- timeout = Config.get([config_key, opts[:pool], :timeout], default)
-
- Keyword.merge(opts, timeout: timeout)
- end
-
- @spec after_request(keyword()) :: :ok
- def after_request(opts), do: adapter_helper().after_request(opts)
-
- defp adapter, do: Application.get_env(:tesla, :adapter)
-
- defp adapter_helper do
- case adapter() do
- Tesla.Adapter.Gun -> AdapterHelper.Gun
- Tesla.Adapter.Hackney -> AdapterHelper.Hackney
- _ -> AdapterHelper
- end
- end
-
- @spec parse_proxy(String.t() | tuple() | nil) ::
- {:ok, host(), pos_integer()}
- | {:ok, proxy_type(), host(), pos_integer()}
- | {:error, atom()}
- | nil
-
- def parse_proxy(nil), do: nil
-
- def parse_proxy(proxy) when is_binary(proxy) do
- with [host, port] <- String.split(proxy, ":"),
- {port, ""} <- Integer.parse(port) do
- {:ok, parse_host(host), port}
- else
- {_, _} ->
- Logger.warn("Parsing port failed #{inspect(proxy)}")
- {:error, :invalid_proxy_port}
-
- :error ->
- Logger.warn("Parsing port failed #{inspect(proxy)}")
- {:error, :invalid_proxy_port}
-
- _ ->
- Logger.warn("Parsing proxy failed #{inspect(proxy)}")
- {:error, :invalid_proxy}
- end
- end
-
- def parse_proxy(proxy) when is_tuple(proxy) do
- with {type, host, port} <- proxy do
- {:ok, type, parse_host(host), port}
- else
- _ ->
- Logger.warn("Parsing proxy failed #{inspect(proxy)}")
- {:error, :invalid_proxy}
- end
- end
-
- @spec parse_host(String.t() | atom() | charlist()) :: charlist() | ip_address()
- def parse_host(host) when is_list(host), do: host
- def parse_host(host) when is_atom(host), do: to_charlist(host)
-
- def parse_host(host) when is_binary(host) do
- host = to_charlist(host)
-
- case :inet.parse_address(host) do
- {:error, :einval} -> host
- {:ok, ip} -> ip
- end
- end
-
- @spec format_host(String.t()) :: charlist()
- def format_host(host) do
- host_charlist = to_charlist(host)
-
- case :inet.parse_address(host_charlist) do
- {:error, :einval} ->
- :idna.encode(host_charlist)
-
- {:ok, _ip} ->
- host_charlist
- end
- end
-end
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index 66ca75367..b37b3fa89 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.HTTP do
Wrapper for `Tesla.request/2`.
"""
- alias Pleroma.HTTP.Connection
+ alias Pleroma.HTTP.AdapterHelper
alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder, as: Builder
alias Tesla.Client
@@ -60,49 +60,30 @@ defmodule Pleroma.HTTP do
{:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)
- adapter_opts = Connection.options(uri, options[:adapter] || [])
- options = put_in(options[:adapter], adapter_opts)
- params = options[:params] || []
- request = build_request(method, headers, options, url, body, params)
-
- adapter = Application.get_env(:tesla, :adapter)
- client = Tesla.client([Tesla.Middleware.FollowRedirects], adapter)
-
- pid = Process.whereis(adapter_opts[:pool])
-
- pool_alive? =
- if adapter == Tesla.Adapter.Gun && pid do
- Process.alive?(pid)
- else
- false
- end
-
- request_opts =
- adapter_opts
- |> Enum.into(%{})
- |> Map.put(:env, Pleroma.Config.get([:env]))
- |> Map.put(:pool_alive?, pool_alive?)
-
- response = request(client, request, request_opts)
-
- Connection.after_request(adapter_opts)
-
- response
- end
-
- @spec request(Client.t(), keyword(), map()) :: {:ok, Env.t()} | {:error, any()}
- def request(%Client{} = client, request, %{env: :test}), do: request(client, request)
-
- def request(%Client{} = client, request, %{body_as: :chunks}), do: request(client, request)
-
- def request(%Client{} = client, request, %{pool_alive?: false}), do: request(client, request)
-
- def request(%Client{} = client, request, %{pool: pool, timeout: timeout}) do
- :poolboy.transaction(
- pool,
- &Pleroma.Pool.Request.execute(&1, client, request, timeout),
- timeout
- )
+ adapter_opts = AdapterHelper.options(uri, options[:adapter] || [])
+
+ case AdapterHelper.get_conn(uri, adapter_opts) do
+ {:ok, adapter_opts} ->
+ options = put_in(options[:adapter], adapter_opts)
+ params = options[:params] || []
+ request = build_request(method, headers, options, url, body, params)
+
+ adapter = Application.get_env(:tesla, :adapter)
+
+ client = Tesla.client(adapter_middlewares(adapter), adapter)
+
+ maybe_limit(
+ fn ->
+ request(client, request)
+ end,
+ adapter,
+ adapter_opts
+ )
+
+ # Connection release is handled in a custom FollowRedirects middleware
+ err ->
+ err
+ end
end
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
@@ -118,4 +99,19 @@ defmodule Pleroma.HTTP do
|> Builder.add_param(:query, :query, params)
|> Builder.convert_to_keyword()
end
+
+ @prefix Pleroma.Gun.ConnectionPool
+ defp maybe_limit(fun, Tesla.Adapter.Gun, opts) do
+ ConcurrentLimiter.limit(:"#{@prefix}.#{opts[:pool] || :default}", fun)
+ end
+
+ defp maybe_limit(fun, _, _) do
+ fun.()
+ end
+
+ defp adapter_middlewares(Tesla.Adapter.Gun) do
+ [Pleroma.HTTP.Middleware.FollowRedirects]
+ end
+
+ defp adapter_middlewares(_), do: []
end
diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex
index 2fc876d92..8a44a001d 100644
--- a/lib/pleroma/http/request_builder.ex
+++ b/lib/pleroma/http/request_builder.ex
@@ -34,10 +34,12 @@ defmodule Pleroma.HTTP.RequestBuilder do
@spec headers(Request.t(), Request.headers()) :: Request.t()
def headers(request, headers) do
headers_list =
- if Pleroma.Config.get([:http, :send_user_agent]) do
+ with true <- Pleroma.Config.get([:http, :send_user_agent]),
+ nil <- Enum.find(headers, fn {key, _val} -> String.downcase(key) == "user-agent" end) do
[{"user-agent", Pleroma.Application.user_agent()} | headers]
else
- headers
+ _ ->
+ headers
end
%{request | headers: headers_list}
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
index 7aacd9d80..31c9afe2a 100644
--- a/lib/pleroma/moderation_log.ex
+++ b/lib/pleroma/moderation_log.ex
@@ -413,6 +413,17 @@ defmodule Pleroma.ModerationLog do
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
+ "action" => "approve",
+ "subject" => users
+ }
+ }) do
+ "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
+ end
+
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
"nicknames" => nicknames,
"tags" => tags,
"action" => "tag"
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 32bcfcaba..0b171563b 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -571,10 +571,7 @@ defmodule Pleroma.Notification do
[
:self,
:invisible,
- :followers,
- :follows,
- :non_followers,
- :non_follows,
+ :block_from_strangers,
:recently_followed,
:filtered
]
@@ -595,45 +592,15 @@ defmodule Pleroma.Notification do
end
def skip?(
- :followers,
+ :block_from_strangers,
%Activity{} = activity,
- %User{notification_settings: %{followers: false}} = user
- ) do
- actor = activity.data["actor"]
- follower = User.get_cached_by_ap_id(actor)
- User.following?(follower, user)
- end
-
- def skip?(
- :non_followers,
- %Activity{} = activity,
- %User{notification_settings: %{non_followers: false}} = user
+ %User{notification_settings: %{block_from_strangers: true}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
end
- def skip?(
- :follows,
- %Activity{} = activity,
- %User{notification_settings: %{follows: false}} = user
- ) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- User.following?(user, followed)
- end
-
- def skip?(
- :non_follows,
- %Activity{} = activity,
- %User{notification_settings: %{non_follows: false}} = user
- ) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- !User.following?(user, followed)
- end
-
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
actor = activity.data["actor"]
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 3e2949ee2..e74c87269 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -124,6 +124,10 @@ defmodule Pleroma.Object.Fetcher do
{:error, "Object has been deleted"} ->
nil
+ {:reject, reason} ->
+ Logger.info("Rejected #{id} while fetching: #{inspect(reason)}")
+ nil
+
e ->
Logger.error("Error while fetching #{id}: #{inspect(e)}")
nil
diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
index b4b47a31f..2e54df47a 100644
--- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex
+++ b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
@@ -4,6 +4,9 @@
defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
import Plug.Conn
+
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
def init(options) do
@@ -11,7 +14,10 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
end
def secret_token do
- Pleroma.Config.get(:admin_token)
+ case Pleroma.Config.get(:admin_token) do
+ blank when blank in [nil, ""] -> nil
+ token -> token
+ end
end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
@@ -26,9 +32,9 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
if admin_token == secret_token() do
- assign(conn, :user, %User{is_admin: true})
+ assign_admin_user(conn)
else
- conn
+ handle_bad_token(conn)
end
end
@@ -36,8 +42,19 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
token = secret_token()
case get_req_header(conn, "x-admin-token") do
- [^token] -> assign(conn, :user, %User{is_admin: true})
- _ -> conn
+ blank when blank in [[], [""]] -> conn
+ [^token] -> assign_admin_user(conn)
+ _ -> handle_bad_token(conn)
end
end
+
+ defp assign_admin_user(conn) do
+ conn
+ |> assign(:user, %User{is_admin: true})
+ |> OAuthScopesPlug.skip_plug()
+ end
+
+ defp handle_bad_token(conn) do
+ RateLimiter.call(conn, name: :authentication)
+ end
end
diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/plugs/frontend_static.ex
new file mode 100644
index 000000000..f549ca75f
--- /dev/null
+++ b/lib/pleroma/plugs/frontend_static.ex
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.FrontendStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
+ """
+ @behaviour Plug
+
+ def file_path(path, frontend_type \\ :primary) do
+ if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+
+ Path.join([
+ instance_static_path,
+ "frontends",
+ configuration["name"],
+ configuration["ref"],
+ path
+ ])
+ else
+ nil
+ end
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
+ |> Plug.Static.init()
+ end
+
+ def call(conn, opts) do
+ frontend_type = Map.get(opts, :frontend_type, :primary)
+ path = file_path("", frontend_type)
+
+ if path do
+ conn
+ |> call_static(opts, path)
+ else
+ conn
+ end
+ end
+
+ defp call_static(conn, opts, from) do
+ opts =
+ opts
+ |> Map.put(:from, from)
+
+ Plug.Static.call(conn, opts)
+ end
+end
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 7d65cf078..c363b193b 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -108,31 +108,48 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|> :erlang.iolist_to_binary()
end
- defp build_csp_multimedia_source_list do
- media_proxy_whitelist =
- Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
- add_source(acc, host)
- end)
+ defp build_csp_from_whitelist([], acc), do: acc
- media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
+ defp build_csp_from_whitelist([last], acc) do
+ [build_csp_param_from_whitelist(last) | acc]
+ end
- upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
+ defp build_csp_from_whitelist([head | tail], acc) do
+ build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
+ end
- s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
+ # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
+ defp build_csp_param_from_whitelist("http" <> _ = url) do
+ build_csp_param(url)
+ end
- captcha_method = Config.get([Pleroma.Captcha, :method])
+ defp build_csp_param_from_whitelist(url), do: url
- captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))
+ defp build_csp_multimedia_source_list do
+ media_proxy_whitelist =
+ [:media_proxy, :whitelist]
+ |> Config.get()
+ |> build_csp_from_whitelist([])
- []
- |> add_source(media_proxy_base_url)
- |> add_source(upload_base_url)
- |> add_source(s3_endpoint)
+ captcha_method = Config.get([Pleroma.Captcha, :method])
+ captcha_endpoint = Config.get([captcha_method, :endpoint])
+
+ base_endpoints =
+ [
+ [:media_proxy, :base_url],
+ [Pleroma.Upload, :base_url],
+ [Pleroma.Uploaders.S3, :public_endpoint]
+ ]
+ |> Enum.map(&Config.get/1)
+
+ [captcha_endpoint | base_endpoints]
+ |> Enum.map(&build_csp_param/1)
+ |> Enum.reduce([], &add_source(&2, &1))
|> add_source(media_proxy_whitelist)
- |> add_source(captcha_endpoint)
end
defp add_source(iodata, nil), do: iodata
+ defp add_source(iodata, []), do: iodata
defp add_source(iodata, source), do: [[?\s, source] | iodata]
defp add_csp_param(csp_iodata, nil), do: csp_iodata
diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex
index 7516f75c3..0fb57e422 100644
--- a/lib/pleroma/plugs/instance_static.ex
+++ b/lib/pleroma/plugs/instance_static.ex
@@ -16,28 +16,24 @@ defmodule Pleroma.Plugs.InstanceStatic do
instance_path =
Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
- if File.exists?(instance_path) do
- instance_path
- else
+ frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary)
+
+ (File.exists?(instance_path) && instance_path) ||
+ (frontend_path && File.exists?(frontend_path) && frontend_path) ||
Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
- end
end
def init(opts) do
opts
|> Keyword.put(:from, "__unconfigured_instance_static_plug")
- |> Keyword.put(:at, "/__unconfigured_instance_static_plug")
|> Plug.Static.init()
end
for only <- Pleroma.Constants.static_only_files() do
- at = Plug.Router.Utils.split("/")
-
def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
call_static(
conn,
opts,
- unquote(at),
Pleroma.Config.get([:instance, :static_dir], "instance/static")
)
end
@@ -47,11 +43,10 @@ defmodule Pleroma.Plugs.InstanceStatic do
conn
end
- defp call_static(conn, opts, at, from) do
+ defp call_static(conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
- |> Map.put(:at, at)
Plug.Static.call(conn, opts)
end
diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex
index 2748102df..488a61d1d 100644
--- a/lib/pleroma/plugs/user_is_admin_plug.ex
+++ b/lib/pleroma/plugs/user_is_admin_plug.ex
@@ -7,37 +7,18 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
import Plug.Conn
alias Pleroma.User
- alias Pleroma.Web.OAuth
def init(options) do
options
end
- def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do
- token = assigns[:token]
-
- cond do
- not Pleroma.Config.enforce_oauth_admin_scope_usage?() ->
- conn
-
- token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
- # Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
- # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
- # Admin might opt out of admin scope for some apps to block any admin actions from them.
- conn
-
- true ->
- fail(conn)
- end
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
+ conn
end
def call(conn, _) do
- fail(conn)
- end
-
- defp fail(conn) do
conn
- |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.")
+ |> render_error(:forbidden, "User is not an admin.")
|> halt()
end
end
diff --git a/lib/pleroma/pool/connections.ex b/lib/pleroma/pool/connections.ex
deleted file mode 100644
index acafe1bea..000000000
--- a/lib/pleroma/pool/connections.ex
+++ /dev/null
@@ -1,283 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Pool.Connections do
- use GenServer
-
- alias Pleroma.Config
- alias Pleroma.Gun
-
- require Logger
-
- @type domain :: String.t()
- @type conn :: Pleroma.Gun.Conn.t()
-
- @type t :: %__MODULE__{
- conns: %{domain() => conn()},
- opts: keyword()
- }
-
- defstruct conns: %{}, opts: []
-
- @spec start_link({atom(), keyword()}) :: {:ok, pid()}
- def start_link({name, opts}) do
- GenServer.start_link(__MODULE__, opts, name: name)
- end
-
- @impl true
- def init(opts), do: {:ok, %__MODULE__{conns: %{}, opts: opts}}
-
- @spec checkin(String.t() | URI.t(), atom()) :: pid() | nil
- def checkin(url, name)
- def checkin(url, name) when is_binary(url), do: checkin(URI.parse(url), name)
-
- def checkin(%URI{} = uri, name) do
- timeout = Config.get([:connections_pool, :checkin_timeout], 250)
-
- GenServer.call(name, {:checkin, uri}, timeout)
- end
-
- @spec alive?(atom()) :: boolean()
- def alive?(name) do
- if pid = Process.whereis(name) do
- Process.alive?(pid)
- else
- false
- end
- end
-
- @spec get_state(atom()) :: t()
- def get_state(name) do
- GenServer.call(name, :state)
- end
-
- @spec count(atom()) :: pos_integer()
- def count(name) do
- GenServer.call(name, :count)
- end
-
- @spec get_unused_conns(atom()) :: [{domain(), conn()}]
- def get_unused_conns(name) do
- GenServer.call(name, :unused_conns)
- end
-
- @spec checkout(pid(), pid(), atom()) :: :ok
- def checkout(conn, pid, name) do
- GenServer.cast(name, {:checkout, conn, pid})
- end
-
- @spec add_conn(atom(), String.t(), Pleroma.Gun.Conn.t()) :: :ok
- def add_conn(name, key, conn) do
- GenServer.cast(name, {:add_conn, key, conn})
- end
-
- @spec remove_conn(atom(), String.t()) :: :ok
- def remove_conn(name, key) do
- GenServer.cast(name, {:remove_conn, key})
- end
-
- @impl true
- def handle_cast({:add_conn, key, conn}, state) do
- state = put_in(state.conns[key], conn)
-
- Process.monitor(conn.conn)
- {:noreply, state}
- end
-
- @impl true
- def handle_cast({:checkout, conn_pid, pid}, state) do
- state =
- with true <- Process.alive?(conn_pid),
- {key, conn} <- find_conn(state.conns, conn_pid),
- used_by <- List.keydelete(conn.used_by, pid, 0) do
- conn_state = if used_by == [], do: :idle, else: conn.conn_state
-
- put_in(state.conns[key], %{conn | conn_state: conn_state, used_by: used_by})
- else
- false ->
- Logger.debug("checkout for closed conn #{inspect(conn_pid)}")
- state
-
- nil ->
- Logger.debug("checkout for alive conn #{inspect(conn_pid)}, but is not in state")
- state
- end
-
- {:noreply, state}
- end
-
- @impl true
- def handle_cast({:remove_conn, key}, state) do
- state = put_in(state.conns, Map.delete(state.conns, key))
- {:noreply, state}
- end
-
- @impl true
- def handle_call({:checkin, uri}, from, state) do
- key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
-
- case state.conns[key] do
- %{conn: pid, gun_state: :up} = conn ->
- time = :os.system_time(:second)
- last_reference = time - conn.last_reference
- crf = crf(last_reference, 100, conn.crf)
-
- state =
- put_in(state.conns[key], %{
- conn
- | last_reference: time,
- crf: crf,
- conn_state: :active,
- used_by: [from | conn.used_by]
- })
-
- {:reply, pid, state}
-
- %{gun_state: :down} ->
- {:reply, nil, state}
-
- nil ->
- {:reply, nil, state}
- end
- end
-
- @impl true
- def handle_call(:state, _from, state), do: {:reply, state, state}
-
- @impl true
- def handle_call(:count, _from, state) do
- {:reply, Enum.count(state.conns), state}
- end
-
- @impl true
- def handle_call(:unused_conns, _from, state) do
- unused_conns =
- state.conns
- |> Enum.filter(&filter_conns/1)
- |> Enum.sort(&sort_conns/2)
-
- {:reply, unused_conns, state}
- end
-
- defp filter_conns({_, %{conn_state: :idle, used_by: []}}), do: true
- defp filter_conns(_), do: false
-
- defp sort_conns({_, c1}, {_, c2}) do
- c1.crf <= c2.crf and c1.last_reference <= c2.last_reference
- end
-
- @impl true
- def handle_info({:gun_up, conn_pid, _protocol}, state) do
- %{origin_host: host, origin_scheme: scheme, origin_port: port} = Gun.info(conn_pid)
-
- host =
- case :inet.ntoa(host) do
- {:error, :einval} -> host
- ip -> ip
- end
-
- key = "#{scheme}:#{host}:#{port}"
-
- state =
- with {key, conn} <- find_conn(state.conns, conn_pid, key),
- {true, key} <- {Process.alive?(conn_pid), key} do
- put_in(state.conns[key], %{
- conn
- | gun_state: :up,
- conn_state: :active,
- retries: 0
- })
- else
- {false, key} ->
- put_in(
- state.conns,
- Map.delete(state.conns, key)
- )
-
- nil ->
- :ok = Gun.close(conn_pid)
-
- state
- end
-
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed}, state) do
- retries = Config.get([:connections_pool, :retry], 1)
- # we can't get info on this pid, because pid is dead
- state =
- with {key, conn} <- find_conn(state.conns, conn_pid),
- {true, key} <- {Process.alive?(conn_pid), key} do
- if conn.retries == retries do
- :ok = Gun.close(conn.conn)
-
- put_in(
- state.conns,
- Map.delete(state.conns, key)
- )
- else
- put_in(state.conns[key], %{
- conn
- | gun_state: :down,
- retries: conn.retries + 1
- })
- end
- else
- {false, key} ->
- put_in(
- state.conns,
- Map.delete(state.conns, key)
- )
-
- nil ->
- Logger.debug(":gun_down for conn which isn't found in state")
-
- state
- end
-
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
- Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
-
- state =
- with {key, conn} <- find_conn(state.conns, conn_pid) do
- Enum.each(conn.used_by, fn {pid, _ref} ->
- Process.exit(pid, reason)
- end)
-
- put_in(
- state.conns,
- Map.delete(state.conns, key)
- )
- else
- nil ->
- Logger.debug(":DOWN for conn which isn't found in state")
-
- state
- end
-
- {:noreply, state}
- end
-
- defp find_conn(conns, conn_pid) do
- Enum.find(conns, fn {_key, conn} ->
- conn.conn == conn_pid
- end)
- end
-
- defp find_conn(conns, conn_pid, conn_key) do
- Enum.find(conns, fn {key, conn} ->
- key == conn_key and conn.conn == conn_pid
- end)
- end
-
- def crf(current, steps, crf) do
- 1 + :math.pow(0.5, current / steps) * crf
- end
-end
diff --git a/lib/pleroma/pool/pool.ex b/lib/pleroma/pool/pool.ex
deleted file mode 100644
index 21a6fbbc5..000000000
--- a/lib/pleroma/pool/pool.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Pool do
- def child_spec(opts) do
- poolboy_opts =
- opts
- |> Keyword.put(:worker_module, Pleroma.Pool.Request)
- |> Keyword.put(:name, {:local, opts[:name]})
- |> Keyword.put(:size, opts[:size])
- |> Keyword.put(:max_overflow, opts[:max_overflow])
-
- %{
- id: opts[:id] || {__MODULE__, make_ref()},
- start: {:poolboy, :start_link, [poolboy_opts, [name: opts[:name]]]},
- restart: :permanent,
- shutdown: 5000,
- type: :worker
- }
- end
-end
diff --git a/lib/pleroma/pool/request.ex b/lib/pleroma/pool/request.ex
deleted file mode 100644
index 3fb930db7..000000000
--- a/lib/pleroma/pool/request.ex
+++ /dev/null
@@ -1,65 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Pool.Request do
- use GenServer
-
- require Logger
-
- def start_link(args) do
- GenServer.start_link(__MODULE__, args)
- end
-
- @impl true
- def init(_), do: {:ok, []}
-
- @spec execute(pid() | atom(), Tesla.Client.t(), keyword(), pos_integer()) ::
- {:ok, Tesla.Env.t()} | {:error, any()}
- def execute(pid, client, request, timeout) do
- GenServer.call(pid, {:execute, client, request}, timeout)
- end
-
- @impl true
- def handle_call({:execute, client, request}, _from, state) do
- response = Pleroma.HTTP.request(client, request)
-
- {:reply, response, state}
- end
-
- @impl true
- def handle_info({:gun_data, _conn, _stream, _, _}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_up, _conn, _protocol}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_down, _conn, _protocol, _reason, _killed}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_error, _conn, _stream, _error}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_push, _conn, _stream, _new_stream, _method, _uri, _headers}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info({:gun_response, _conn, _stream, _, _status, _headers}, state) do
- {:noreply, state}
- end
-
- @impl true
- def handle_info(msg, state) do
- Logger.warn("Received unexpected message #{inspect(__MODULE__)} #{inspect(msg)}")
- {:noreply, state}
- end
-end
diff --git a/lib/pleroma/pool/supervisor.ex b/lib/pleroma/pool/supervisor.ex
deleted file mode 100644
index faf646cb2..000000000
--- a/lib/pleroma/pool/supervisor.ex
+++ /dev/null
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Pool.Supervisor do
- use Supervisor
-
- alias Pleroma.Config
- alias Pleroma.Pool
-
- def start_link(args) do
- Supervisor.start_link(__MODULE__, args, name: __MODULE__)
- end
-
- def init(_) do
- conns_child = %{
- id: Pool.Connections,
- start:
- {Pool.Connections, :start_link, [{:gun_connections, Config.get([:connections_pool])}]}
- }
-
- Supervisor.init([conns_child | pools()], strategy: :one_for_one)
- end
-
- defp pools do
- pools = Config.get(:pools)
-
- pools =
- if Config.get([Pleroma.Upload, :proxy_remote]) == false do
- Keyword.delete(pools, :upload)
- else
- pools
- end
-
- for {pool_name, pool_opts} <- pools do
- pool_opts
- |> Keyword.put(:id, {Pool, pool_name})
- |> Keyword.put(:name, pool_name)
- |> Pool.child_spec()
- end
- end
-end
diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex
index e81ea8bde..d5a339681 100644
--- a/lib/pleroma/reverse_proxy/client/tesla.ex
+++ b/lib/pleroma/reverse_proxy/client/tesla.ex
@@ -5,6 +5,8 @@
defmodule Pleroma.ReverseProxy.Client.Tesla do
@behaviour Pleroma.ReverseProxy.Client
+ alias Pleroma.Gun.ConnectionPool
+
@type headers() :: [{String.t(), String.t()}]
@type status() :: pos_integer()
@@ -31,6 +33,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
if is_map(response.body) and method != :head do
{:ok, response.status, response.headers, response.body}
else
+ conn_pid = response.opts[:adapter][:conn]
+ ConnectionPool.release_conn(conn_pid)
{:ok, response.status, response.headers}
end
else
@@ -41,15 +45,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
@impl true
@spec stream_body(map()) ::
{:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return()
- def stream_body(%{pid: pid, opts: opts, fin: true}) do
- # if connection was reused, but in tesla were redirects,
- # tesla returns new opened connection, which must be closed manually
- if opts[:old_conn], do: Tesla.Adapter.Gun.close(pid)
- # if there were redirects we need to checkout old conn
- conn = opts[:old_conn] || opts[:conn]
-
- if conn, do: :ok = Pleroma.Pool.Connections.checkout(conn, self(), :gun_connections)
-
+ def stream_body(%{pid: pid, fin: true}) do
+ ConnectionPool.release_conn(pid)
:done
end
@@ -74,8 +71,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do
@impl true
@spec close(map) :: :ok | no_return()
def close(%{pid: pid}) do
- adapter = check_adapter()
- adapter.close(pid)
+ ConnectionPool.release_conn(pid)
end
defp check_adapter do
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 28ad4c846..0de4e2309 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -165,6 +165,9 @@ defmodule Pleroma.ReverseProxy do
{:ok, code, _, _} ->
{:error, {:invalid_http_response, code}}
+ {:ok, code, _} ->
+ {:error, {:invalid_http_response, code}}
+
{:error, error} ->
{:error, error}
end
diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex
new file mode 100644
index 000000000..4cacae02f
--- /dev/null
+++ b/lib/pleroma/telemetry/logger.ex
@@ -0,0 +1,76 @@
+defmodule Pleroma.Telemetry.Logger do
+ @moduledoc "Transforms Pleroma telemetry events to logs"
+
+ require Logger
+
+ @events [
+ [:pleroma, :connection_pool, :reclaim, :start],
+ [:pleroma, :connection_pool, :reclaim, :stop],
+ [:pleroma, :connection_pool, :provision_failure],
+ [:pleroma, :connection_pool, :client_death]
+ ]
+ def attach do
+ :telemetry.attach_many("pleroma-logger", @events, &handle_event/4, [])
+ end
+
+ # Passing anonymous functions instead of strings to logger is intentional,
+ # that way strings won't be concatenated if the message is going to be thrown
+ # out anyway due to higher log level configured
+
+ def handle_event(
+ [:pleroma, :connection_pool, :reclaim, :start],
+ _,
+ %{max_connections: max_connections, reclaim_max: reclaim_max},
+ _
+ ) do
+ Logger.debug(fn ->
+ "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
+ reclaim_max
+ } connections"
+ end)
+ end
+
+ def handle_event(
+ [:pleroma, :connection_pool, :reclaim, :stop],
+ %{reclaimed_count: 0},
+ _,
+ _
+ ) do
+ Logger.error(fn ->
+ "Connection pool failed to reclaim any connections due to all of them being in use. It will have to drop requests for opening connections to new hosts"
+ end)
+ end
+
+ def handle_event(
+ [:pleroma, :connection_pool, :reclaim, :stop],
+ %{reclaimed_count: reclaimed_count},
+ _,
+ _
+ ) do
+ Logger.debug(fn -> "Connection pool cleaned up #{reclaimed_count} idle connections" end)
+ end
+
+ def handle_event(
+ [:pleroma, :connection_pool, :provision_failure],
+ %{opts: [key | _]},
+ _,
+ _
+ ) do
+ Logger.error(fn ->
+ "Connection pool had to refuse opening a connection to #{key} due to connection limit exhaustion"
+ end)
+ end
+
+ def handle_event(
+ [:pleroma, :connection_pool, :client_death],
+ %{client_pid: client_pid, reason: reason},
+ %{key: key},
+ _
+ ) do
+ Logger.warn(fn ->
+ "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
+ inspect(reason)
+ }"
+ end)
+ end
+end
diff --git a/lib/pleroma/tesla/middleware/follow_redirects.ex b/lib/pleroma/tesla/middleware/follow_redirects.ex
new file mode 100644
index 000000000..5a7032215
--- /dev/null
+++ b/lib/pleroma/tesla/middleware/follow_redirects.ex
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2015-2020 Tymon Tobolski <https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex>
+# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Middleware.FollowRedirects do
+ @moduledoc """
+ Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex
+
+ Follow 3xx redirects
+ ## Options
+ - `:max_redirects` - limit number of redirects (default: `5`)
+ """
+
+ alias Pleroma.Gun.ConnectionPool
+
+ @behaviour Tesla.Middleware
+
+ @max_redirects 5
+ @redirect_statuses [301, 302, 303, 307, 308]
+
+ @impl Tesla.Middleware
+ def call(env, next, opts \\ []) do
+ max = Keyword.get(opts, :max_redirects, @max_redirects)
+
+ redirect(env, next, max)
+ end
+
+ defp redirect(env, next, left) do
+ opts = env.opts[:adapter]
+
+ case Tesla.run(env, next) do
+ {:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 ->
+ release_conn(opts)
+
+ case Tesla.get_header(res, "location") do
+ nil ->
+ {:ok, res}
+
+ location ->
+ location = parse_location(location, res)
+
+ case get_conn(location, opts) do
+ {:ok, opts} ->
+ %{env | opts: Keyword.put(env.opts, :adapter, opts)}
+ |> new_request(res.status, location)
+ |> redirect(next, left - 1)
+
+ e ->
+ e
+ end
+ end
+
+ {:ok, %{status: status}} when status in @redirect_statuses ->
+ release_conn(opts)
+ {:error, {__MODULE__, :too_many_redirects}}
+
+ {:error, _} = e ->
+ release_conn(opts)
+ e
+
+ other ->
+ unless opts[:body_as] == :chunks do
+ release_conn(opts)
+ end
+
+ other
+ end
+ end
+
+ defp get_conn(location, opts) do
+ uri = URI.parse(location)
+
+ case ConnectionPool.get_conn(uri, opts) do
+ {:ok, conn} ->
+ {:ok, Keyword.merge(opts, conn: conn)}
+
+ e ->
+ e
+ end
+ end
+
+ defp release_conn(opts) do
+ ConnectionPool.release_conn(opts[:conn])
+ end
+
+ # The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally
+ # requested resource is not available, however a related resource (or another redirect)
+ # available via GET is available at the specified location.
+ # https://tools.ietf.org/html/rfc7231#section-6.4.4
+ defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []}
+
+ # The 307 (Temporary Redirect) status code indicates that the target
+ # resource resides temporarily under a different URI and the user agent
+ # MUST NOT change the request method (...)
+ # https://tools.ietf.org/html/rfc7231#section-6.4.7
+ defp new_request(env, 307, location), do: %{env | url: location}
+
+ defp new_request(env, _, location), do: %{env | url: location, query: []}
+
+ defp parse_location("https://" <> _rest = location, _env), do: location
+ defp parse_location("http://" <> _rest = location, _env), do: location
+
+ defp parse_location(location, env) do
+ env.url
+ |> URI.parse()
+ |> URI.merge(location)
+ |> URI.to_string()
+ end
+end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index b9989f901..09e606b37 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -42,7 +42,12 @@ defmodule Pleroma.User do
require Logger
@type t :: %__MODULE__{}
- @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
+ @type account_status ::
+ :active
+ | :deactivated
+ | :password_reset_pending
+ | :confirmation_pending
+ | :approval_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@@ -106,6 +111,8 @@ defmodule Pleroma.User do
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false)
+ field(:approval_pending, :boolean, default: false)
+ field(:registration_reason, :string, default: nil)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:domain_blocks, {:array, :string}, default: [])
@@ -262,6 +269,7 @@ defmodule Pleroma.User do
@spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
+ def account_status(%User{approval_pending: true}), do: :approval_pending
def account_status(%User{confirmation_pending: true}) do
if Config.get([:instance, :account_activation_required]) do
@@ -530,11 +538,21 @@ defmodule Pleroma.User do
end
defp put_emoji(changeset) do
- bio = get_change(changeset, :bio)
- name = get_change(changeset, :name)
+ emojified_fields = [:bio, :name, :raw_fields]
+
+ if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
+ bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
+ name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
+
+ emoji = Map.merge(bio, name)
+
+ emoji =
+ changeset
+ |> get_field(:raw_fields)
+ |> Enum.reduce(emoji, fn x, acc ->
+ Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
+ end)
- if bio || name do
- emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
put_change(changeset, :emoji, emoji)
else
changeset
@@ -623,6 +641,7 @@ defmodule Pleroma.User do
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
+ reason_limit = Config.get([:instance, :registration_reason_length], 500)
params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? =
@@ -632,8 +651,16 @@ defmodule Pleroma.User do
opts[:need_confirmation]
end
+ need_approval? =
+ if is_nil(opts[:need_approval]) do
+ Config.get([:instance, :account_approval_required])
+ else
+ opts[:need_approval]
+ end
+
struct
|> confirmation_changeset(need_confirmation: need_confirmation?)
+ |> approval_changeset(need_approval: need_approval?)
|> cast(params, [
:bio,
:raw_bio,
@@ -643,17 +670,28 @@ defmodule Pleroma.User do
:password,
:password_confirmation,
:emoji,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :registration_reason
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
+ |> validate_format(:email, @email_regex)
+ |> validate_change(:email, fn :email, email ->
+ valid? =
+ Config.get([User, :email_blacklist])
+ |> Enum.all?(fn blacklisted_domain ->
+ !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
+ end)
+
+ if valid?, do: [], else: [email: "Invalid email"]
+ end)
|> unique_constraint(:nickname)
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
- |> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
+ |> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
|> put_password_hash
|> put_ap_id()
@@ -703,27 +741,62 @@ defmodule Pleroma.User do
def post_register_action(%User{} = user) do
with {:ok, user} <- autofollow_users(user),
{:ok, user} <- set_cache(user),
- {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
+ {:ok, _} <- send_welcome_email(user),
+ {:ok, _} <- send_welcome_message(user),
+ {:ok, _} <- send_welcome_chat_message(user),
{:ok, _} <- try_send_confirmation_email(user) do
{:ok, user}
end
end
- def try_send_confirmation_email(%User{} = user) do
- if user.confirmation_pending &&
- Config.get([:instance, :account_activation_required]) do
- user
- |> Pleroma.Emails.UserEmail.account_confirmation_email()
- |> Pleroma.Emails.Mailer.deliver_async()
+ def send_welcome_message(user) do
+ if User.WelcomeMessage.enabled?() do
+ User.WelcomeMessage.post_message(user)
+ {:ok, :enqueued}
+ else
+ {:ok, :noop}
+ end
+ end
+
+ def send_welcome_chat_message(user) do
+ if User.WelcomeChatMessage.enabled?() do
+ User.WelcomeChatMessage.post_message(user)
+ {:ok, :enqueued}
+ else
+ {:ok, :noop}
+ end
+ end
+
+ def send_welcome_email(%User{email: email} = user) when is_binary(email) do
+ if User.WelcomeEmail.enabled?() do
+ User.WelcomeEmail.send_email(user)
+ {:ok, :enqueued}
+ else
+ {:ok, :noop}
+ end
+ end
+
+ def send_welcome_email(_), do: {:ok, :noop}
+ @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
+ def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
+ if Config.get([:instance, :account_activation_required]) do
+ send_confirmation_email(user)
{:ok, :enqueued}
else
{:ok, :noop}
end
end
- def try_send_confirmation_email(users) do
- Enum.each(users, &try_send_confirmation_email/1)
+ def try_send_confirmation_email(_), do: {:ok, :noop}
+
+ @spec send_confirmation_email(Uset.t()) :: User.t()
+ def send_confirmation_email(%User{} = user) do
+ user
+ |> Pleroma.Emails.UserEmail.account_confirmation_email()
+ |> Pleroma.Emails.Mailer.deliver_async()
+
+ user
end
def needs_update?(%User{local: true}), do: false
@@ -1459,6 +1532,19 @@ defmodule Pleroma.User do
end
end
+ def approve(users) when is_list(users) do
+ Repo.transaction(fn ->
+ Enum.map(users, fn user ->
+ with {:ok, user} <- approve(user), do: user
+ end)
+ end)
+ end
+
+ def approve(%User{} = user) do
+ change(user, approval_pending: false)
+ |> update_and_set_cache()
+ end
+
def update_notification_settings(%User{} = user, settings) do
user
|> cast(%{notification_settings: settings}, [])
@@ -1485,12 +1571,17 @@ defmodule Pleroma.User do
defp delete_or_deactivate(%User{local: true} = user) do
status = account_status(user)
- if status == :confirmation_pending do
- delete_and_invalidate_cache(user)
- else
- user
- |> change(%{deactivated: true, email: nil})
- |> update_and_set_cache()
+ case status do
+ :confirmation_pending ->
+ delete_and_invalidate_cache(user)
+
+ :approval_pending ->
+ delete_and_invalidate_cache(user)
+
+ _ ->
+ user
+ |> change(%{deactivated: true, email: nil})
+ |> update_and_set_cache()
end
end
@@ -2143,6 +2234,12 @@ defmodule Pleroma.User do
cast(user, params, [:confirmation_pending, :confirmation_token])
end
+ @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
+ def approval_changeset(user, need_approval: need_approval?) do
+ params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
+ cast(user, params, [:approval_pending])
+ end
+
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
index 4bd55e139..7d9e8a000 100644
--- a/lib/pleroma/user/notification_setting.ex
+++ b/lib/pleroma/user/notification_setting.ex
@@ -10,21 +10,15 @@ defmodule Pleroma.User.NotificationSetting do
@primary_key false
embedded_schema do
- field(:followers, :boolean, default: true)
- field(:follows, :boolean, default: true)
- field(:non_follows, :boolean, default: true)
- field(:non_followers, :boolean, default: true)
- field(:privacy_option, :boolean, default: false)
+ field(:block_from_strangers, :boolean, default: false)
+ field(:hide_notification_contents, :boolean, default: false)
end
def changeset(schema, params) do
schema
|> cast(prepare_attrs(params), [
- :followers,
- :follows,
- :non_follows,
- :non_followers,
- :privacy_option
+ :block_from_strangers,
+ :hide_notification_contents
])
end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 66ffe9090..45553cb6c 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do
external: boolean(),
active: boolean(),
deactivated: boolean(),
+ need_approval: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
@@ -146,6 +147,10 @@ defmodule Pleroma.User.Query do
|> where([u], not is_nil(u.nickname))
end
+ defp compose_query({:need_approval, _}, query) do
+ where(query, [u], u.approval_pending)
+ end
+
defp compose_query({:followers, %User{id: id}}, query) do
query
|> where([u], u.id != ^id)
diff --git a/lib/pleroma/user/welcome_chat_message.ex b/lib/pleroma/user/welcome_chat_message.ex
new file mode 100644
index 000000000..3e7d1f424
--- /dev/null
+++ b/lib/pleroma/user/welcome_chat_message.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.WelcomeChatMessage do
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:welcome, :chat_message, :enabled], false)
+
+ @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil}
+ def post_message(user) do
+ [:welcome, :chat_message, :sender_nickname]
+ |> Config.get(nil)
+ |> fetch_sender()
+ |> do_post(user, welcome_message())
+ end
+
+ defp do_post(%User{} = sender, recipient, message)
+ when is_binary(message) do
+ CommonAPI.post_chat_message(
+ sender,
+ recipient,
+ message
+ )
+ end
+
+ defp do_post(_sender, _recipient, _message), do: {:ok, nil}
+
+ defp fetch_sender(nickname) when is_binary(nickname) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ user
+ else
+ _ -> nil
+ end
+ end
+
+ defp fetch_sender(_), do: nil
+
+ defp welcome_message do
+ Config.get([:welcome, :chat_message, :message], nil)
+ end
+end
diff --git a/lib/pleroma/user/welcome_email.ex b/lib/pleroma/user/welcome_email.ex
new file mode 100644
index 000000000..5322000d4
--- /dev/null
+++ b/lib/pleroma/user/welcome_email.ex
@@ -0,0 +1,62 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.WelcomeEmail do
+ @moduledoc """
+ The module represents the functions to send welcome email.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.Emails
+ alias Pleroma.User
+
+ import Pleroma.Config.Helpers, only: [instance_name: 0]
+
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:welcome, :email, :enabled], false)
+
+ @spec send_email(User.t()) :: {:ok, Oban.Job.t()}
+ def send_email(%User{} = user) do
+ user
+ |> Emails.UserEmail.welcome(email_options(user))
+ |> Emails.Mailer.deliver_async()
+ end
+
+ defp email_options(user) do
+ bindings = [user: user, instance_name: instance_name()]
+
+ %{}
+ |> add_sender(Config.get([:welcome, :email, :sender], nil))
+ |> add_option(:subject, bindings)
+ |> add_option(:html, bindings)
+ |> add_option(:text, bindings)
+ end
+
+ defp add_option(opts, option, bindings) do
+ [:welcome, :email, option]
+ |> Config.get(nil)
+ |> eval_string(bindings)
+ |> merge_options(opts, option)
+ end
+
+ defp add_sender(opts, {_name, _email} = sender) do
+ merge_options(sender, opts, :sender)
+ end
+
+ defp add_sender(opts, sender) when is_binary(sender) do
+ add_sender(opts, {instance_name(), sender})
+ end
+
+ defp add_sender(opts, _), do: opts
+
+ defp merge_options(nil, options, _option), do: options
+
+ defp merge_options(value, options, option) do
+ Map.merge(options, %{option => value})
+ end
+
+ defp eval_string(nil, _), do: nil
+ defp eval_string("", _), do: nil
+ defp eval_string(str, bindings), do: EEx.eval_string(str, bindings)
+end
diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex
index f8f520285..86e1c0678 100644
--- a/lib/pleroma/user/welcome_message.ex
+++ b/lib/pleroma/user/welcome_message.ex
@@ -3,32 +3,45 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeMessage do
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.CommonAPI
- def post_welcome_message_to_user(user) do
- with %User{} = sender_user <- welcome_user(),
- message when is_binary(message) <- welcome_message() do
- CommonAPI.post(sender_user, %{
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:welcome, :direct_message, :enabled], false)
+
+ @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil}
+ def post_message(user) do
+ [:welcome, :direct_message, :sender_nickname]
+ |> Config.get(nil)
+ |> fetch_sender()
+ |> do_post(user, welcome_message())
+ end
+
+ defp do_post(%User{} = sender, %User{nickname: nickname}, message)
+ when is_binary(message) do
+ CommonAPI.post(
+ sender,
+ %{
visibility: "direct",
- status: "@#{user.nickname}\n#{message}"
- })
- else
- _ -> {:ok, nil}
- end
+ status: "@#{nickname}\n#{message}"
+ }
+ )
end
- defp welcome_user do
- with nickname when is_binary(nickname) <-
- Pleroma.Config.get([:instance, :welcome_user_nickname]),
- %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ defp do_post(_sender, _recipient, _message), do: {:ok, nil}
+
+ defp fetch_sender(nickname) when is_binary(nickname) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user
else
_ -> nil
end
end
+ defp fetch_sender(_), do: nil
+
defp welcome_message do
- Pleroma.Config.get([:instance, :welcome_message])
+ Config.get([:welcome, :direct_message, :message], nil)
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8da5cf938..a4db1d87c 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1370,19 +1370,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
+ {:error, {:reject, reason} = e} ->
+ Logger.info("Rejected user #{ap_id}: #{inspect(reason)}")
+ {:error, e}
+
{:error, e} ->
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
end
end
- def maybe_handle_clashing_nickname(nickname) do
- with %User{} = old_user <- User.get_by_nickname(nickname) do
- Logger.info("Found an old user for #{nickname}, ap id is #{old_user.ap_id}, renaming.")
+ def maybe_handle_clashing_nickname(data) do
+ nickname = data[:nickname]
+
+ with %User{} = old_user <- User.get_by_nickname(nickname),
+ {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
+ Logger.info(
+ "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
+ data[:ap_id]
+ }, renaming."
+ )
old_user
|> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"})
|> User.update_and_set_cache()
+ else
+ {:ap_id_comparison, true} ->
+ Logger.info(
+ "Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
+ )
+
+ _ ->
+ nil
end
end
@@ -1398,7 +1417,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> User.remote_user_changeset(data)
|> User.update_and_set_cache()
else
- maybe_handle_clashing_nickname(data[:nickname])
+ maybe_handle_clashing_nickname(data)
data
|> User.remote_user_changeset()
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index 8e47f1e02..7b4c78e0f 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -21,8 +21,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
@impl true
def describe, do: {:ok, %{}}
- defp local?(%{"id" => id}) do
- String.starts_with?(id, Pleroma.Web.Endpoint.url())
+ defp local?(%{"actor" => actor}) do
+ String.starts_with?(actor, Pleroma.Web.Endpoint.url())
end
defp note?(activity) do
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 0270b96ae..b96388489 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
if score < 0.8 do
{:ok, message}
else
- {:reject, nil}
+ {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index a7e187b5e..b22464111 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -39,14 +39,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
{:ok, message}
{:old_user, false} ->
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
{:error, _} ->
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
e ->
- Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 2627a0007..3bf70b894 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -27,7 +27,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
def filter_by_summary(_in_reply_to, child), do: child
- def filter(%{"type" => "Create", "object" => child_object} = object) do
+ def filter(%{"type" => "Create", "object" => child_object} = object)
+ when is_map(child_object) do
child =
child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"])
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index f6b2c4415..9ba07b4e3 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do
- {:reject, nil}
+ {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else
{:ok, message}
end
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
{:ok, message} <- delist_message(message, delist_threshold) do
{:ok, message}
else
- _e -> {:reject, nil}
+ e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index 88b0d2b39..15e09dcf0 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
- {:reject, nil}
+ {:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
{:ok, message}
end
@@ -89,8 +89,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, message} <- check_replace(message) do
{:ok, message}
else
- _e ->
- {:reject, nil}
+ {:reject, nil} -> {:reject, "[KeywordPolicy] "}
+ {:reject, _} = e -> e
+ _e -> {:reject, "[KeywordPolicy] "}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 06f003921..7910ca131 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -12,8 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || [])
- if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
- {:reject, nil}
+ if rejected_mention =
+ Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
+ {:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else
{:ok, message}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index a62914135..d45d2d7e3 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_reject(message, actions) do
if :reject in actions do
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy]"}
else
{:ok, message}
end
@@ -37,8 +37,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_delist(message, actions) do
if :delist in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
- cc = List.delete(message["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
+ to =
+ List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++
+ [user.follower_address]
+
+ cc =
+ List.delete(message["cc"] || [], user.follower_address) ++
+ [Pleroma.Constants.as_public()]
message =
message
@@ -47,9 +52,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message}
else
- # Unhandleable error: somebody is messing around, just drop the message.
_e ->
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
@@ -59,8 +63,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_strip_followers(message, actions) do
if :strip_followers in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"], user.follower_address)
- cc = List.delete(message["cc"], user.follower_address)
+ to = List.delete(message["to"] || [], user.follower_address)
+ cc = List.delete(message["cc"] || [], user.follower_address)
message =
message
@@ -69,9 +73,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message}
else
- # Unhandleable error: somebody is messing around, just drop the message.
_e ->
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index 4fd63106d..0b9ed2224 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
{:ok, object}
true ->
- {:reject, nil}
+ {:reject, "[RejectNonPublic] visibility: #{visibility}"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 70a2ca053..bb193475a 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@behaviour Pleroma.Web.ActivityPub.MRF
alias Pleroma.Config
+ alias Pleroma.FollowingRelationship
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@@ -21,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
accepts == [] -> {:ok, object}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
- true -> {:reject, nil}
+ true -> {:reject, "[SimplePolicy] host not in accept list"}
end
end
@@ -31,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in reject list"}
else
{:ok, object}
end
@@ -108,13 +109,42 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object}
end
+ defp intersection(list1, list2) do
+ list1 -- list1 -- list2
+ end
+
+ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
+ followers_only =
+ Config.get([:mrf_simple, :followers_only])
+ |> MRF.subdomains_regex()
+
+ object =
+ with true <- MRF.subdomain_match?(followers_only, actor_host),
+ user <- User.get_cached_by_ap_id(object["actor"]) do
+ # Don't use Map.get/3 intentionally, these must not be nil
+ fixed_to = object["to"] || []
+ fixed_cc = object["cc"] || []
+
+ to = FollowingRelationship.followers_ap_ids(user, fixed_to)
+ cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
+
+ object
+ |> Map.put("to", intersection([user.follower_address | to], fixed_to))
+ |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
+ else
+ _ -> object
+ end
+
+ {:ok, object}
+ end
+
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in report_removal list"}
else
{:ok, object}
end
@@ -159,7 +189,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in reject_deletes list"}
else
{:ok, object}
end
@@ -174,10 +204,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_media_removal(actor_info, object),
{:ok, object} <- check_media_nsfw(actor_info, object),
{:ok, object} <- check_ftl_removal(actor_info, object),
+ {:ok, object} <- check_followers_only(actor_info, object),
{:ok, object} <- check_report_removal(actor_info, object) do
{:ok, object}
else
- _e -> {:reject, nil}
+ {:reject, nil} -> {:reject, "[SimplePolicy]"}
+ {:reject, _} = e -> e
+ _ -> {:reject, "[SimplePolicy]"}
end
end
@@ -191,7 +224,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object}
else
- _e -> {:reject, nil}
+ {:reject, nil} -> {:reject, "[SimplePolicy]"}
+ {:reject, _} = e -> e
+ _ -> {:reject, "[SimplePolicy]"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index c310462cb..febabda08 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -134,12 +134,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
if user.local == true do
{:ok, message}
else
- {:reject, nil}
+ {:reject,
+ "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
end
end
- defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
- do: {:reject, nil}
+ defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
+ do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index 651aed70f..1a28f2ba2 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
if actor in allow_list do
{:ok, object}
else
- {:reject, nil}
+ {:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
index 6167a74e2..a6c545570 100644
--- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -11,22 +11,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
with {:ok, _} <- filter(child_message) do
{:ok, message}
else
- {:reject, nil} ->
- {:reject, nil}
+ {:reject, _} = e -> e
end
end
def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
- true <-
- Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
- false <-
- length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
+ {_, true} <-
+ {:accepted,
+ Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
+ {_, false} <-
+ {:rejected,
+ length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
{:ok, _} <- filter(message["object"]) do
{:ok, message}
else
- _ -> {:reject, nil}
+ {:reject, _} = e -> e
+ {:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
+ {:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
+ _ -> {:reject, "[VocabularyPolicy]"}
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index df926829c..0dcc7be4d 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
the system.
"""
+ alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
@@ -71,6 +72,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|> UndoValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
+ undone_object = Activity.get_by_ap_id(object["object"])
+
+ meta =
+ meta
+ |> Keyword.put(:object_data, undone_object.data)
+
{:ok, object, meta}
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index aeef31945..bd46f8034 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -34,10 +34,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
cng
|> validate_change(field_name, fn field_name, actor ->
- if User.get_cached_by_ap_id(actor) do
- []
- else
- [{field_name, "can't find user"}]
+ case User.get_cached_by_ap_id(actor) do
+ %User{deactivated: true} ->
+ [{field_name, "user is deactivated"}]
+
+ %User{} ->
+ []
+
+ _ ->
+ [{field_name, "can't find user"}]
end
end)
end
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 6875c47f6..36e325c37 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -52,6 +52,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
if !do_not_federate && local do
+ activity =
+ if object = Keyword.get(meta, :object_data) do
+ %{activity | data: Map.put(activity.data, "object", object)}
+ else
+ activity
+ end
+
Federator.publish(activity)
{:ok, :federated}
else
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index b70cbd043..d88f7f3ee 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -49,7 +49,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
- %{host: host, path: path} = URI.parse(inbox)
+
+ uri = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@@ -57,8 +58,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
signature =
Pleroma.Signature.sign(actor, %{
- "(request-target)": "post #{path}",
- host: host,
+ "(request-target)": "post #{uri.path}",
+ host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
date: date
@@ -76,8 +77,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", digest}
]
) do
- if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
- do: Instances.set_reachable(inbox)
+ if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
+ Instances.set_reachable(inbox)
+ end
result
else
@@ -96,6 +98,14 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> publish_one()
end
+ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
+ if port == URI.default_port(scheme) do
+ host
+ else
+ "#{host}:#{port}"
+ end
+ end
+
defp should_federate?(inbox, public) do
if public do
true
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 884646ceb..35aa05eb5 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -62,15 +62,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do
+ addrs = map[field]
+
cond do
- is_binary(map[field]) ->
- Map.put(map, field, [map[field]])
+ is_list(addrs) ->
+ Map.put(map, field, Enum.filter(addrs, &is_binary/1))
- is_nil(map[field]) ->
- Map.put(map, field, [])
+ is_binary(addrs) ->
+ Map.put(map, field, [addrs])
true ->
- map
+ Map.put(map, field, [])
end
end
@@ -176,7 +178,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.drop(["conversation"])
else
e ->
- Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index dfae602df..713b0ca1f 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -719,15 +719,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
case Activity.get_by_ap_id_with_object(id) do
%Activity{} = activity ->
+ activity_actor = User.get_by_ap_id(activity.object.data["actor"])
+
%{
"type" => "Note",
"id" => activity.data["id"],
"content" => activity.object.data["content"],
"published" => activity.object.data["published"],
"actor" =>
- AccountView.render("show.json", %{
- user: User.get_by_ap_id(activity.object.data["actor"])
- })
+ AccountView.render(
+ "show.json",
+ %{user: activity_actor, skip_visibility_check: true}
+ )
}
_ ->
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 e5f14269a..aa2af1ab5 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
:user_toggle_activation,
:user_activate,
:user_deactivate,
+ :user_approve,
:tag_users,
:untag_users,
:right_add,
@@ -303,6 +304,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> render("index.json", %{users: Keyword.values(updated_users)})
end
+ def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.approve(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "approve"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: updated_users})
+ end
+
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
@@ -345,12 +361,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
json(
conn,
- AccountView.render("index.json", users: users, count: count, page_size: page_size)
+ AccountView.render("index.json",
+ users: users,
+ count: count,
+ page_size: page_size
+ )
)
end
end
- @filters ~w(local external active deactivated is_admin is_moderator)
+ @filters ~w(local external active deactivated need_approval is_admin is_moderator)
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
@@ -616,29 +636,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
User.toggle_confirmation(users)
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "confirm_email"
- })
+ 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 = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
-
- User.try_send_confirmation_email(users)
+ 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"
- })
+ ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
json(conn, "")
end
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index 7f60470cb..0df13007f 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.ConfigDB
alias Pleroma.Plugs.OAuthScopesPlug
- @descriptions Pleroma.Docs.JSON.compile()
-
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
@@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
def descriptions(conn, _params) do
- descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
+ descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
json(conn, descriptions)
end
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index e1e929632..333e72e42 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -77,7 +77,9 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"roles" => User.roles(user),
"tags" => user.tags || [],
"confirmation_pending" => user.confirmation_pending,
- "url" => user.uri || user.ap_id
+ "approval_pending" => user.approval_pending,
+ "url" => user.uri || user.ap_id,
+ "registration_reason" => user.registration_reason
}
end
@@ -105,7 +107,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
end
def merge_account_views(%User{} = user) do
- MastodonAPI.AccountView.render("show.json", %{user: user})
+ MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true})
|> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user}))
end
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index a258e8421..2a7f1a706 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -29,6 +29,10 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
}
end
+ def admin_api_params do
+ [Operation.parameter(:admin_token, :query, :string, "Allows authorization via admin token.")]
+ end
+
def pagination_params do
[
Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 952d9347b..aaebc9b5c 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -159,6 +159,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Accounts which follow the given account, if network is not hidden by the account owner.",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(:id, :query, :string, "ID of the resource owner"),
with_relationships_param() | pagination_params()
],
responses: %{
@@ -177,6 +178,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Accounts which the given account is following, if network is not hidden by the account owner.",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(:id, :query, :string, "ID of the resource owner"),
with_relationships_param() | pagination_params()
],
responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
@@ -447,21 +449,32 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
- # TODO: This is actually a token respone, but there's no oauth operation file yet.
+ # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
defp create_response do
%Schema{
title: "AccountCreateResponse",
description: "Response schema for an account",
type: :object,
properties: %{
+ # The response when auto-login on create succeeds (token is issued):
token_type: %Schema{type: :string},
access_token: %Schema{type: :string},
refresh_token: %Schema{type: :string},
scope: %Schema{type: :string},
created_at: %Schema{type: :integer, format: :"date-time"},
me: %Schema{type: :string},
- expires_in: %Schema{type: :integer}
+ expires_in: %Schema{type: :integer},
+ #
+ # The response when registration succeeds but auto-login fails (no token):
+ identifier: %Schema{type: :string},
+ message: %Schema{type: :string}
},
+ required: [],
+ # Note: example of successful registration with failed login response:
+ # example: %{
+ # "identifier" => "missing_confirmed_email",
+ # "message" => "You have been registered. Please check your email for further instructions."
+ # },
example: %{
"token_type" => "Bearer",
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
index 7b38a2ef4..3a8380797 100644
--- a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex
@@ -26,6 +26,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
%Schema{type: :boolean, default: false},
"Get only saved in database settings"
)
+ | admin_api_params()
],
security: [%{"oAuth" => ["read"]}],
responses: %{
@@ -41,6 +42,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
summary: "Update config settings",
operationId: "AdminAPI.ConfigController.update",
security: [%{"oAuth" => ["write"]}],
+ parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
@@ -73,6 +75,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
summary: "Get JSON with config descriptions.",
operationId: "AdminAPI.ConfigController.descriptions",
security: [%{"oAuth" => ["read"]}],
+ parameters: admin_api_params(),
responses: %{
200 =>
Operation.response("Config Descriptions", "application/json", %Schema{
diff --git a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
index d3af9db49..801024d75 100644
--- a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
@@ -20,6 +20,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Get a list of generated invites",
operationId: "AdminAPI.InviteController.index",
security: [%{"oAuth" => ["read:invites"]}],
+ parameters: admin_api_params(),
responses: %{
200 =>
Operation.response("Invites", "application/json", %Schema{
@@ -51,6 +52,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Create an account registration invite token",
operationId: "AdminAPI.InviteController.create",
security: [%{"oAuth" => ["write:invites"]}],
+ parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
@@ -71,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Revoke invite by token",
operationId: "AdminAPI.InviteController.revoke",
security: [%{"oAuth" => ["write:invites"]}],
+ parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
@@ -97,6 +100,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
summary: "Sends registration invite via email",
operationId: "AdminAPI.InviteController.email",
security: [%{"oAuth" => ["write:invites"]}],
+ parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
index 0358cfbad..20d033f66 100644
--- a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
@@ -33,6 +33,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
%Schema{type: :integer, default: 50},
"Number of statuses to return"
)
+ | admin_api_params()
],
responses: %{
200 => success_response()
@@ -46,6 +47,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
summary: "Remove a banned MediaProxy URL from Cachex",
operationId: "AdminAPI.MediaProxyCacheController.delete",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
@@ -71,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
summary: "Purge and optionally ban a MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.purge",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
index fbc9f80d7..a75f3e622 100644
--- a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
@@ -36,6 +36,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
%Schema{type: :integer, default: 50},
"Number of apps to return"
)
+ | admin_api_params()
],
responses: %{
200 =>
@@ -72,6 +73,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
summary: "Create OAuth App",
operationId: "AdminAPI.OAuthAppController.create",
requestBody: request_body("Parameters", create_request()),
+ parameters: admin_api_params(),
security: [%{"oAuth" => ["write"]}],
responses: %{
200 => Operation.response("App", "application/json", oauth_app()),
@@ -85,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
tags: ["Admin", "oAuth Apps"],
summary: "Update OAuth App",
operationId: "AdminAPI.OAuthAppController.update",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_request()),
responses: %{
@@ -103,7 +105,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
tags: ["Admin", "oAuth Apps"],
summary: "Delete OAuth App",
operationId: "AdminAPI.OAuthAppController.delete",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}],
responses: %{
204 => no_content_response(),
diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
index 7672cb467..67ee5eee0 100644
--- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "List Relays",
operationId: "AdminAPI.RelayController.index",
security: [%{"oAuth" => ["read"]}],
+ parameters: admin_api_params(),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
@@ -41,6 +42,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "Follow a Relay",
operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}],
+ parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
@@ -64,6 +66,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
summary: "Unfollow a Relay",
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
+ parameters: admin_api_params(),
requestBody:
request_body("Parameters", %Schema{
type: :object,
diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
index 15e78bfaf..3bb7ec49e 100644
--- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex
@@ -48,6 +48,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
%Schema{type: :integer, default: 50},
"Number number of log entries per page"
)
+ | admin_api_params()
],
responses: %{
200 =>
@@ -71,7 +72,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
tags: ["Admin", "Reports"],
summary: "Get an individual report",
operationId: "AdminAPI.ReportController.show",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:reports"]}],
responses: %{
200 => Operation.response("Report", "application/json", report()),
@@ -86,6 +87,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
summary: "Change the state of one or multiple reports",
operationId: "AdminAPI.ReportController.update",
security: [%{"oAuth" => ["write:reports"]}],
+ parameters: admin_api_params(),
requestBody: request_body("Parameters", update_request(), required: true),
responses: %{
204 => no_content_response(),
@@ -100,7 +102,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
tags: ["Admin", "Reports"],
summary: "Create report note",
operationId: "AdminAPI.ReportController.notes_create",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
requestBody:
request_body("Parameters", %Schema{
type: :object,
@@ -124,6 +126,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do
parameters: [
Operation.parameter(:report_id, :path, :string, "Report ID"),
Operation.parameter(:id, :path, :string, "Note ID")
+ | admin_api_params()
],
security: [%{"oAuth" => ["write:reports"]}],
responses: %{
diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
index 745399b4b..c105838a4 100644
--- a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex
@@ -55,6 +55,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
%Schema{type: :integer, default: 50},
"Number of statuses to return"
)
+ | admin_api_params()
],
responses: %{
200 =>
@@ -71,7 +72,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"],
summary: "Show Status",
operationId: "AdminAPI.StatusController.show",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:statuses"]}],
responses: %{
200 => Operation.response("Status", "application/json", status()),
@@ -85,7 +86,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"],
summary: "Change the scope of an individual reported status",
operationId: "AdminAPI.StatusController.update",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}],
requestBody: request_body("Parameters", update_request(), required: true),
responses: %{
@@ -100,7 +101,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
tags: ["Admin", "Statuses"],
summary: "Delete an individual reported status",
operationId: "AdminAPI.StatusController.delete",
- parameters: [id_param()],
+ parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}],
responses: %{
200 => empty_object_response(),
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
index cf299bfc2..b1a0d26ab 100644
--- a/lib/pleroma/web/api_spec/operations/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -300,11 +300,11 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"content" => "Check this out :firefox:",
"id" => "13",
"chat_id" => "1",
- "actor_id" => "someflakeid",
+ "account_id" => "someflakeid",
"unread" => false
},
%{
- "actor_id" => "someflakeid",
+ "account_id" => "someflakeid",
"content" => "Whats' up?",
"id" => "12",
"chat_id" => "1",
diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
index 049bcf931..1e0da8209 100644
--- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
}
end
+ # Supporting domain query parameter is deprecated in Mastodon API
def create_operation do
%Operation{
tags: ["domain_blocks"],
@@ -45,11 +46,13 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
""",
operationId: "DomainBlockController.create",
requestBody: domain_block_request(),
+ parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{200 => empty_object_response()}
}
end
+ # Supporting domain query parameter is deprecated in Mastodon API
def delete_operation do
%Operation{
tags: ["domain_blocks"],
@@ -57,6 +60,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
operationId: "DomainBlockController.delete",
requestBody: domain_block_request(),
+ parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
@@ -71,10 +75,9 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
type: :object,
properties: %{
domain: %Schema{type: :string}
- },
- required: [:domain]
+ }
},
- required: true,
+ required: false,
example: %{
"domain" => "facebook.com"
}
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index cf148bc9d..ca79f0747 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -90,11 +90,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
notification_settings: %Schema{
type: :object,
properties: %{
- followers: %Schema{type: :boolean},
- follows: %Schema{type: :boolean},
- non_followers: %Schema{type: :boolean},
- non_follows: %Schema{type: :boolean},
- privacy_option: %Schema{type: :boolean}
+ block_from_strangers: %Schema{type: :boolean},
+ hide_notification_contents: %Schema{type: :boolean}
}
},
relationship: AccountRelationship,
@@ -182,11 +179,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"unread_conversation_count" => 0,
"tags" => [],
"notification_settings" => %{
- "followers" => true,
- "follows" => true,
- "non_followers" => true,
- "non_follows" => true,
- "privacy_option" => false
+ "block_from_strangers" => false,
+ "hide_notification_contents" => false
},
"relationship" => %{
"blocked_by" => false,
diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex
index 3ee85aa76..bbf2a4427 100644
--- a/lib/pleroma/web/api_spec/schemas/chat_message.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex
@@ -19,13 +19,46 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
content: %Schema{type: :string, nullable: true},
created_at: %Schema{type: :string, format: :"date-time"},
emojis: %Schema{type: :array},
- attachment: %Schema{type: :object, nullable: true}
+ attachment: %Schema{type: :object, nullable: true},
+ card: %Schema{
+ type: :object,
+ nullable: true,
+ description: "Preview card for links included within status content",
+ required: [:url, :title, :description, :type],
+ properties: %{
+ type: %Schema{
+ type: :string,
+ enum: ["link", "photo", "video", "rich"],
+ description: "The type of the preview card"
+ },
+ provider_name: %Schema{
+ type: :string,
+ nullable: true,
+ description: "The provider of the original resource"
+ },
+ provider_url: %Schema{
+ type: :string,
+ format: :uri,
+ description: "A link to the provider of the original resource"
+ },
+ url: %Schema{type: :string, format: :uri, description: "Location of linked resource"},
+ image: %Schema{
+ type: :string,
+ nullable: true,
+ format: :uri,
+ description: "Preview thumbnail"
+ },
+ title: %Schema{type: :string, description: "Title of linked resource"},
+ description: %Schema{type: :string, description: "Description of preview"}
+ }
+ }
},
example: %{
"account_id" => "someflakeid",
"chat_id" => "1",
"content" => "hey you again",
"created_at" => "2020-04-21T15:06:45.000Z",
+ "card" => nil,
"emojis" => [
%{
"static_url" => "https://dontbulling.me/emoji/Firefox.gif",
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex
index bce27897f..3b1469c19 100644
--- a/lib/pleroma/web/chat_channel.ex
+++ b/lib/pleroma/web/chat_channel.ex
@@ -4,8 +4,10 @@
defmodule Pleroma.Web.ChatChannel do
use Phoenix.Channel
+
alias Pleroma.User
alias Pleroma.Web.ChatChannel.ChatChannelState
+ alias Pleroma.Web.MastodonAPI.AccountView
def join("chat:public", _message, socket) do
send(self(), :after_join)
@@ -22,9 +24,9 @@ defmodule Pleroma.Web.ChatChannel do
if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
author = User.get_cached_by_nickname(user_name)
- author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
+ author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
- message = ChatChannelState.add_message(%{text: text, author: author})
+ message = ChatChannelState.add_message(%{text: text, author: author_json})
broadcast!(socket, "new_msg", message)
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 226d42c2c..527fb288d 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -28,6 +28,17 @@ defmodule Pleroma.Web.Endpoint do
}
)
+ # Careful! No `only` restriction here, as we don't know what frontends contain.
+ plug(Pleroma.Plugs.FrontendStatic,
+ at: "/",
+ frontend_type: :primary,
+ gzip: true,
+ cache_control_for_etags: @static_cache_control,
+ headers: %{
+ "cache-control" => @static_cache_control
+ }
+ )
+
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index d56f43818..9cd334a33 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -47,7 +47,7 @@ defmodule Pleroma.Web.Feed.UserController do
"atom"
end
- with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
activities =
%{
type: ["Create"],
@@ -71,6 +71,7 @@ defmodule Pleroma.Web.Feed.UserController do
render_error(conn, :not_found, "Not found")
end
+ def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index fe5d022f5..f45678184 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
- alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@@ -100,11 +100,34 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params),
- {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
- {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
+ {:ok, user} <- TwitterAPI.register_user(params),
+ {_, {:ok, token}} <-
+ {:login, OAuthController.login(user, app, app.scopes)} do
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
- {:error, error} -> json_response(conn, :bad_request, %{error: error})
+ {:login, {:account_status, :confirmation_pending}} ->
+ json_response(conn, :ok, %{
+ message: "You have been registered. Please check your email for further instructions.",
+ identifier: "missing_confirmed_email"
+ })
+
+ {:login, {:account_status, :approval_pending}} ->
+ json_response(conn, :ok, %{
+ message:
+ "You have been registered. You'll be able to log in once your account is approved.",
+ identifier: "awaiting_approval"
+ })
+
+ {:login, _} ->
+ json_response(conn, :ok, %{
+ message:
+ "You have been registered. Some post-registration steps may be pending. " <>
+ "Please log in manually.",
+ identifier: "manual_login_required"
+ })
+
+ {:error, error} ->
+ json_response(conn, :bad_request, %{error: error})
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
index 825b231ab..9c2d093cd 100644
--- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -32,9 +32,19 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
json(conn, %{})
end
+ def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do
+ User.block_domain(blocker, domain)
+ json(conn, %{})
+ end
+
@doc "DELETE /api/v1/domain_blocks"
def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
User.unblock_domain(blocker, domain)
json(conn, %{})
end
+
+ def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do
+ User.unblock_domain(blocker, domain)
+ json(conn, %{})
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index 29affa7d5..5a983db39 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -93,7 +93,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
- as: :user,
embed_relationships: options[:embed_relationships]
)
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 12be530c9..ecfa38489 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -172,6 +172,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
with_direct_conversation_id: true
)
else
+ {:error, {:reject, message}} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: message})
+
{:error, message} ->
conn
|> put_status(:unprocessable_entity)
@@ -309,7 +314,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
@doc "GET /api/v1/statuses/:id/favourited_by"
def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do
- with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ with true <- Pleroma.Config.get([:instance, :show_reactions]),
+ %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
users =
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index bc9745044..864c0417f 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -27,21 +27,40 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
UserRelationship.view_relationships_option(reading_user, users)
end
- opts = Map.put(opts, :relationships, relationships_opt)
+ opts =
+ opts
+ |> Map.merge(%{relationships: relationships_opt, as: :user})
+ |> Map.delete(:users)
users
|> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
end
- def render("show.json", %{user: user} = opts) do
- if User.visible_for(user, opts[:for]) == :visible do
+ @doc """
+ Renders specified user account.
+ :skip_visibility_check option skips visibility check and renders any user (local or remote)
+ regardless of [:pleroma, :restrict_unauthenticated] setting.
+ :for option specifies the requester and can be a User record or nil.
+ Only use `user: user, for: user` when `user` is the actual requester of own profile.
+ """
+ def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do
+ do_render("show.json", opts)
+ end
+
+ def render("show.json", %{user: user, for: for_user_or_nil} = opts) do
+ if User.visible_for(user, for_user_or_nil) == :visible do
do_render("show.json", opts)
else
%{}
end
end
+ def render("show.json", _) do
+ raise "In order to prevent account accessibility issues, " <>
+ ":skip_visibility_check or :for option is required."
+ end
+
def render("mention.json", %{user: user}) do
%{
id: to_string(user.id),
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index 06f0c1728..a91994915 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
%{
id: participation.id |> to_string(),
- accounts: render(AccountView, "index.json", users: users, as: :user),
+ accounts: render(AccountView, "index.json", users: users, for: user),
unread: !participation.read,
last_status:
render(StatusView, "show.json",
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 5deb0d7ed..ea2d3aa9c 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -26,6 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
thumbnail: Keyword.get(instance, :instance_thumbnail),
languages: ["en"],
registrations: Keyword.get(instance, :registrations_open),
+ approval_required: Keyword.get(instance, :account_approval_required),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
poll_limits: Keyword.get(instance, :poll_limits),
@@ -41,7 +42,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
account_activation_required: Keyword.get(instance, :account_activation_required),
features: features(),
federation: federation(),
- fields_limits: fields_limits()
+ fields_limits: fields_limits(),
+ post_formats: Config.get([:instance, :allowed_post_formats])
},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index fa9d695f3..91b41ef59 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -297,13 +297,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
emoji_reactions =
with %{data: %{"reactions" => emoji_reactions}} <- object do
- Enum.map(emoji_reactions, fn [emoji, users] ->
- %{
- name: emoji,
- count: length(users),
- me: !!(opts[:for] && opts[:for].ap_id in users)
- }
+ Enum.map(emoji_reactions, fn
+ [emoji, users] when is_list(users) ->
+ build_emoji_map(emoji, users, opts[:for])
+
+ {emoji, users} when is_list(users) ->
+ build_emoji_map(emoji, users, opts[:for])
+
+ _ ->
+ nil
end)
+ |> Enum.reject(&is_nil/1)
else
_ -> []
end
@@ -545,4 +549,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
do: id in pinned_activities
+
+ defp build_emoji_map(emoji, users, current_user) do
+ %{
+ name: emoji,
+ count: length(users),
+ me: !!(current_user && current_user.ap_id in users)
+ }
+ end
end
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index 6f35826da..dfbfcea6b 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -60,22 +60,28 @@ defmodule Pleroma.Web.MediaProxy do
defp whitelisted?(url) do
%{host: domain} = URI.parse(url)
- mediaproxy_whitelist = Config.get([:media_proxy, :whitelist])
-
- upload_base_url_domain =
- if !is_nil(Config.get([Upload, :base_url])) do
- [URI.parse(Config.get([Upload, :base_url])).host]
+ mediaproxy_whitelist_domains =
+ [:media_proxy, :whitelist]
+ |> Config.get()
+ |> Enum.map(&maybe_get_domain_from_url/1)
+
+ whitelist_domains =
+ if base_url = Config.get([Upload, :base_url]) do
+ %{host: base_domain} = URI.parse(base_url)
+ [base_domain | mediaproxy_whitelist_domains]
else
- []
+ mediaproxy_whitelist_domains
end
- whitelist = mediaproxy_whitelist ++ upload_base_url_domain
+ domain in whitelist_domains
+ end
- Enum.any?(whitelist, fn pattern ->
- String.equivalent?(domain, pattern)
- end)
+ defp maybe_get_domain_from_url("http" <> _ = url) do
+ URI.parse(url).host
end
+ defp maybe_get_domain_from_url(domain), do: domain
+
def encode_url(url) do
base64 = Base.url_encode64(url, @base64_opts)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 7683589cf..f29b3cb57 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -260,11 +260,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
) do
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn),
- {:account_status, :active} <- {:account_status, User.account_status(user)},
- {:ok, scopes} <- validate_scopes(app, params),
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes),
- {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
- {:ok, token} <- Token.exchange_token(app, auth) do
+ requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
+ {:ok, token} <- login(user, app, requested_scopes) do
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
error ->
@@ -337,6 +334,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do
)
end
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
+ render_error(
+ conn,
+ :forbidden,
+ "Your account is awaiting approval.",
+ %{},
+ "awaiting_approval"
+ )
+ end
+
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
render_invalid_credentials_error(conn)
end
@@ -512,6 +519,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
+ defp do_create_authorization(conn, auth_attrs, user \\ nil)
+
defp do_create_authorization(
%Plug.Conn{} = conn,
%{
@@ -521,19 +530,37 @@ defmodule Pleroma.Web.OAuth.OAuthController do
"redirect_uri" => redirect_uri
} = auth_attrs
},
- user \\ nil
+ user
) do
with {_, {:ok, %User{} = user}} <-
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
%App{} = app <- Repo.get_by(App, client_id: client_id),
true <- redirect_uri in String.split(app.redirect_uris),
- {:ok, scopes} <- validate_scopes(app, auth_attrs),
- {:account_status, :active} <- {:account_status, User.account_status(user)},
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
+ requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
+ {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
{:ok, auth, user}
end
end
+ defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
+ when is_list(requested_scopes) do
+ with {:account_status, :active} <- {:account_status, User.account_status(user)},
+ {:ok, scopes} <- validate_scopes(app, requested_scopes),
+ {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
+ {:ok, auth}
+ end
+ end
+
+ # Note: intended to be a private function but opened for AccountController that logs in on signup
+ @doc "If checks pass, creates authorization and token for given user, app and requested scopes."
+ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
+ with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
+ {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ {:ok, token}
+ end
+ end
+
# Special case: Local MastodonFE
defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
@@ -550,12 +577,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- @spec validate_scopes(App.t(), map()) ::
+ @spec validate_scopes(App.t(), map() | list()) ::
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
- defp validate_scopes(%App{} = app, params) do
- params
- |> Scopes.fetch_scopes(app.scopes)
- |> Scopes.validate(app.scopes)
+ defp validate_scopes(%App{} = app, params) when is_map(params) do
+ requested_scopes = Scopes.fetch_scopes(params, app.scopes)
+ validate_scopes(app, requested_scopes)
+ end
+
+ defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do
+ Scopes.validate(requested_scopes, app.scopes)
end
def default_redirect_uri(%App{} = app) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index c8ef3d915..e8a1746d4 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -89,11 +89,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
conn
|> put_view(MessageReferenceView)
- |> render("show.json", for: user, chat_message_reference: cm_ref)
+ |> render("show.json", chat_message_reference: cm_ref)
end
end
- def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
+ def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
id: chat_id,
message_id: message_id
}) do
@@ -104,12 +104,15 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
conn
|> put_view(MessageReferenceView)
- |> render("show.json", for: user, chat_message_reference: cm_ref)
+ |> render("show.json", chat_message_reference: cm_ref)
end
end
def mark_as_read(
- %{body_params: %{last_read_id: last_read_id}, assigns: %{user: %{id: user_id}}} = conn,
+ %{
+ body_params: %{last_read_id: last_read_id},
+ assigns: %{user: %{id: user_id}}
+ } = conn,
%{id: id}
) do
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
@@ -121,7 +124,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = params) do
+ def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
cm_refs =
chat
@@ -130,7 +133,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
conn
|> put_view(MessageReferenceView)
- |> render("index.json", for: user, chat_message_references: cm_refs)
+ |> render("index.json", chat_message_references: cm_refs)
else
_ ->
conn
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
index 33ecd1f70..657f46324 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -21,8 +21,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
]
)
- @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
- plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list])
+ @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
index 19dcffdf3..7f9254c13 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
@@ -25,7 +25,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do
- with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
+ with true <- Pleroma.Config.get([:instance, :show_reactions]),
+ %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
%Object{data: %{"reactions" => reactions}} when is_list(reactions) <-
Object.normalize(activity) do
reactions = filter(reactions, params)
diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
index f2112a86e..d4e08b50d 100644
--- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
%{
chat_message_reference: %{
id: id,
- object: %{data: chat_message},
+ object: %{data: chat_message} = object,
chat_id: chat_id,
unread: unread
}
@@ -30,7 +30,12 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
attachment:
chat_message["attachment"] &&
StatusView.render("attachment.json", attachment: chat_message["attachment"]),
- unread: unread
+ unread: unread,
+ card:
+ StatusView.render(
+ "card.json",
+ Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
+ )
}
end
diff --git a/lib/pleroma/web/pleroma_api/views/chat_view.ex b/lib/pleroma/web/pleroma_api/views/chat_view.ex
index 1c996da11..04dc20d51 100644
--- a/lib/pleroma/web/pleroma_api/views/chat_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/chat_view.ex
@@ -15,10 +15,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do
def render("show.json", %{chat: %Chat{} = chat} = opts) do
recipient = User.get_cached_by_ap_id(chat.recipient)
last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat)
+ account_view_opts = account_view_opts(opts, recipient)
%{
id: chat.id |> to_string(),
- account: AccountView.render("show.json", Map.put(opts, :user, recipient)),
+ account: AccountView.render("show.json", account_view_opts),
unread: MessageReference.unread_count_for_chat(chat),
last_message:
last_message &&
@@ -27,7 +28,17 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do
}
end
- def render("index.json", %{chats: chats}) do
- render_many(chats, __MODULE__, "show.json")
+ def render("index.json", %{chats: chats} = opts) do
+ render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
+ end
+
+ defp account_view_opts(opts, recipient) do
+ account_view_opts = Map.put(opts, :user, recipient)
+
+ if Map.has_key?(account_view_opts, :for) do
+ account_view_opts
+ else
+ Map.put(account_view_opts, :skip_visibility_check, true)
+ end
end
end
diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
index 84d2d303d..e0f98b50a 100644
--- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do
%{
name: emoji,
count: length(users),
- accounts: render(AccountView, "index.json", users: users, for: user, as: :user),
+ accounts: render(AccountView, "index.json", users: users, for: user),
me: !!(user && user.ap_id in user_ap_ids)
}
end
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index cdb827e76..16368485e 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -104,7 +104,7 @@ defmodule Pleroma.Web.Push.Impl do
def build_content(
%{
- user: %{notification_settings: %{privacy_option: true}}
+ user: %{notification_settings: %{hide_notification_contents: true}}
} = notification,
_actor,
_object,
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 1729141e9..6210f2c5a 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -9,12 +9,17 @@ defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
+ @rich_media_options [
+ pool: :media,
+ max_body: 2_000_000
+ ]
+
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
+ validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])
page_url
- |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld)
+ |> Linkify.Parser.url?(validate_tld: validate_tld)
|> parse_uri(page_url)
end
@@ -49,11 +54,11 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|> hd
end
- def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
+ def fetch_data_for_object(object) do
with true <- Config.get([:rich_media, :enabled]),
- %Object{} = object <- Object.normalize(activity),
false <- object.data["sensitive"] || false,
- {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
+ {:ok, page_url} <-
+ HTML.extract_first_external_url(object, object.data["content"]),
:ok <- validate_page_url(page_url),
{:ok, rich_media} <- Parser.parse(page_url) do
%{page_url: page_url, rich_media: rich_media}
@@ -62,10 +67,35 @@ defmodule Pleroma.Web.RichMedia.Helpers do
end
end
+ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
+ with true <- Config.get([:rich_media, :enabled]),
+ %Object{} = object <- Object.normalize(activity) do
+ fetch_data_for_object(object)
+ else
+ _ -> %{}
+ end
+ end
+
def fetch_data_for_activity(_), do: %{}
def perform(:fetch, %Activity{} = activity) do
fetch_data_for_activity(activity)
:ok
end
+
+ def rich_media_get(url) do
+ headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
+
+ options =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
+ Keyword.merge(@rich_media_options,
+ recv_timeout: 2_000,
+ with_body: true
+ )
+ else
+ @rich_media_options
+ end
+
+ Pleroma.HTTP.get(url, headers, options)
+ end
end
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index c8a767935..ca592833f 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -3,11 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
- @options [
- pool: :media,
- max_body: 2_000_000
- ]
-
defp parsers do
Pleroma.Config.get([:rich_media, :parsers])
end
@@ -75,21 +70,8 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp parse_url(url) do
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.merge(@options,
- recv_timeout: 2_000,
- with_body: true
- )
- else
- @options
- end
-
try do
- rich_media_agent = Pleroma.Application.user_agent() <> "; Bot"
-
- {:ok, %Tesla.Env{body: html}} =
- Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts)
+ {:ok, %Tesla.Env{body: html}} = Pleroma.Web.RichMedia.Helpers.rich_media_get(url)
html
|> parse_html()
diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
index 6bdeac89c..1fe6729c3 100644
--- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
end
defp get_oembed_data(url) do
- with {:ok, %Tesla.Env{body: json}} <- Pleroma.HTTP.get(url, [], adapter: [pool: :media]) do
+ with {:ok, %Tesla.Env{body: json}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url) do
Jason.decode(json)
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 386308362..c6433cc53 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -138,6 +138,7 @@ defmodule Pleroma.Web.Router do
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
patch("/users/activate", AdminAPIController, :user_activate)
patch("/users/deactivate", AdminAPIController, :user_deactivate)
+ patch("/users/approve", AdminAPIController, :user_approve)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 5836ec1e0..51603fe0c 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -37,7 +37,7 @@
}
a {
- color: color: #d8a070;
+ color: #d8a070;
text-decoration: none;
}
diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
index 750f65386..5ab59b57b 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
@@ -10,7 +10,7 @@
<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input">
<%= label f, :code, "Recovery code" %>
- <%= text_input f, :code %>
+ <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
index af6e546b0..af85777eb 100644
--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
@@ -10,7 +10,7 @@
<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
<div class="input">
<%= label f, :code, "Authentication code" %>
- <%= text_input f, :code %>
+ <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
<%= hidden_input f, :mfa_token, value: @mfa_token %>
<%= hidden_input f, :state, value: @state %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 5cfb385ac..2294d9d0d 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|> Map.put(:nickname, params[:username])
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|> Map.put(:password_confirmation, params[:password])
+ |> Map.put(:registration_reason, params[:reason])
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)
@@ -44,6 +45,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
case User.register(changeset) do
{:ok, user} ->
+ maybe_notify_admins(user)
{:ok, user}
{:error, changeset} ->
@@ -56,6 +58,18 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
+ defp maybe_notify_admins(%User{} = account) do
+ if Pleroma.Config.get([:instance, :account_approval_required]) do
+ User.all_superusers()
+ |> Enum.filter(fn user -> not is_nil(user.email) end)
+ |> Enum.each(fn superuser ->
+ superuser
+ |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account)
+ |> Pleroma.Emails.Mailer.deliver_async()
+ end)
+ end
+ end
+
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
%User{local: true, email: email} = user when is_binary(email) <-
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index f739dacb6..b1669d198 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -9,36 +9,6 @@ defmodule Pleroma.Web.MastoFEView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.CustomEmojiView
- @default_settings %{
- onboarded: true,
- home: %{
- shows: %{
- reblog: true,
- reply: true
- }
- },
- notifications: %{
- alerts: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- shows: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- sounds: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- }
- }
- }
-
def initial_state(token, user, custom_emojis) do
limit = Config.get([:instance, :limit])
@@ -86,7 +56,7 @@ defmodule Pleroma.Web.MastoFEView do
"video\/mp4"
]
},
- settings: user.mastofe_settings || @default_settings,
+ settings: user.mastofe_settings || %{},
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),