Merge remote-tracking branch 'origin/develop' into review-pr-7933

This commit is contained in:
Lain Soykaf 2026-07-24 23:24:11 +04:00
commit 465a81ecf2
No known key found for this signature in database
115 changed files with 1733 additions and 546 deletions

View file

@ -1,85 +0,0 @@
when:
# Temporary PR validation while arm/v7 Docker builds are being brought up.
- event: pull_request
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
- event: tag
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: stable
# Target the CAX21 arm64 host with native arm32 userspace support, not generic arm64 runners.
labels:
platform: linux/arm
armv7: native
variables:
docker_variables: &docker_variables
repo: pleroma/pleroma
registry: git.pleroma.social
armv7_build_settings: &armv7_build_settings
<<: *docker_variables
build_args:
- ELIXIR_IMG=arm32v7/elixir
- ELIXIR_TAG=1.17.3-otp-26-alpine
- ALPINE_IMG=arm32v7/alpine
- ALPINE_VER=3.20
extra_opts: --custom-platform=linux/arm/v7
armv7_push_settings: &armv7_push_settings
<<: *armv7_build_settings
username:
from_secret: pleroma-ci-user
password:
from_secret: pleroma-ci-password
steps:
docker-armv7-pr:
image: woodpeckerci/plugin-kaniko:2.3.1
when:
- event: pull_request
settings:
<<: *armv7_build_settings
dry_run: true
tags:
- armv7-pr-check
docker-develop-armv7:
image: woodpeckerci/plugin-kaniko:2.3.1
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
settings:
<<: *armv7_push_settings
tags:
- latest-armv7
- develop-armv7
- ${CI_COMMIT_SHA:0:8}-armv7
docker-stable-armv7:
image: woodpeckerci/plugin-kaniko:2.3.1
when:
- evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
settings:
<<: *armv7_push_settings
tags:
- latest-armv7
- stable-armv7
- ${CI_COMMIT_SHA:0:8}-armv7
docker-stable-tag-armv7:
image: woodpeckerci/plugin-kaniko:2.3.1
when:
- event: tag
- evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
settings:
<<: *armv7_push_settings
tags:
- latest-armv7
- stable-armv7
- ${CI_COMMIT_SHA:0:8}-armv7
- ${CI_COMMIT_TAG}-armv7

View file

@ -27,7 +27,7 @@ steps:
settings: &docker_settings settings: &docker_settings
registry: "git.pleroma.social" registry: "git.pleroma.social"
image: "pleroma/pleroma" image: "pleroma/pleroma"
architectures: [amd64, arm64] architectures: [amd64, arm64, armv7]
tags: tags:
- latest - latest
- develop - develop

View file

@ -12,6 +12,7 @@ matrix:
platform: platform:
- linux/amd64 - linux/amd64
- linux/arm64 - linux/arm64
- linux/arm
# This is needed for the when clauses below. # This is needed for the when clauses below.
labels: labels:
@ -26,11 +27,19 @@ variables:
from_secret: pleroma-ci-user from_secret: pleroma-ci-user
password: password:
from_secret: pleroma-ci-password from_secret: pleroma-ci-password
kaniko_image: &kaniko_image woodpeckerci/plugin-kaniko:2.3.1 armv7_docker_variables: &armv7_docker_variables
<<: *docker_variables
build_args:
- ELIXIR_IMG=docker.io/arm32v7/elixir
- ELIXIR_TAG=1.17.3-otp-26-alpine
- ALPINE_IMG=docker.io/arm32v7/alpine
- ALPINE_VER=3.20
extra_opts: --custom-platform=linux/arm/v7
kaniko_image: &kaniko_image docker.io/woodpeckerci/plugin-kaniko:2.3.2
steps: steps:
docker-develop-amd64: docker-develop-amd64:
image: woodpeckerci/plugin-kaniko:2.3.1 image: *kaniko_image
when: when:
- evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
settings: settings:
@ -41,7 +50,7 @@ steps:
- ${CI_COMMIT_SHA:0:8}-amd64 - ${CI_COMMIT_SHA:0:8}-amd64
docker-develop-arm64: docker-develop-arm64:
image: woodpeckerci/plugin-kaniko:2.3.1 image: *kaniko_image
when: when:
- evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"' - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
settings: settings:
@ -51,6 +60,17 @@ steps:
- develop-arm64 - develop-arm64
- ${CI_COMMIT_SHA:0:8}-arm64 - ${CI_COMMIT_SHA:0:8}-arm64
docker-develop-armv7:
image: *kaniko_image
when:
- evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
settings:
<<: *armv7_docker_variables
tags:
- latest-armv7
- develop-armv7
- ${CI_COMMIT_SHA:0:8}-armv7
docker-stable-amd64: docker-stable-amd64:
image: *kaniko_image image: *kaniko_image
when: when:
@ -106,3 +126,25 @@ steps:
tags: tags:
- <<: *arm64_tags - <<: *arm64_tags
- ${CI_COMMIT_TAG}-arm64 - ${CI_COMMIT_TAG}-arm64
docker-stable-armv7:
image: *kaniko_image
when:
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
settings:
<<: *armv7_docker_variables
tags: &armv7_tags
- latest-armv7
- stable-armv7
- ${CI_COMMIT_SHA:0:8}-armv7
docker-stable-tag-armv7:
image: *kaniko_image
when:
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
settings:
<<: *armv7_docker_variables
tags:
- <<: *armv7_tags
- ${CI_COMMIT_TAG}-armv7

View file

@ -24,10 +24,11 @@ steps:
entrypoint: *script_file_entrypoint entrypoint: *script_file_entrypoint
environment: environment:
MIX_ENV: test MIX_ENV: test
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
DB_HOST: postgres DB_HOST: postgres
DB_PORT: 5432 DB_PORT: 5432
commands: commands:
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl - apk add --no-cache build-base cmake exiftool ffmpeg vips-dev file-dev git openssl
- adduser -D -h /home/testuser testuser - adduser -D -h /home/testuser testuser
- mkdir -p /home/testuser/.mix /home/testuser/.hex - mkdir -p /home/testuser/.mix /home/testuser/.hex
- chown -R testuser:testuser . /home/testuser - chown -R testuser:testuser . /home/testuser

View file

@ -19,15 +19,16 @@ variables:
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh' - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
steps: steps:
unit-testing-elixir-1.18: unit-testing-elixir-1.19:
image: elixir:1.18-otp-27-alpine image: elixir:1.19-otp-28-alpine
entrypoint: *script_file_entrypoint entrypoint: *script_file_entrypoint
environment: environment:
MIX_ENV: test MIX_ENV: test
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
DB_HOST: postgres DB_HOST: postgres
DB_PORT: 5432 DB_PORT: 5432
commands: commands:
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl - apk add --no-cache build-base cmake exiftool ffmpeg vips-dev file-dev git openssl
- adduser -D -h /home/testuser testuser - adduser -D -h /home/testuser testuser
- mkdir -p /home/testuser/.mix /home/testuser/.hex - mkdir -p /home/testuser/.mix /home/testuser/.hex
- chown -R testuser:testuser . /home/testuser - chown -R testuser:testuser . /home/testuser

View file

@ -1,8 +1,8 @@
# https://hub.docker.com/r/hexpm/elixir/tags # https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir ARG ELIXIR_IMG=hexpm/elixir
ARG ELIXIR_VER=1.17.3 ARG ELIXIR_VER=1.17.3
ARG ERLANG_VER=26.2.5.6 ARG ERLANG_VER=27.3.4.14
ARG ALPINE_VER=3.17.9 ARG ALPINE_VER=3.23.5
ARG ELIXIR_TAG=${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} ARG ELIXIR_TAG=${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER}
ARG ALPINE_IMG=alpine ARG ALPINE_IMG=alpine

View file

@ -0,0 +1 @@
Allow disabling Chats feature

View file

@ -0,0 +1 @@
Dockerfile: update base images to fix problems with SSL handshakes

View file

@ -0,0 +1 @@
Implemented ConfigDB configuration blacklist and accompanying Mix task

View file

@ -0,0 +1 @@
Fixed Elixir 1.19 warnings

View file

@ -0,0 +1 @@
Fix preferred frontend not working due to InstanceStatic plug prematurely returning HTTP 404

View file

@ -0,0 +1 @@
Fix hashtag timeline query for additional tag handling

View file

@ -0,0 +1 @@
Changed how Logger is configured due to changes in Elixir and added deprecation warnings for the old configuration format

View file

@ -0,0 +1 @@
Avoid a duplicate user query during OAuth authentication.

View file

@ -0,0 +1 @@
Avoid creating atoms from serialized PublisherWorker parameters.

1
changelog.d/rss.fix Normal file
View file

@ -0,0 +1 @@
Fix RSS feeds to be W3C compliant and render better in RSS readers

View file

@ -0,0 +1 @@
Keep followed users visible in streams when their domain is blocked.

View file

View file

@ -0,0 +1 @@
Fix inconsistent User cache keys and invalidate numeric ID aliases.

View file

@ -0,0 +1 @@
Include arm32v7 in multi-arch Docker images

View file

@ -133,13 +133,17 @@ config :pleroma, Pleroma.Web.Endpoint,
] ]
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, backends: [:console] # Primary config
config :logger, level: :debug
config :logger, :console, # Console config
level: :debug, config :logger, :default_handler, level: :debug
config :logger, :default_formatter,
format: "\n$time $metadata[$level] $message\n", format: "\n$time $metadata[$level] $message\n",
metadata: [:actor, :path, :type, :user] metadata: [:actor, :path, :type, :user]
# Syslog config
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
level: :debug, level: :debug,
ident: "pleroma", ident: "pleroma",
@ -962,6 +966,10 @@ config :pleroma, Pleroma.Search.QdrantSearch,
vectors: %{size: 384, distance: "Cosine"} vectors: %{size: 384, distance: "Cosine"}
} }
config :pleroma, :database_config_blacklist, [
{:pleroma, :logger}
]
config :pleroma, :database_config_whitelist, [ config :pleroma, :database_config_whitelist, [
{:pleroma}, {:pleroma},
{:cors_plug}, {:cors_plug},
@ -971,6 +979,8 @@ config :pleroma, :database_config_whitelist, [
{:web_push_encryption, :vapid_details} {:web_push_encryption, :vapid_details}
] ]
config :pleroma, Pleroma.Chat, enabled: true
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -2726,6 +2726,19 @@ config :pleroma, :config_description, [
} }
] ]
}, },
%{
group: :pleroma,
key: Pleroma.Chat,
type: :group,
description: "Pleroma Chat settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables the chats API."
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :http, key: :http,

View file

@ -36,7 +36,7 @@ config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Local
# different ports. # different ports.
# Do not include timestamps in development logs # Do not include timestamps in development logs
config :logger, Logger.Backends.Console, format: "$metadata[$level] $message\n" config :logger, :default_formatter, format: "$metadata[$level] $message\n"
# Set a higher stacktrace during development. Avoid configuring such # Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive. # in production as building large stacktraces may be expensive.

View file

@ -20,8 +20,7 @@ config :pleroma, Pleroma.Web.Endpoint,
config :phoenix, serve_endpoints: true config :phoenix, serve_endpoints: true
# Do not print debug messages in production # Do not print debug messages in production
config :logger, Logger.Backends.Console, level: :info config :logger, :default_handler, level: :info
config :logger, :console, level: :info
config :logger, :ex_syslogger, level: :info config :logger, :ex_syslogger, level: :info
# ## SSL Support # ## SSL Support

View file

@ -15,9 +15,11 @@ config :pleroma, Pleroma.Captcha,
method: Pleroma.Captcha.Mock method: Pleroma.Captcha.Mock
# Print only warnings and errors during test # Print only warnings and errors during test
config :logger, :console, config :logger, :default_handler, level: :warning
level: :warning,
format: "\n[$level] $message\n" config :logger, :default_formatter,
format: "\n[$level] $message\n",
colors: [enabled: false]
config :pleroma, :auth, oauth_consumer_strategies: [] config :pleroma, :auth, oauth_consumer_strategies: []

View file

@ -858,21 +858,23 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen
* ``private_key``: VAPID private key * ``private_key``: VAPID private key
## :logger ## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog * Logging to console/stdout is done by default, use `{ExSyslogger, :ex_syslogger}` to log to syslog
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed: An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
```elixir ```elixir
config :logger, config :pleroma, :logger,
backends: [{ExSyslogger, :ex_syslogger}] backends: [{ExSyslogger, :ex_syslogger}]
config :logger, default_handler: false
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
level: :warning level: :warning
``` ```
Another example, keeping console output and adding the pid to syslog output: Another example, keeping console output and adding the pid to syslog output:
```elixir ```elixir
config :logger, config :pleroma, :logger,
backends: [:console, {ExSyslogger, :ex_syslogger}] backends: [{ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
level: :warning, level: :warning,
@ -883,23 +885,22 @@ See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_s
An example of logging info to local syslog, but debug to console: An example of logging info to local syslog, but debug to console:
```elixir ```elixir
config :logger, config :pleroma, :logger,
backends: [ {ExSyslogger, :ex_syslogger}, :console ], backends: [{ExSyslogger, :ex_syslogger}]
level: :info
config :logger, :ex_syslogger, config :logger, :ex_syslogger,
level: :info, level: :info,
ident: "pleroma", ident: "pleroma",
format: "$metadata[$level] $message" format: "$metadata[$level] $message"
config :logger, :console, config :logger, :default_handler,
level: :debug, level: :debug
config :logger, :default_formatter,
format: "\n$time $metadata[$level] $message\n", format: "\n$time $metadata[$level] $message\n",
metadata: [:request_id] metadata: [:request_id]
``` ```
## Database options ## Database options
### RUM indexing for full text search ### RUM indexing for full text search

View file

@ -1,8 +1,8 @@
## Required dependencies ## Required dependencies
* PostgreSQL >=11.0 * PostgreSQL >=11.0
* Elixir >=1.15.0 <1.19 * Elixir >=1.15.0, <1.20
* Erlang OTP >=23.0.0 (supported: <28) * Erlang OTP >=24.0.0 (supported: <29)
* git * git
* file / libmagic * file / libmagic
* gcc or clang * gcc or clang

View file

@ -252,40 +252,43 @@ defmodule Mix.Tasks.Pleroma.Config do
if whitelisted_configs in [nil, false] do if whitelisted_configs in [nil, false] do
shell_error("No unwanted settings in ConfigDB. No changes made.") shell_error("No unwanted settings in ConfigDB. No changes made.")
else else
whitelisted_groups = {whitelisted_groups, whitelisted_keys} = get_filters(whitelisted_configs)
whitelisted_configs
|> Enum.filter(fn
{_group} -> true
_ -> false
end)
|> Enum.map(fn {group} -> group end)
whitelisted_keys =
whitelisted_configs
|> Enum.filter(fn
{_group, _key} -> true
_ -> false
end)
filtered = filtered =
from(c in ConfigDB) from(c in ConfigDB)
|> Repo.all() |> Repo.all()
|> Enum.filter(&not_whitelisted?(&1, whitelisted_groups, whitelisted_keys)) |> Enum.filter(&not_whitelisted?(&1, whitelisted_groups, whitelisted_keys))
if not Enum.empty?(filtered) do do_config_filter(filtered, force)
shell_info("The following settings will be removed from ConfigDB:\n") end
Enum.each(filtered, &dump(&1)) end
if force or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do # Removes blacklisted configuration sections
filtered_ids = Enum.map(filtered, fn %{id: id} -> id end) def run(["filter_blacklisted" | rest]) do
{options, [], []} =
OptionParser.parse(
rest,
strict: [force: :boolean],
aliases: [f: :force]
)
Repo.delete_all(from(c in ConfigDB, where: c.id in ^filtered_ids)) force = Keyword.get(options, :force, false)
else
shell_error("No changes made.") start_pleroma()
end
else blacklisted_configs = Pleroma.Config.get(:database_config_blacklist)
shell_error("No unwanted settings in ConfigDB. No changes made.")
end if blacklisted_configs in [nil, false] do
shell_error("No unwanted settings in ConfigDB. No changes made.")
else
{blacklisted_groups, blacklisted_keys} = get_filters(blacklisted_configs)
filtered =
from(c in ConfigDB)
|> Repo.all()
|> Enum.filter(&blacklisted?(&1, blacklisted_groups, blacklisted_keys))
do_config_filter(filtered, force)
end end
end end
@ -490,8 +493,49 @@ defmodule Mix.Tasks.Pleroma.Config do
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
end end
defp get_filters(config) do
groups =
config
|> Enum.filter(fn
{_group} -> true
_ -> false
end)
|> Enum.map(fn {group} -> group end)
keys =
config
|> Enum.filter(fn
{_group, _key} -> true
_ -> false
end)
{groups, keys}
end
defp not_whitelisted?(%{group: group, key: key}, whitelisted_groups, whitelisted_keys) do defp not_whitelisted?(%{group: group, key: key}, whitelisted_groups, whitelisted_keys) do
not Enum.member?(whitelisted_groups, group) and not Enum.member?(whitelisted_groups, group) and
not Enum.member?(whitelisted_keys, {group, key}) not Enum.member?(whitelisted_keys, {group, key})
end end
defp blacklisted?(%{group: group, key: key}, blacklisted_groups, blacklisted_keys) do
Enum.member?(blacklisted_groups, group) or
Enum.member?(blacklisted_keys, {group, key})
end
defp do_config_filter(filtered, _) when length(filtered) == 0 do
shell_error("No unwanted settings in ConfigDB. No changes made.")
end
defp do_config_filter(filtered, force) do
shell_info("The following settings will be removed from ConfigDB:\n")
Enum.each(filtered, &dump(&1))
if force or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
filtered_ids = Enum.map(filtered, fn %{id: id} -> id end)
Repo.delete_all(from(c in ConfigDB, where: c.id in ^filtered_ids))
else
shell_error("No changes made.")
end
end
end end

View file

@ -43,6 +43,7 @@ defmodule Pleroma.Application do
# every time the application is restarted, so we disable module # every time the application is restarted, so we disable module
# conflicts at runtime # conflicts at runtime
Code.compiler_options(ignore_module_conflict: true) Code.compiler_options(ignore_module_conflict: true)
configure_logger()
Pleroma.Telemetry.Logger.attach() Pleroma.Telemetry.Logger.attach()
Config.Holder.save_default() Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers() Pleroma.HTML.compile_scrubbers()
@ -113,6 +114,44 @@ defmodule Pleroma.Application do
Supervisor.start_link(children, opts) Supervisor.start_link(children, opts)
end end
def configure_logger do
Config.get([:logger, :backends], [])
|> Enum.each(fn backend ->
case backend_to_logger(backend) do
{:ok, logger} ->
add_logger(logger)
{:error, :console_backend} ->
Logger.warning(":console is no longer considered a backend and is enabled by default")
end
end)
end
defp add_logger(backend) do
case LoggerBackends.add(backend) do
{:ok, _} ->
Logger.debug("Successfully added logger backend: #{inspect(backend)}")
{:error, :already_present} ->
:ok
{:error, reason} ->
Logger.error("Failed to add logger backend #{inspect(backend)}: #{inspect(reason)}")
end
end
defp backend_to_logger(:console), do: {:error, :console_backend}
defp backend_to_logger({:ex_syslogger = backend, name}) do
Logger.warning(
"Configuration {:#{backend}, :#{name}} is incorrect. Use {ExSyslogger, :#{name}} instead!"
)
{:ok, {ExSyslogger, name}}
end
defp backend_to_logger(backend), do: {:ok, backend}
def load_custom_modules do def load_custom_modules do
dir = Config.get([:modules, :runtime_dir]) dir = Config.get([:modules, :runtime_dir])

View file

@ -108,4 +108,6 @@ defmodule Pleroma.Chat do
|> cast(%{pinned: false}, [:pinned]) |> cast(%{pinned: false}, [:pinned])
|> Repo.update() |> Repo.update()
end end
def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], true)
end end

View file

@ -20,6 +20,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"} "\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
] ]
@logger_formatter_config_knobs [:colors, :format, :metadata, :truncate, :utc_log]
def check_exiftool_filter do def check_exiftool_filter do
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
@ -218,7 +220,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_quarantined_instances_tuples(), check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(), check_transparency_exclusions_tuples(),
check_simple_policy_tuples(), check_simple_policy_tuples(),
check_exiftool_filter() check_exiftool_filter(),
check_deprecated_logger_config()
] ]
|> Enum.reduce(:ok, fn |> Enum.reduce(:ok, fn
:ok, :ok -> :ok :ok, :ok -> :ok
@ -415,4 +418,72 @@ defmodule Pleroma.Config.DeprecationWarnings do
:ok :ok
end end
end end
defp merge_deprecated_logger_config(config) do
formatter_config = Enum.filter(config, fn {k, _} -> k in @logger_formatter_config_knobs end)
formatter = Logger.default_formatter(formatter_config)
log_level = Keyword.get(config, :level, nil)
Logger.debug("""
Reconfiguring console Logger with deprecated configuration syntax.
Handler configuration:
log_level: #{inspect(log_level)}
Formatter:
#{inspect(formatter)}
""")
if Config.get(:env) != :test do
if log_level, do: :logger.update_handler_config(:default, :level, log_level)
:logger.set_handler_config(:default, :formatter, formatter)
end
end
@spec check_deprecated_logger_config() :: :ok | :error
def check_deprecated_logger_config do
backends_config = Application.get_env(:logger, :backends)
console_config = Application.get_env(:logger, :console)
# NOTE: No need to merge the old backends config since it still works.
# And new configuration will just add new Logger backends.
backend =
if backends_config do
Logger.warning("""
!!!DEPRECATION WARNING!!!
Your configuration is using deprecated syntax for configuring backends of Elixir's logger.
`config :logger, backends: [...]` is deprecated syntax due to changes in Elixir.
Please update your configuration at your earliest convenience to use:
`config :pleroma, :logger, backends: [...]`
Note: `:console` is no longer considered a backend and is used by default, you can disable it using:
`config :logger, :default_handler: false`
""")
true
else
false
end
console =
if console_config do
Logger.warning("""
!!!DEPRECATION WARNING!!!
Your configuration is using deprecated syntax for configuring logging to console.
`config :logger, :console` is deprecated syntax due to changes in Elixir.
Please update your configuration at your earliest convenience to use
`config :logger, default_handler` and `config :logger, :default_formatter`.
Note: `:default_handler` is used only for the `level` setting. All other configurations go under
`:default_formatter`. For more info visit: https://hexdocs.pm/logger/Logger.html#module-backends-and-backwards-compatibility
""")
merge_deprecated_logger_config(console_config)
true
else
false
end
if backend or console, do: :error, else: :ok
end
end end

View file

@ -51,7 +51,7 @@ defmodule Pleroma.Config.TransferTask do
started_applications = Application.started_applications() started_applications = Application.started_applications()
reject = [nil, :postgrex] reject = [nil, :postgrex, :logger]
reject = reject =
if restart_pleroma? do if restart_pleroma? do

View file

@ -12,13 +12,27 @@ defmodule Pleroma.Docs.Generator do
@spec list_behaviour_implementations(behaviour :: module()) :: [module()] @spec list_behaviour_implementations(behaviour :: module()) :: [module()]
def list_behaviour_implementations(behaviour) do def list_behaviour_implementations(behaviour) do
:code.all_loaded() # :code.all_loaded() no longer works on Elixir 1.19 and newer since modules are lazily loaded.
|> Enum.filter(fn {module, _} -> # We can get a complete list of modules by interating over Application.loaded_applications/0,
# but that includes every module which would get loaded below, which is very slow.
# Hardcode the Pleroma application instead, which is good enough for now.
application_modules =
case :application.get_key(:pleroma, :modules) do
{:ok, modules} -> modules
:undefined -> []
end
# At compile time or in tests some modules will be outside of the Pleroma application.
loaded_modules = Enum.map(:code.all_loaded(), fn {module, _} -> module end)
(application_modules ++ loaded_modules)
|> Enum.uniq()
|> Enum.filter(fn module ->
Code.ensure_loaded(module)
# This shouldn't be needed as all modules are expected to have module_info/1, # This shouldn't be needed as all modules are expected to have module_info/1,
# but in test environments some transient modules `:elixir_compiler_XX` # but in test environments some transient modules `:elixir_compiler_XX`
# are loaded for some reason (where XX is a random integer). # are loaded for some reason (where XX is a random integer).
Code.ensure_loaded(module)
if function_exported?(module, :module_info, 1) do if function_exported?(module, :module_info, 1) do
module.module_info(:attributes) module.module_info(:attributes)
|> Keyword.get_values(:behaviour) |> Keyword.get_values(:behaviour)
@ -26,7 +40,6 @@ defmodule Pleroma.Docs.Generator do
|> Enum.member?(behaviour) |> Enum.member?(behaviour)
end end
end) end)
|> Enum.map(fn {module, _} -> module end)
end end
@doc """ @doc """

View file

@ -272,13 +272,13 @@ defmodule Pleroma.User do
end end
def cached_blocked_users_ap_ids(user) do def cached_blocked_users_ap_ids(user) do
@cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ -> @cachex.fetch!(:user_cache, cache_key(:blocked_users_ap_ids, user), fn _ ->
blocked_users_ap_ids(user) blocked_users_ap_ids(user)
end) end)
end end
def cached_muted_users_ap_ids(user) do def cached_muted_users_ap_ids(user) do
@cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ -> @cachex.fetch!(:user_cache, cache_key(:muted_users_ap_ids, user), fn _ ->
muted_users_ap_ids(user) muted_users_ap_ids(user)
end) end)
end end
@ -1253,13 +1253,18 @@ defmodule Pleroma.User do
get_cached_by_nickname(nickname) get_cached_by_nickname(nickname)
end end
defp cache_key(:id, %User{id: id}), do: cache_key(:id, id)
defp cache_key(:nickname, %User{nickname: nickname}), do: cache_key(:nickname, nickname)
defp cache_key(namespace, %User{ap_id: ap_id}), do: cache_key(namespace, ap_id)
defp cache_key(namespace, value), do: "#{namespace}:#{value}"
def set_cache({:ok, user}), do: set_cache(user) def set_cache({:ok, user}), do: set_cache(user)
def set_cache({:error, err}), do: {:error, err} def set_cache({:error, err}), do: {:error, err}
def set_cache(%User{} = user) do def set_cache(%User{} = user) do
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) @cachex.put(:user_cache, cache_key(:ap_id, user), user)
@cachex.put(:user_cache, "nickname:#{user.nickname}", user) @cachex.put(:user_cache, cache_key(:nickname, user), user)
@cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user)) @cachex.put(:user_cache, cache_key(:friends_ap_ids, user), get_user_friends_ap_ids(user))
{:ok, user} {:ok, user}
end end
@ -1287,22 +1292,23 @@ defmodule Pleroma.User do
@spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()] @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
def get_cached_user_friends_ap_ids(user) do def get_cached_user_friends_ap_ids(user) do
@cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ -> @cachex.fetch!(:user_cache, cache_key(:friends_ap_ids, user), fn _ ->
get_user_friends_ap_ids(user) get_user_friends_ap_ids(user)
end) end)
end end
def invalidate_cache(user) do def invalidate_cache(user) do
@cachex.del(:user_cache, "ap_id:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:ap_id, user))
@cachex.del(:user_cache, "nickname:#{user.nickname}") @cachex.del(:user_cache, cache_key(:nickname, user))
@cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:id, user))
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:friends_ap_ids, user))
@cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:blocked_users_ap_ids, user))
@cachex.del(:user_cache, cache_key(:muted_users_ap_ids, user))
end end
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
def get_cached_by_ap_id(ap_id) do def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}" key = cache_key(:ap_id, ap_id)
with {:ok, nil} <- @cachex.get(:user_cache, key), with {:ok, nil} <- @cachex.get(:user_cache, key),
user when not is_nil(user) <- get_by_ap_id(ap_id), user when not is_nil(user) <- get_by_ap_id(ap_id),
@ -1315,14 +1321,14 @@ defmodule Pleroma.User do
end end
def get_cached_by_id(id) do def get_cached_by_id(id) do
key = "id:#{id}" key = cache_key(:id, id)
ap_id = ap_id =
@cachex.fetch!(:user_cache, key, fn _ -> @cachex.fetch!(:user_cache, key, fn _ ->
user = get_by_id(id) user = get_by_id(id)
if user do if user do
@cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) @cachex.put(:user_cache, cache_key(:ap_id, user), user)
{:commit, user.ap_id} {:commit, user.ap_id}
else else
{:ignore, ""} {:ignore, ""}
@ -1333,7 +1339,7 @@ defmodule Pleroma.User do
end end
def get_cached_by_nickname(nickname) do def get_cached_by_nickname(nickname) do
key = "nickname:#{nickname}" key = cache_key(:nickname, nickname)
@cachex.fetch!(:user_cache, key, fn _ -> @cachex.fetch!(:user_cache, key, fn _ ->
case get_or_fetch_by_nickname(nickname) do case get_or_fetch_by_nickname(nickname) do
@ -1651,7 +1657,7 @@ defmodule Pleroma.User do
|> Oban.insert() |> Oban.insert()
end end
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") @cachex.del(:user_cache, cache_key(:muted_users_ap_ids, muter))
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)} {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
end end
@ -1661,7 +1667,7 @@ defmodule Pleroma.User do
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee), with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
{:ok, user_notification_mute} <- {:ok, user_notification_mute} <-
UserRelationship.delete_notification_mute(muter, mutee) do UserRelationship.delete_notification_mute(muter, mutee) do
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") @cachex.del(:user_cache, cache_key(:muted_users_ap_ids, muter))
{:ok, [user_mute, user_notification_mute]} {:ok, [user_mute, user_notification_mute]}
end end
end end
@ -2815,7 +2821,7 @@ defmodule Pleroma.User do
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()} {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked, expires_at) do defp add_to_block(%User{} = user, %User{} = blocked, expires_at) do
with {:ok, relationship} <- UserRelationship.create_block(user, blocked, expires_at) do with {:ok, relationship} <- UserRelationship.create_block(user, blocked, expires_at) do
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:blocked_users_ap_ids, user))
{:ok, relationship} {:ok, relationship}
end end
end end
@ -2824,7 +2830,7 @@ defmodule Pleroma.User do
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do defp remove_from_block(%User{} = user, %User{} = blocked) do
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}") @cachex.del(:user_cache, cache_key(:blocked_users_ap_ids, user))
{:ok, relationship} {:ok, relationship}
end end
end end

View file

@ -893,14 +893,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Repo.all() |> Repo.all()
# Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan # Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan
from( query =
[_activity, object] in query, from(
join: hto in "hashtags_objects", [_activity, object] in query,
on: hto.object_id == object.id, join: hto in "hashtags_objects",
where: hto.hashtag_id in ^hashtag_ids, on: hto.object_id == object.id,
distinct: [desc: object.id], where: hto.hashtag_id in ^hashtag_ids
order_by: [desc: object.id] )
)
if is_nil(query.distinct) do
from([_activity, object] in query,
distinct: [desc: object.id],
order_by: [desc: object.id]
)
else
query
end
end end
defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
@moduledoc "Adds expiration to all local Create activities" @moduledoc "Adds expiration to all local Create activities"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(activity) do def filter(activity) do
activity = activity =

View file

@ -3,11 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@moduledoc "Prevent followbots from following with a bit of heuristic"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.User alias Pleroma.User
@moduledoc "Prevent followbots from following with a bit of heuristic" use Pleroma.Web.ActivityPub.MRF.Policy
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
# XXX: this should become User.normalize_by_ap_id() or similar, really. # XXX: this should become User.normalize_by_ap_id() or similar, really.
defp normalize_by_ap_id(%{"id" => id}), do: User.get_cached_by_ap_id(id) defp normalize_by_ap_id(%{"id" => id}), do: User.get_cached_by_ap_id(id)

View file

@ -3,9 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
require Logger require Logger

View file

@ -3,11 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
require Pleroma.Constants require Pleroma.Constants
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
defp user_has_posted?(%User{} = u), do: u.note_count > 0 defp user_has_posted?(%User{} = u), do: u.note_count > 0

View file

@ -3,10 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
require Logger
@moduledoc "Drop and log everything received" @moduledoc "Drop and log everything received"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Logger
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(activity) do def filter(activity) do
Logger.debug("REJECTING #{inspect(activity)}") Logger.debug("REJECTING #{inspect(activity)}")

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.Object.Updater alias Pleroma.Object.Updater
@ -10,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
@moduledoc "Reject or force-unlisted emojis with certain URLs or names" @moduledoc "Reject or force-unlisted emojis with certain URLs or names"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
defp config_remove_url do defp config_remove_url do
Pleroma.Config.get([:mrf_emoji, :remove_url], []) Pleroma.Config.get([:mrf_emoji, :remove_url], [])

View file

@ -3,11 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
alias Pleroma.Object
@moduledoc "Ensure a re: is prepended on replies to a post with a Subject" @moduledoc "Ensure a re: is prepended on replies to a post with a Subject"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Object
use Pleroma.Web.ActivityPub.MRF.Policy
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless]) @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
def history_awareness, do: :auto def history_awareness, do: :auto

View file

@ -4,12 +4,15 @@
defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
require Logger require Logger
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(activity) do def filter(activity) do
with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]), with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),

View file

@ -3,9 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@moduledoc "Remove bot posts from federated timeline" @moduledoc "Remove bot posts from federated timeline"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.User
use Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants

View file

@ -3,13 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
defp get_author(url) do defp get_author(url) do
with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false), with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false),

View file

@ -3,13 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def history_awareness, do: :auto def history_awareness, do: :auto

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.Config alias Pleroma.Config
@ -14,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists. Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
""" """
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def history_awareness, do: :manual def history_awareness, do: :manual

View file

@ -3,13 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@moduledoc "Block activities with too much mentions (configurable)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.User alias Pleroma.User
require Pleroma.Constants require Pleroma.Constants
@moduledoc "Block activities with too much mentions (configurable)" use Pleroma.Web.ActivityPub.MRF.Policy
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp delist_activity(activity, threshold) when threshold > 0 do defp delist_activity(activity, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
@moduledoc "Force a quote line into the message content." @moduledoc "Force a quote line into the message content."
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
defp build_inline_quote(template, url) do defp build_inline_quote(template, url) do
quote_line = String.replace(template, "{url}", "<a href=\"#{url}\">#{url}</a>") quote_line = String.replace(template, "{url}", "<a href=\"#{url}\">#{url}</a>")

View file

@ -3,13 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@moduledoc "Reject or Word-Replace activities with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.Web.ActivityPub.MRF.Utils alias Pleroma.Web.ActivityPub.MRF.Utils
@moduledoc "Reject or Word-Replace activities with a keyword or regex" use Pleroma.Web.ActivityPub.MRF.Policy
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp string_matches?(string, pattern) when is_binary(pattern) do defp string_matches?(string, pattern) when is_binary(pattern) do
String.contains?(string, pattern) String.contains?(string, pattern)

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
@moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them" @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.HTTP alias Pleroma.HTTP
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy

View file

@ -4,9 +4,10 @@
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@moduledoc "Block activities which mention a user" @moduledoc "Block activities which mention a user"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(%{"type" => "Create"} = activity) do def filter(%{"type" => "Create"} = activity) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
@moduledoc "Filter local activities which have no content" @moduledoc "Filter local activities which have no content"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
@impl true @impl true

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@moduledoc "Does nothing (lets the messages go through unmodified)" @moduledoc "Does nothing (lets the messages go through unmodified)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(activity) do def filter(activity) do
{:ok, activity} {:ok, activity}

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
@moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)" @moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def history_awareness, do: :auto def history_awareness, do: :auto

View file

@ -4,9 +4,11 @@
defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@moduledoc "Scrub configured hypertext markup" @moduledoc "Scrub configured hypertext markup"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.HTML alias Pleroma.HTML
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def history_awareness, do: :auto def history_awareness, do: :auto

View file

@ -38,6 +38,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
- `unlist`: Unlist all detected NSFW content? Default: `false` - `unlist`: Unlist all detected NSFW content? Default: `false`
- `reject`: Reject all detected NSFW content (takes precedence)? Default: `false` - `reject`: Reject all detected NSFW content (takes precedence)? Default: `false`
""" """
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Constants alias Pleroma.Constants
alias Pleroma.HTTP alias Pleroma.HTTP
@ -46,7 +48,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
require Logger require Logger
require Pleroma.Constants require Pleroma.Constants
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
@policy :mrf_nsfw_api @policy :mrf_nsfw_api
def build_request_url(url) do def build_request_url(url) do

View file

@ -3,13 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@moduledoc "Filter activities depending on their age"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
require Pleroma.Constants require Pleroma.Constants
@moduledoc "Filter activities depending on their age" use Pleroma.Web.ActivityPub.MRF.Policy
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp check_date(%{"object" => %{"published" => published}} = activity) do defp check_date(%{"object" => %{"published" => published}} = activity) do
with %DateTime{} = now <- DateTime.utc_now(), with %DateTime{} = now <- DateTime.utc_now(),

View file

@ -15,4 +15,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.Policy do
} }
@callback history_awareness() :: :auto | :manual @callback history_awareness() :: :auto | :manual
@optional_callbacks config_description: 0, history_awareness: 0, id_filter: 1 @optional_callbacks config_description: 0, history_awareness: 0, id_filter: 1
defmacro __using__(_) do
quote do
# Otherwise Pleroma.Docs.Generator.list_behaviour_implementations fails
# since the Elixir compiler doesn't automatically load the module since v1.19.
@compile {:autoload, true}
end
end
end end

View file

@ -6,11 +6,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
@moduledoc """ @moduledoc """
QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread. QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread.
""" """
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def history_awareness, do: :auto def history_awareness, do: :auto

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
@moduledoc "Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)" @moduledoc "Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
require Pleroma.Constants require Pleroma.Constants

View file

@ -4,11 +4,12 @@
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@moduledoc "Rejects non-public (followers-only, direct) activities" @moduledoc "Rejects non-public (followers-only, direct) activities"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants

View file

@ -2,6 +2,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
@moduledoc "Drop remote reports if they don't contain enough information." @moduledoc "Drop remote reports if they don't contain enough information."
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
@impl true @impl true

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@moduledoc "Filter activities depending on their origin instance" @moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
alias Pleroma.User alias Pleroma.User

View file

@ -3,12 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
@moduledoc "Detect new emojis by their shortcode and steals them"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
require Logger require Logger
alias Pleroma.Config alias Pleroma.Config
@moduledoc "Detect new emojis by their shortcode and steals them" use Pleroma.Web.ActivityPub.MRF.Policy
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], []) defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], [])

View file

@ -3,12 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
require Logger require Logger
@behaviour Pleroma.Web.ActivityPub.MRF.Policy use Pleroma.Web.ActivityPub.MRF.Policy
defp lookup_subchain(actor) do defp lookup_subchain(actor) do
with matches <- Config.get([:mrf_subchain, :match_actor]), with matches <- Config.get([:mrf_subchain, :match_actor]),

View file

@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@moduledoc """ @moduledoc """
Apply policies based on user tags Apply policies based on user tags
@ -18,6 +16,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
- `mrf_tag:disable-remote-subscription`: Reject non-local follow requests - `mrf_tag:disable-remote-subscription`: Reject non-local follow requests
- `mrf_tag:disable-any-subscription`: Reject any follow requests - `mrf_tag:disable-any-subscription`: Reject any follow requests
""" """
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.User
use Pleroma.Web.ActivityPub.MRF.Policy
require Pleroma.Constants require Pleroma.Constants

View file

@ -3,11 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
alias Pleroma.Config
@moduledoc "Accept-list of users from specified instances" @moduledoc "Accept-list of users from specified instances"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.Config
use Pleroma.Web.ActivityPub.MRF.Policy
defp filter_by_list(activity, []), do: {:ok, activity} defp filter_by_list(activity, []), do: {:ok, activity}
defp filter_by_list(%{"actor" => actor} = activity, allow_list) do defp filter_by_list(%{"actor" => actor} = activity, allow_list) do

View file

@ -4,9 +4,10 @@
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@moduledoc "Filter activities which belong to certain activity vocabularies" @moduledoc "Filter activities which belong to certain activity vocabularies"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
use Pleroma.Web.ActivityPub.MRF.Policy
@impl true @impl true
def filter(%{"type" => "Undo", "object" => object} = activity) do def filter(%{"type" => "Undo", "object" => object} = activity) do
with {:ok, _} <- filter(object) do with {:ok, _} <- filter(object) do

View file

@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
capabilities = capabilities =
if is_boolean(user.accepts_chat_messages) do if is_boolean(user.accepts_chat_messages) do
%{ %{
"acceptsChatMessages" => user.accepts_chat_messages "acceptsChatMessages" => Pleroma.Chat.enabled?() and user.accepts_chat_messages
} }
else else
%{} %{}

View file

@ -71,7 +71,10 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
end end
def descriptions(conn, _params) do def descriptions(conn, _params) do
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1) descriptions =
Pleroma.Docs.JSON.compiled_descriptions()
|> Enum.filter(&allowed_config?(&1, :whitelist))
|> Enum.reject(&allowed_config?(&1, :blacklist))
json(conn, translate_descriptions(descriptions)) json(conn, translate_descriptions(descriptions))
end end
@ -132,7 +135,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
with :ok <- configurable_from_database() do with :ok <- configurable_from_database() do
results = results =
configs configs
|> Enum.filter(&whitelisted_config?/1) |> Enum.filter(&allowed_config?(&1, :whitelist))
|> Enum.reject(&allowed_config?(&1, :blacklist))
|> Enum.map(fn |> Enum.map(fn
%{group: group, key: key, delete: true} = params -> %{group: group, key: key, delete: true} = params ->
ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]}) ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
@ -174,27 +178,34 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
end end
end end
defp whitelisted_config?(":pleroma", ":database_config_whitelist"), do: false defp allowed_config?(":pleroma", ":database_config_whitelist", _), do: true
defp allowed_config?(":pleroma", ":database_config_blacklist", _), do: true
defp whitelisted_config?(group, key) do defp allowed_config?(group, key, mode) do
if whitelisted_configs = Config.get(:database_config_whitelist) do configs =
Enum.any?(whitelisted_configs, fn case mode do
{whitelisted_group} -> :whitelist -> Config.get(:database_config_whitelist)
group == inspect(whitelisted_group) :blacklist -> Config.get(:database_config_blacklist)
end
{whitelisted_group, whitelisted_key} -> if configs do
group == inspect(whitelisted_group) && key == inspect(whitelisted_key) Enum.any?(configs, fn
{allowed_group} ->
group == inspect(allowed_group)
{allowed_group, allowed_key} ->
group == inspect(allowed_group) && key == inspect(allowed_key)
end) end)
else else
true true
end end
end end
defp whitelisted_config?(%{group: group, key: key}) do defp allowed_config?(%{group: group, key: key}, mode) do
whitelisted_config?(group, key) allowed_config?(group, key, mode)
end end
defp whitelisted_config?(%{group: group} = config) do defp allowed_config?(%{group: group} = config, mode) do
whitelisted_config?(group, config[:key]) allowed_config?(group, config[:key], mode)
end end
end end

View file

@ -31,7 +31,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The updated chat", "The updated chat",
"application/json", "application/json",
Chat Chat
) ),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -56,7 +57,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The read ChatMessage", "The read ChatMessage",
"application/json", "application/json",
ChatMessage ChatMessage
) ),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -87,7 +89,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The existing chat", "The existing chat",
"application/json", "application/json",
Chat Chat
) ),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -118,7 +121,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The created or existing chat", "The created or existing chat",
"application/json", "application/json",
Chat Chat
) ),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -146,7 +150,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
Operation.parameter(:pinned, :query, BooleanLike.schema(), "Include only pinned chats") Operation.parameter(:pinned, :query, BooleanLike.schema(), "Include only pinned chats")
], ],
responses: %{ responses: %{
200 => Operation.response("The chats of the user", "application/json", chats_response()) 200 => Operation.response("The chats of the user", "application/json", chats_response()),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -172,7 +177,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
| pagination_params() | pagination_params()
], ],
responses: %{ responses: %{
200 => Operation.response("The chats of the user", "application/json", chats_response()) 200 => Operation.response("The chats of the user", "application/json", chats_response()),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -224,6 +230,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
ChatMessage ChatMessage
), ),
400 => Operation.response("Bad Request", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError),
422 => Operation.response("MRF Rejection", "application/json", ApiError) 422 => Operation.response("MRF Rejection", "application/json", ApiError)
}, },
security: [ security: [
@ -249,7 +256,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The deleted ChatMessage", "The deleted ChatMessage",
"application/json", "application/json",
ChatMessage ChatMessage
) ),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -268,7 +276,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
Operation.parameter(:id, :path, :string, "The id of the chat", required: true) Operation.parameter(:id, :path, :string, "The id of the chat", required: true)
], ],
responses: %{ responses: %{
200 => Operation.response("The existing chat", "application/json", Chat) 200 => Operation.response("The existing chat", "application/json", Chat),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{
@ -287,7 +296,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
Operation.parameter(:id, :path, :string, "The id of the chat", required: true) Operation.parameter(:id, :path, :string, "The id of the chat", required: true)
], ],
responses: %{ responses: %{
200 => Operation.response("The existing chat", "application/json", Chat) 200 => Operation.response("The existing chat", "application/json", Chat),
404 => Operation.response("Not Found", "application/json", ApiError)
}, },
security: [ security: [
%{ %{

View file

@ -139,6 +139,8 @@ defmodule Pleroma.Web.Endpoint do
from: {:pleroma, "priv/static/adminfe/"} from: {:pleroma, "priv/static/adminfe/"}
) )
plug(Pleroma.Web.Plugs.StaticNotFoundPlug)
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint. # :code_reloader configuration of your endpoint.
if code_reloading? do if code_reloading? do

View file

@ -89,7 +89,8 @@ defmodule Pleroma.Web.Federator do
# Job Worker Callbacks # Job Worker Callbacks
@spec perform(atom(), any()) :: {:ok, any()} | {:error, any()} @impl true
@spec perform(atom(), any()) :: :ok | {:ok, any()} | {:error, any()}
def perform(:publish_one, params) do def perform(:publish_one, params) do
Publisher.prepare_one(params) Publisher.prepare_one(params)
|> Publisher.publish_one() |> Publisher.publish_one()

View file

@ -3,5 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator.Publishing do defmodule Pleroma.Web.Federator.Publishing do
@callback perform(atom(), any()) :: :ok | {:ok, any()} | {:error, any()}
@callback publish(map()) :: any() @callback publish(map()) :: any()
end end

View file

@ -38,6 +38,20 @@ defmodule Pleroma.Web.Feed.FeedView do
end end
end end
def most_recent_update([], :rss), do: nil
def most_recent_update(activities, :rss) do
activities
|> Enum.map(fn activity ->
case activity.object do
%Object{updated_at: updated_at} -> updated_at
_ -> activity.updated_at
end
end)
|> Enum.max_by(&NaiveDateTime.to_erl/1)
|> to_rfc2822()
end
def most_recent_update(activities, user, :atom) do def most_recent_update(activities, user, :atom) do
(List.first(activities) || user).updated_at (List.first(activities) || user).updated_at
|> to_rfc3339() |> to_rfc3339()
@ -97,6 +111,67 @@ defmodule Pleroma.Web.Feed.FeedView do
end end
end end
def rss_content_encoded(data) do
data
|> feed_content_encoded()
|> String.replace("]]>", "]]&gt;")
end
def atom_content_encoded(data), do: feed_content_encoded(data)
defp feed_content_encoded(data) do
base_content =
case activity_content(data) do
"" ->
(data["summary"] || data["type"] || "")
|> escape()
content ->
content
end
attachments_html =
if sensitive?(data) do
""
else
(data["attachment"] || [])
|> Enum.map(&rss_attachment_preview/1)
|> Enum.reject(&(&1 == ""))
|> Enum.join("<br/><br/>")
end
[base_content, attachments_html]
|> Enum.reject(&(&1 == ""))
|> Enum.join("<br/><br/>")
end
defp rss_attachment_preview(attachment) do
href = attachment_href(attachment)
if is_binary(href) and href != "" do
media_type = attachment_type(attachment) || "application/octet-stream"
escaped_href = escape(href)
name = attachment["name"] || "Attachment"
escaped_name = escape(name)
cond do
String.starts_with?(media_type, "image/") ->
~s(<p><img src="#{escaped_href}" alt="#{escaped_name}" loading="lazy"/></p>)
String.starts_with?(media_type, "video/") ->
~s(<p><video controls preload="metadata" src="#{escaped_href}"></video></p><p><a href="#{escaped_href}">#{escaped_name}</a></p>)
String.starts_with?(media_type, "audio/") ->
~s(<p><audio controls preload="metadata" src="#{escaped_href}"></audio></p><p><a href="#{escaped_href}">#{escaped_name}</a></p>)
true ->
~s(<p><a href="#{escaped_href}">#{escaped_name}</a></p>)
end
else
""
end
end
def activity_content(%{"content" => content}) do def activity_content(%{"content" => content}) do
content content
|> String.replace(~r/[\n\r]/, "") |> String.replace(~r/[\n\r]/, "")
@ -106,16 +181,104 @@ defmodule Pleroma.Web.Feed.FeedView do
def activity_context(activity), do: escape(activity.data["context"]) def activity_context(activity), do: escape(activity.data["context"])
def sensitive?(data), do: data["sensitive"] in [true, "true"]
def feed_self_url(conn) do
conn
|> Phoenix.Controller.current_url()
|> escape()
end
def activity_link(activity, data) do
cond do
activity.local -> data["id"] || data["url"]
true -> data["external_url"] || data["id"] || data["url"]
end
end
def attachment_href(attachment) do def attachment_href(attachment) do
attachment["url"] attachment
|> hd() |> attachment_url_data()
|> Map.get("href") |> Map.get("href")
end end
def attachment_type(attachment) do def attachment_type(attachment) do
attachment["url"] attachment
|> hd() |> attachment_url_data()
|> Map.get("mediaType") |> Map.get("mediaType") || attachment["mediaType"]
end
def attachment_size(attachment) do
attachment
|> attachment_url_data()
|> Map.get("size", 0)
end
def attachment_size_positive(attachment) do
case attachment_size(attachment) do
size when is_integer(size) and size > 0 ->
size
size when is_binary(size) ->
case Integer.parse(size) do
{parsed, ""} when parsed > 0 -> parsed
_ -> nil
end
_ ->
nil
end
end
def attachment_medium(attachment) do
case attachment_type(attachment) do
"image/" <> _rest -> "image"
"video/" <> _rest -> "video"
"audio/" <> _rest -> "audio"
_ -> "document"
end
end
def rss_enclosure_attachment(attachments) do
Enum.find(attachments || [], &(not is_nil(attachment_size_positive(&1))))
end
def attachment_description(attachment) do
attachment["name"] || ""
end
def media_content_xml(_attachment, sensitive) when sensitive in [true, "true"], do: ""
def media_content_xml(attachment, _sensitive) do
href = escape(attachment_href(attachment) || "")
type = escape(attachment_type(attachment) || "application/octet-stream")
medium = escape(attachment_medium(attachment))
file_size_attr =
case attachment_size_positive(attachment) do
size when is_integer(size) -> ~s( fileSize="#{size}")
_ -> ""
end
case attachment_description(attachment) do
"" ->
~s(<media:content url="#{href}" type="#{type}"#{file_size_attr} medium="#{medium}"/>\n)
description ->
"""
<media:content url="#{href}" type="#{type}"#{file_size_attr} medium="#{medium}">
<media:description type="plain">#{escape(description)}</media:description>
</media:content>
"""
end
end
defp attachment_url_data(attachment) do
case attachment["url"] do
[first | _] when is_map(first) -> first
%{} = map -> map
_ -> %{}
end
end end
def get_href(id) do def get_href(id) do

View file

@ -716,7 +716,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end end
end end
defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do defp put_application(
params,
%{assigns: %{token: %Token{} = token, user: %User{} = user}} = _conn
) do
if user.disclose_client do if user.disclose_client do
%{client_name: client_name, website: website} = Repo.preload(token, :app).app %{client_name: client_name, website: website} = Repo.preload(token, :app).app
Map.put(params, :generator, %{type: "Application", name: client_name, url: website}) Map.put(params, :generator, %{type: "Application", name: client_name, url: website})

View file

@ -385,6 +385,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|> cast_params() |> cast_params()
|> Map.update(:include_types, [], fn include_types -> include_types end) |> Map.update(:include_types, [], fn include_types -> include_types end)
if "pleroma:chat_mention" in options.include_types and not Pleroma.Chat.enabled?() do
options
|> Map.update(:include_types, [], fn include_types ->
List.delete(include_types, "pleroma:chat_mention")
end)
else
options
end
if ("pleroma:report" not in options.include_types and if ("pleroma:report" not in options.include_types and
User.privileged?(user, :reports_manage_reports)) or User.privileged?(user, :reports_manage_reports)) or
User.privileged?(user, :reports_manage_reports) do User.privileged?(user, :reports_manage_reports) do

View file

@ -287,6 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
user.last_status_at && user.last_status_at &&
user.last_status_at |> NaiveDateTime.to_date() |> Date.to_iso8601() user.last_status_at |> NaiveDateTime.to_date() |> Date.to_iso8601()
accepts_chat_messages = Pleroma.Chat.enabled?() and user.accepts_chat_messages
%{ %{
id: to_string(user.id), id: to_string(user.id),
username: username_from_nickname(user.nickname), username: username_from_nickname(user.nickname),
@ -336,7 +338,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
relationship: relationship, relationship: relationship,
skip_thread_containment: user.skip_thread_containment, skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(), background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages, accepts_chat_messages: accepts_chat_messages,
favicon: favicon, favicon: favicon,
avatar_description: avatar_description, avatar_description: avatar_description,
header_description: header_description header_description: header_description
@ -391,6 +393,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role) |> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text) |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|> Kernel.put_in([:source, :pleroma, :show_birthday], user.show_birthday) |> Kernel.put_in([:source, :pleroma, :show_birthday], user.show_birthday)
|> Kernel.put_in([:source, :pleroma, :accepts_chat_messages], user.accepts_chat_messages)
end end
defp maybe_put_settings(data, _, _, _), do: data defp maybe_put_settings(data, _, _, _), do: data

View file

@ -202,8 +202,12 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
end, end,
"pleroma_emoji_reactions", "pleroma_emoji_reactions",
"pleroma_custom_emoji_reactions", "pleroma_custom_emoji_reactions",
"pleroma_chat_messages", if Pleroma.Chat.enabled?() do
"pleroma:pin_chats", "pleroma_chat_messages"
end,
if Pleroma.Chat.enabled?() do
"pleroma:pin_chats"
end,
if Config.get([:instance, :show_reactions]) do if Config.get([:instance, :show_reactions]) do
"exposable_reactions" "exposable_reactions"
end, end,

View file

@ -41,6 +41,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
) )
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
plug(:ensure_chats_enabled)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
@ -244,4 +245,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
_ -> nil _ -> nil
end end
end end
defp ensure_chats_enabled(conn, _opts) do
if Chat.enabled?() do
conn
else
conn
|> put_status(:not_found)
|> put_view(Pleroma.Web.ErrorView)
|> render("404.json")
|> halt()
end
end
end end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do defmodule Pleroma.Web.Plugs.EnsureHostMatchesPlug do
@moduledoc "Ensures Host header matches instance" @moduledoc "Ensures Host header matches instance"
@behaviour Plug
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint

View file

@ -4,7 +4,7 @@
defmodule Pleroma.Web.Plugs.InstanceStatic do defmodule Pleroma.Web.Plugs.InstanceStatic do
require Pleroma.Constants require Pleroma.Constants
import Plug.Conn, only: [put_resp_header: 3, put_status: 2, send_resp: 3, halt: 1] import Plug.Conn, only: [put_resp_header: 3]
@moduledoc """ @moduledoc """
This is a shim to call `Plug.Static` but with runtime `from` configuration. This is a shim to call `Plug.Static` but with runtime `from` configuration.
@ -51,37 +51,10 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do
|> Map.put(:from, from) |> Map.put(:from, from)
|> Map.put(:content_types, false) |> Map.put(:content_types, false)
conn = conn = set_content_type(conn, conn.request_path)
conn
|> set_content_type(conn.request_path)
|> Plug.Static.call(opts)
if conn.halted do # Call Plug.Static with our sanitized content-type
conn Plug.Static.call(conn, opts)
else
path = String.trim_leading(conn.request_path, "/")
if not File.exists?(file_path(path)) do
conn
|> put_status(:not_found)
|> send_404()
|> halt()
else
conn
end
end
end
defp send_404(conn) do
if String.ends_with?(String.downcase(conn.request_path), ".json") do
conn
|> put_resp_header("content-type", "application/json")
|> send_resp(404, Jason.encode!(%{error: "not found"}))
else
conn
|> put_resp_header("content-type", "text/plain")
|> send_resp(404, "Not found")
end
end end
defp set_content_type(conn, "/emoji/" <> filepath) do defp set_content_type(conn, "/emoji/" <> filepath) do

View file

@ -52,7 +52,7 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do
where: t.token == ^token where: t.token == ^token
) )
with %Token{user_id: user_id} = token_record <- Repo.one(token_query) |> Repo.preload(:user), with %Token{user_id: user_id} = token_record <- Repo.one(token_query),
false <- is_nil(user_id), false <- is_nil(user_id),
%User{} = user <- User.get_cached_by_id(user_id) do %User{} = user <- User.get_cached_by_id(user_id) do
{:ok, user, token_record} {:ok, user, token_record}

View file

@ -0,0 +1,38 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.StaticNotFoundPlug do
@moduledoc """
A small plug that enforces returning 404 on static files when they don't exist.
"""
@behaviour Plug
require Pleroma.Constants
import Plug.Conn, only: [put_resp_header: 3, send_resp: 3, halt: 1]
def init(opts), do: opts
for only <- Pleroma.Constants.static_only_files() do
def call(%{request_path: "/" <> unquote(only) <> _} = conn, _opts) do
send_404(conn)
end
end
def call(conn, _) do
conn
end
defp send_404(conn) do
if String.ends_with?(String.downcase(conn.request_path), ".json") do
conn
|> put_resp_header("content-type", "application/json")
|> send_resp(404, Jason.encode!(%{error: "not found"}))
else
conn
|> put_resp_header("content-type", "text/plain")
|> send_resp(404, "Not found")
end
|> halt()
end
end

View file

@ -195,6 +195,9 @@ defmodule Pleroma.Web.Streamer do
recipients = MapSet.new(item.recipients) recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
following_ap_ids =
if user.domain_blocks == [], do: [], else: User.get_cached_user_friends_ap_ids(user)
with parent <- Object.normalize(item, fetch: false) || item, with parent <- Object.normalize(item, fetch: false) || item,
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
@ -205,8 +208,12 @@ defmodule Pleroma.Web.Streamer do
true <- MapSet.disjoint?(recipients, recipient_blocks), true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor), %{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]), %{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), false <-
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), item.actor not in following_ap_ids and
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
false <-
parent.data["actor"] not in following_ap_ids and
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
true <- thread_containment(item, user), true <- thread_containment(item, user),
false <- CommonAPI.thread_muted?(parent, user) do false <- CommonAPI.thread_muted?(parent, user) do
false false
@ -289,15 +296,17 @@ defmodule Pleroma.Web.Streamer do
defp do_stream(topic, {user, %MessageReference{} = cm_ref}) defp do_stream(topic, {user, %MessageReference{} = cm_ref})
when topic in ["user", "user:pleroma_chat"] do when topic in ["user", "user:pleroma_chat"] do
topic = "#{topic}:#{user.id}" if Pleroma.Chat.enabled?() do
topic = "#{topic}:#{user.id}"
text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, topic) text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, topic)
Registry.dispatch(@registry, topic, fn list -> Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, _auth} -> Enum.each(list, fn {pid, _auth} ->
send(pid, {:text, text}) send(pid, {:text, text})
end)
end) end)
end) end
end end
defp do_stream("user", %Activity{} = item) do defp do_stream("user", %Activity{} = item) do

View file

@ -1,9 +1,9 @@
<entry> <entry>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<id><%= @data["id"] %></id> <id><%= escape(@data["id"] || "") %></id>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
<content type="html"><%= activity_description(@data) %></content> <content type="html"><%= escape(atom_content_encoded(@data)) %></content>
<published><%= to_rfc3339(@data["published"]) %></published> <published><%= to_rfc3339(@data["published"]) %></published>
<updated><%= to_rfc3339(@data["published"]) %></updated> <updated><%= to_rfc3339(@data["published"]) %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>"> <ostatus:conversation ref="<%= activity_context(@activity) %>">
@ -15,35 +15,26 @@
<summary><%= escape(@data["summary"]) %></summary> <summary><%= escape(@data["summary"]) %></summary>
<% end %> <% end %>
<%= if @activity.local do %> <link type="text/html" href='<%= escape(activity_link(@activity, @data) || "") %>' rel="alternate"/>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<%= for tag <- Pleroma.Object.hashtags(@object) do %> <%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category> <category term="<%= tag %>"></category>
<% end %> <% end %>
<%= for attachment <- @data["attachment"] || [] do %> <%= unless sensitive?(@data) do %>
<link rel="enclosure" href="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/> <%= for attachment <- @data["attachment"] || [] do %>
<%= if size = attachment_size_positive(attachment) do %>
<link rel="enclosure" href="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>" length="<%= size %>"/>
<% else %>
<link rel="enclosure" href="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>"/>
<% end %>
<% end %>
<% end %> <% end %>
<%= if @data["inReplyTo"] do %> <%= if @data["inReplyTo"] do %>
<thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/> <thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/>
<% end %> <% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>
<% end %>
<% end %>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %> <%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/> <link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %> <% end %>

View file

@ -1,45 +1,34 @@
<item> <item>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<guid><%= @data["id"] %></guid> <guid isPermaLink="true"><%= escape(@data["id"] || "") %></guid>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
<description><%= activity_description(@data) %></description> <description><%= activity_description(@data) %></description>
<content:encoded><![CDATA[<%= rss_content_encoded(@data) %>]]></content:encoded>
<pubDate><%= to_rfc2822(@data["published"]) %></pubDate> <pubDate><%= to_rfc2822(@data["published"]) %></pubDate>
<ostatus:conversation ref="<%= activity_context(@activity) %>"> <ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %> <%= activity_context(@activity) %>
</ostatus:conversation> </ostatus:conversation>
<%= if @activity.local do %> <link><%= escape(activity_link(@activity, @data) || "") %></link>
<link><%= @data["id"] %></link>
<% else %>
<link><%= @data["external_url"] %></link>
<% end %>
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
<%= for tag <- Pleroma.Object.hashtags(@object) do %> <%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category> <%= if tag && tag != "" do %>
<category><%= tag %></category>
<% end %>
<% end %>
<%= unless sensitive?(@data) do %>
<%= with attachment when not is_nil(attachment) <- rss_enclosure_attachment(@data["attachment"]) do %>
<enclosure url="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>" length="<%= attachment_size_positive(attachment) %>" />
<% end %>
<% end %> <% end %>
<%= for attachment <- @data["attachment"] || [] do %> <%= for attachment <- @data["attachment"] || [] do %>
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>" /> <%= media_content_xml(attachment, sensitive?(@data)) %>
<% end %> <% end %>
<%= if @data["inReplyTo"] do %> <%= if @data["inReplyTo"] do %>
<thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/> <thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/>
<% end %> <% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection">http://activityschema.org/collection/public</link>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"><%= id %></link>
<% end %>
<% end %>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji"><%= file %></link>
<% end %>
</item> </item>

View file

@ -4,9 +4,9 @@
<%= render Phoenix.Controller.view_module(@conn), "_tag_author.atom", assigns %> <%= render Phoenix.Controller.view_module(@conn), "_tag_author.atom", assigns %>
<id><%= @data["id"] %></id> <id><%= escape(@data["id"] || "") %></id>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
<content type="html"><%= activity_description(@data) %></content> <content type="html"><%= escape(atom_content_encoded(@data)) %></content>
<published><%= to_rfc3339(@data["published"]) %></published> <published><%= to_rfc3339(@data["published"]) %></published>
<updated><%= to_rfc3339(@data["published"]) %></updated> <updated><%= to_rfc3339(@data["published"]) %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>"> <ostatus:conversation ref="<%= activity_context(@activity) %>">
@ -15,34 +15,25 @@
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/> <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] != "" do %> <%= if @data["summary"] != "" do %>
<summary><%= @data["summary"] %></summary> <summary><%= escape(@data["summary"]) %></summary>
<% end %> <% end %>
<%= if @activity.local do %> <link type="text/html" href='<%= escape(activity_link(@activity, @data) || "") %>' rel="alternate"/>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"
href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
href="<%= id %>" />
<% end %>
<% end %>
<% end %>
<%= for tag <- Pleroma.Object.hashtags(@object) do %> <%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category> <category term="<%= tag %>"></category>
<% end %> <% end %>
<%= unless sensitive?(@data) do %>
<%= for attachment <- @data["attachment"] || [] do %>
<%= if size = attachment_size_positive(attachment) do %>
<link rel="enclosure" href="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>" length="<%= size %>"/>
<% else %>
<link rel="enclosure" href="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>"/>
<% end %>
<% end %>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %> <%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/> <link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %> <% end %>

View file

@ -1,14 +1,24 @@
<item> <item>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title> <guid isPermaLink="true"><%= escape(activity_link(@activity, @data) || "") %></guid>
<link><%= escape(activity_link(@activity, @data) || "") %></link>
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
<link><%= activity_context(@activity) %></link>
<pubDate><%= to_rfc2822(@data["published"]) %></pubDate> <pubDate><%= to_rfc2822(@data["published"]) %></pubDate>
<title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
<description><%= activity_description(@data) %></description> <description><%= activity_description(@data) %></description>
<%= for attachment <- @data["attachment"] || [] do %> <content:encoded><![CDATA[<%= rss_content_encoded(@data) %>]]></content:encoded>
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<%= unless sensitive?(@data) do %>
<%= with attachment when not is_nil(attachment) <- rss_enclosure_attachment(@data["attachment"]) do %>
<enclosure url="<%= escape(attachment_href(attachment) || "") %>" type="<%= escape(attachment_type(attachment) || "application/octet-stream") %>" length="<%= attachment_size_positive(attachment) %>"/>
<% end %>
<% end %> <% end %>
<%= for attachment <- @data["attachment"] || [] do %>
<%= media_content_xml(attachment, sensitive?(@data)) %>
<% end %>
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
<%= if tag && tag != "" do %>
<category><%= tag %></category>
<% end %>
<% end %>
</item> </item>

View file

@ -9,10 +9,12 @@
<id><%= Routes.tag_feed_url(@conn, :feed, @tag) <> ".atom" %></id> <id><%= Routes.tag_feed_url(@conn, :feed, @tag) <> ".atom" %></id>
<title>#<%= @tag %></title> <title>#<%= @tag %></title>
<subtitle><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></subtitle> <%= with subtitle when subtitle not in [nil, ""] <- Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) do %>
<subtitle><%= subtitle %></subtitle>
<% end %>
<logo><%= feed_logo() %></logo> <logo><%= feed_logo() %></logo>
<updated><%= most_recent_update(@activities) %></updated> <updated><%= most_recent_update(@activities) || to_rfc3339(NaiveDateTime.utc_now()) %></updated>
<link rel="self" href="<%= "#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom" %>" type="application/atom+xml"/> <link rel="self" href="<%= feed_self_url(@conn) %>" type="application/atom+xml"/>
<%= for activity <- @activities do %> <%= for activity <- @activities do %>
<%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" <rss version="2.0"
xmlns:webfeeds="http://webfeeds.org/rss/1.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:thr="http://purl.org/syndication/thread/1.0"> xmlns:thr="http://purl.org/syndication/thread/1.0">
<channel> <channel>
@ -8,7 +10,12 @@
<description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description> <description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description>
<link><%= "#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss" %></link> <link><%= "#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss" %></link>
<webfeeds:logo><%= feed_logo() %></webfeeds:logo> <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
<webfeeds:icon><%= feed_logo() %></webfeeds:icon>
<webfeeds:accentColor>2b90d9</webfeeds:accentColor> <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
<%= with last_build_date when not is_nil(last_build_date) <- most_recent_update(@activities, :rss) do %>
<lastBuildDate><%= last_build_date %></lastBuildDate>
<% end %>
<generator><%= Pleroma.Application.named_version() %></generator>
<%= for activity <- @activities do %> <%= for activity <- @activities do %>
<%= render Phoenix.Controller.view_module(@conn), "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %> <%= render Phoenix.Controller.view_module(@conn), "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
<% end %> <% end %>

View file

@ -8,10 +8,12 @@
<id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id> <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<title><%= @user.nickname <> "'s timeline" %></title> <title><%= @user.nickname <> "'s timeline" %></title>
<subtitle><%= escape(@user.bio) %></subtitle> <%= if @user.bio && @user.bio != "" do %>
<subtitle><%= escape(@user.bio) %></subtitle>
<% end %>
<updated><%= most_recent_update(@activities, @user, :atom) %></updated> <updated><%= most_recent_update(@activities, @user, :atom) %></updated>
<logo><%= logo(@user) %></logo> <logo><%= logo(@user) %></logo>
<link rel="self" href="<%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom" %>" type="application/atom+xml"/> <link rel="self" href="<%= feed_self_url(@conn) %>" type="application/atom+xml"/>
<%= render Phoenix.Controller.view_module(@conn), "_author.atom", assigns %> <%= render Phoenix.Controller.view_module(@conn), "_author.atom", assigns %>

View file

@ -1,26 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" <rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:webfeeds="http://webfeeds.org/rss/1.0"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:activity="http://activitystrea.ms/spec/1.0/"
xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"
xmlns:poco="http://portablecontacts.net/spec/1.0"> xmlns:poco="http://portablecontacts.net/spec/1.0">
<channel> <channel>
<title><%= @user.nickname <> "'s timeline" %></title> <title><%= @user.nickname <> "'s timeline" %></title>
<link><%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %></link> <atom:link href="<%= feed_self_url(@conn) %>"
<atom:link href="<%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %>"
rel="self" type="application/rss+xml" /> rel="self" type="application/rss+xml" />
<link><%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %></link>
<description><%= escape(@user.bio) %></description> <description><%= escape(@user.bio) %></description>
<image> <image>
<url><%= logo(@user) %></url> <url><%= logo(@user) %></url>
<title><%= @user.nickname <> "'s timeline" %></title> <title><%= @user.nickname <> "'s timeline" %></title>
<link><%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %></link> <link><%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss" %></link>
</image> </image>
<lastBuildDate><%= most_recent_update(@activities, @user, :rss) %></lastBuildDate>
<webfeeds:icon><%= logo(@user) %></webfeeds:icon>
<generator><%= Pleroma.Application.named_version() %></generator>
<%= render Phoenix.Controller.view_module(@conn), "_author.rss", assigns %> <%= render Phoenix.Controller.view_module(@conn), "_author.rss", assigns %>
<%= if last_activity(@activities) do %> <%= if last_activity(@activities) do %>
<link rel="next"><%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}" %></link> <atom:link rel="next" href="<%= "#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}" %>" />
<% end %> <% end %>
<%= for activity <- @activities do %> <%= for activity <- @activities do %>

View file

@ -4,25 +4,39 @@
defmodule Pleroma.Workers.PublisherWorker do defmodule Pleroma.Workers.PublisherWorker do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
use Oban.Worker, queue: :federator_outgoing, max_attempts: 13 use Oban.Worker, queue: :federator_outgoing, max_attempts: 13
@publish_one_param_keys %{
"activity_id" => :activity_id,
"cc" => :cc,
"inbox" => :inbox,
"unreachable_since" => :unreachable_since
}
@impl true @impl true
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id) activity = Activity.get_by_id(activity_id)
Federator.perform(:publish, activity) federator().perform(:publish, activity)
end end
def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end) params =
Enum.reduce(params, %{}, fn {key, value}, acc ->
case @publish_one_param_keys do
%{^key => atom_key} -> Map.put(acc, atom_key, value)
_ -> acc
end
end)
# Cancel / skip the job if this server believed to be unreachable now # Cancel / skip the job if this server believed to be unreachable now
if not Instances.reachable?(params.inbox) do if not Instances.reachable?(params.inbox) do
{:cancel, :unreachable} {:cancel, :unreachable}
else else
case Federator.perform(:publish_one, params) do case federator().perform(:publish_one, params) do
{:ok, _} -> {:ok, _} ->
:ok :ok
@ -44,6 +58,8 @@ defmodule Pleroma.Workers.PublisherWorker do
@impl true @impl true
def timeout(_job), do: :timer.seconds(10) def timeout(_job), do: :timer.seconds(10)
defp federator, do: Config.get([:pipeline, :federator], Federator)
@base_backoff 15 @base_backoff 15
@pow 5 @pow 5
@impl true @impl true

View file

@ -15,6 +15,13 @@ defmodule Pleroma.Mixfile do
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
test_coverage: [tool: :covertool, summary: true], test_coverage: [tool: :covertool, summary: true],
test_ignore_filters: [
"test/credo/check/consistency/file_location.ex",
"test/fixtures/config/temp.exported_from_db.secret.exs",
"test/fixtures/config/temp.secret.exs",
"test/fixtures/modules/good_mrf.ex",
"test/fixtures/modules/runtime_module.ex"
],
# Docs # Docs
name: "Pleroma", name: "Pleroma",
homepage_url: "https://pleroma.social/", homepage_url: "https://pleroma.social/",

View file

@ -369,5 +369,39 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
MixTask.run(["filter_whitelisted", "--force"]) MixTask.run(["filter_whitelisted", "--force"])
assert config_records() == existing assert config_records() == existing
end end
test "filters blacklisted settings" do
clear_config(:database_config_whitelist, [{:pleroma}])
clear_config(:database_config_blacklist, [{:pleroma, :logger}, {:blacklisted_group}])
insert_config_record(:pleroma, :logger, backends: :console)
insert_config_record(:web_push_encryption, :vapid_details, a: 1)
insert_config_record(:blacklisted_group, :anything, a: 1)
MixTask.run(["filter_blacklisted", "--force"])
assert [
%ConfigDB{group: :pleroma, key: :instance},
%ConfigDB{group: :pleroma, key: Pleroma.Captcha},
%ConfigDB{group: :pleroma2, key: :key2},
%ConfigDB{group: :web_push_encryption, key: :vapid_details}
] = config_records()
end
test "filter_blacklisted doesn't crash when blacklist is unset" do
clear_config(:database_config_blacklist, nil)
existing = config_records()
MixTask.run(["filter_blacklisted", "--force"])
assert config_records() == existing
end
test "filter_blacklisted doesn't crash when blacklist is disabled" do
clear_config(:database_config_blacklist, false)
existing = config_records()
MixTask.run(["filter_blacklisted", "--force"])
assert config_records() == existing
end
end end
end end

View file

@ -0,0 +1,39 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ApplicationTest do
use Pleroma.DataCase
import ExUnit.CaptureLog
# TODO HACK: Temporarily disabled.
# Currently causes a bunch of warnings about :warn being deprecated in logs.
# ex_syslogger needs to either be updated upstream or forked.
describe "config :pleroma, :logger, backends: [{:ex_syslogger, :ex_syslogger}]" do
setup do
clear_config([:logger], backends: [{:ex_syslogger, :ex_syslogger}])
end
@tag :skip
test "is handled" do
log = capture_log(fn -> Pleroma.Application.configure_logger() end)
assert log =~
"Configuration {:ex_syslogger, :ex_syslogger} is incorrect. Use {ExSyslogger, :ex_syslogger} instead!"
assert log =~ "Successfully added logger backend: {ExSyslogger, :ex_syslogger}"
end
end
describe "config :pleroma, :logger, :backends: [:console]" do
setup do
clear_config([:logger], backends: [:console])
end
test "emits a warning" do
assert capture_log(fn -> Pleroma.Application.configure_logger() end) =~
":console is no longer considered a backend and is enabled by default"
end
end
end

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarningsTest do defmodule Pleroma.Config.DeprecationWarningsTest do
use ExUnit.Case use ExUnit.Case, async: false
use Pleroma.Tests.Helpers use Pleroma.Tests.Helpers
import ExUnit.CaptureLog import ExUnit.CaptureLog
@ -388,4 +388,62 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
end) =~ end) =~
"Your config is using the old namespace for the Shoutbox configuration." "Your config is using the old namespace for the Shoutbox configuration."
end end
describe "check_deprecated_logger_config" do
setup do
initial_console = Application.get_env(:logger, :console, nil)
initial_backends = Application.get_env(:logger, :backends, nil)
# NOTE: The Logger does not get reconfigured when running in the test env,
# the added and on_exit removed settings below should have no effect besides
# triggering the deprecation warnings
Application.put_env(:logger, :console, level: :all)
Application.put_env(:logger, :backends, [:console])
on_exit(fn ->
if initial_console do
Application.put_env(:logger, :console, initial_console)
else
Application.delete_env(:logger, :console)
end
if initial_backends do
Application.put_env(:logger, :backends, initial_backends)
else
Application.delete_env(:logger, :backends)
end
end)
end
test "warns on deprecated syntax" do
log =
capture_log(fn ->
Pleroma.Config.DeprecationWarnings.check_deprecated_logger_config()
end)
assert log =~
"""
!!!DEPRECATION WARNING!!!
Your configuration is using deprecated syntax for configuring backends of Elixir's logger.
`config :logger, backends: [...]` is deprecated syntax due to changes in Elixir.
Please update your configuration at your earliest convenience to use:
`config :pleroma, :logger, backends: [...]`
Note: `:console` is no longer considered a backend and is used by default, you can disable it using:
`config :logger, :default_handler: false`
"""
assert log =~
"""
!!!DEPRECATION WARNING!!!
Your configuration is using deprecated syntax for configuring logging to console.
`config :logger, :console` is deprecated syntax due to changes in Elixir.
Please update your configuration at your earliest convenience to use
`config :logger, default_handler` and `config :logger, :default_formatter`.
Note: `:default_handler` is used only for the `level` setting. All other configurations go under
`:default_formatter`. For more info visit: https://hexdocs.pm/logger/Logger.html#module-backends-and-backwards-compatibility
"""
end
end
end end

Some files were not shown because too many files have changed in this diff Show more