Merge branch 'develop' into refactor/notification_settings

This commit is contained in:
Mark Felder 2020-07-15 09:30:23 -05:00
commit 9d30bacace
61 changed files with 762 additions and 531 deletions

View File

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MFR policy to set global expiration for all local Create activities - MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard - OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated. - Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>

View File

@ -512,6 +512,7 @@
attachments_cleanup: 5, attachments_cleanup: 5,
new_users_digest: 1 new_users_digest: 1
], ],
plugins: [Oban.Plugins.Pruner],
crontab: [ crontab: [
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, {"0 * * * *", Pleroma.Workers.Cron.StatsWorker},

View File

@ -1768,8 +1768,8 @@
%{ %{
key: :whitelist, key: :whitelist,
type: {:list, :string}, type: {:list, :string},
description: "List of domains to bypass the mediaproxy", description: "List of hosts with scheme to bypass the mediaproxy",
suggestions: ["example.com"] suggestions: ["http://example.com"]
} }
] ]
}, },
@ -2008,13 +2008,15 @@
label: "Pleroma Admin Token", label: "Pleroma Admin Token",
type: :group, type: :group,
description: description:
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter", "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
children: [ children: [
%{ %{
key: :admin_token, key: :admin_token,
type: :string, type: :string,
description: "Admin token", description: "Admin token",
suggestions: ["We recommend a secure random string or UUID"] suggestions: [
"Please use a high entropy string or UUID"
]
} }
] ]
}, },

View File

@ -113,6 +113,11 @@
config :pleroma, :instances_favicons, enabled: true config :pleroma, :instances_favicons, enabled: true
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
streaming_enabled: true,
public_endpoint: nil
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View File

@ -252,6 +252,7 @@ This section describe PWA manifest instance-specific values. Currently this opti
* `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`). * `background_color`: Describe the background color of the app. (Example: `"#191b22"`, `"aliceblue"`).
## :emoji ## :emoji
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
* `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]` * `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]`
* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]` * `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
@ -260,10 +261,11 @@ This section describe PWA manifest instance-specific values. Currently this opti
memory for this amount of seconds multiplied by the number of files. memory for this amount of seconds multiplied by the number of files.
## :media_proxy ## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
* `whitelist`: List of domains to bypass the mediaproxy * `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`)
* `invalidation`: options for remove media from cache after delete object: * `invalidation`: options for remove media from cache after delete object:
* `enabled`: Enables purge cache * `enabled`: Enables purge cache
* `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use.
@ -278,6 +280,7 @@ Urls of attachments pass to script as arguments.
* `script_path`: path to external script. * `script_path`: path to external script.
Example: Example:
```elixir ```elixir
config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
script_path: "./installation/nginx-cache-purge.example" script_path: "./installation/nginx-cache-purge.example"
@ -629,8 +632,7 @@ Email notifications settings.
Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage): Configuration options described in [Oban readme](https://github.com/sorentwo/oban#usage):
* `repo` - app's Ecto repo (`Pleroma.Repo`) * `repo` - app's Ecto repo (`Pleroma.Repo`)
* `verbose` - logs verbosity * `log` - logs verbosity
* `prune` - non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning) (`:disabled` / `{:maxlen, value}` / `{:maxage, value}`)
* `queues` - job queues (see below) * `queues` - job queues (see below)
* `crontab` - periodic jobs, see [`Oban.Cron`](#obancron) * `crontab` - periodic jobs, see [`Oban.Cron`](#obancron)
@ -815,6 +817,8 @@ or
curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites" curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites"
``` ```
Warning: it's discouraged to use this feature because of the associated security risk: static / rarely changed instance-wide token is much weaker compared to email-password pair of a real admin user; consider using HTTP Basic Auth or OAuth-based authentication instead.
### :auth ### :auth
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator. * `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator.

View File

@ -35,6 +35,10 @@ def user_agent do
# See http://elixir-lang.org/docs/stable/elixir/Application.html # See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications # for more information on OTP Applications
def start(_type, _args) do def start(_type, _args) do
# Scrubbers are compiled at runtime and therefore will cause a conflict
# every time the application is restarted, so we disable module
# conflicts at runtime
Code.compiler_options(ignore_module_conflict: true)
Config.Holder.save_default() Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers() Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn() Config.DeprecationWarnings.warn()

View File

@ -54,6 +54,7 @@ def warn do
check_hellthread_threshold() check_hellthread_threshold()
mrf_user_allowlist() mrf_user_allowlist()
check_old_mrf_config() check_old_mrf_config()
check_media_proxy_whitelist_config()
end end
def check_old_mrf_config do def check_old_mrf_config do
@ -65,7 +66,7 @@ def check_old_mrf_config do
move_namespace_and_warn(@mrf_config_map, warning_preface) move_namespace_and_warn(@mrf_config_map, warning_preface)
end end
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
def move_namespace_and_warn(config_map, warning_preface) do def move_namespace_and_warn(config_map, warning_preface) do
warning = warning =
Enum.reduce(config_map, "", fn Enum.reduce(config_map, "", fn
@ -84,4 +85,16 @@ def move_namespace_and_warn(config_map, warning_preface) do
Logger.warn(warning_preface <> warning) Logger.warn(warning_preface <> warning)
end end
end end
@spec check_media_proxy_whitelist_config() :: :ok | nil
def check_media_proxy_whitelist_config do
whitelist = Config.get([:media_proxy, :whitelist])
if Enum.any?(whitelist, &(not String.starts_with?(&1, "http"))) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
""")
end
end
end end

View File

@ -4,6 +4,9 @@
defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
import Plug.Conn import Plug.Conn
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
def init(options) do def init(options) do
@ -11,7 +14,10 @@ def init(options) do
end end
def secret_token do def secret_token do
Pleroma.Config.get(:admin_token) case Pleroma.Config.get(:admin_token) do
blank when blank in [nil, ""] -> nil
token -> token
end
end end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
@ -26,9 +32,9 @@ def call(conn, _) do
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
if admin_token == secret_token() do if admin_token == secret_token() do
assign(conn, :user, %User{is_admin: true}) assign_admin_user(conn)
else else
conn handle_bad_token(conn)
end end
end end
@ -36,8 +42,19 @@ def authenticate(conn) do
token = secret_token() token = secret_token()
case get_req_header(conn, "x-admin-token") do case get_req_header(conn, "x-admin-token") do
[^token] -> assign(conn, :user, %User{is_admin: true}) blank when blank in [[], [""]] -> conn
_ -> conn [^token] -> assign_admin_user(conn)
_ -> handle_bad_token(conn)
end end
end end
defp assign_admin_user(conn) do
conn
|> assign(:user, %User{is_admin: true})
|> OAuthScopesPlug.skip_plug()
end
defp handle_bad_token(conn) do
RateLimiter.call(conn, name: :authentication)
end
end end

View File

@ -108,31 +108,48 @@ defp csp_string do
|> :erlang.iolist_to_binary() |> :erlang.iolist_to_binary()
end end
defp build_csp_from_whitelist([], acc), do: acc
defp build_csp_from_whitelist([last], acc) do
[build_csp_param_from_whitelist(last) | acc]
end
defp build_csp_from_whitelist([head | tail], acc) do
build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
end
# TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
defp build_csp_param_from_whitelist("http" <> _ = url) do
build_csp_param(url)
end
defp build_csp_param_from_whitelist(url), do: url
defp build_csp_multimedia_source_list do defp build_csp_multimedia_source_list do
media_proxy_whitelist = media_proxy_whitelist =
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc -> [:media_proxy, :whitelist]
add_source(acc, host) |> Config.get()
end) |> build_csp_from_whitelist([])
media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
captcha_method = Config.get([Pleroma.Captcha, :method]) captcha_method = Config.get([Pleroma.Captcha, :method])
captcha_endpoint = Config.get([captcha_method, :endpoint])
captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint])) base_endpoints =
[
[:media_proxy, :base_url],
[Pleroma.Upload, :base_url],
[Pleroma.Uploaders.S3, :public_endpoint]
]
|> Enum.map(&Config.get/1)
[] [captcha_endpoint | base_endpoints]
|> add_source(media_proxy_base_url) |> Enum.map(&build_csp_param/1)
|> add_source(upload_base_url) |> Enum.reduce([], &add_source(&2, &1))
|> add_source(s3_endpoint)
|> add_source(media_proxy_whitelist) |> add_source(media_proxy_whitelist)
|> add_source(captcha_endpoint)
end end
defp add_source(iodata, nil), do: iodata defp add_source(iodata, nil), do: iodata
defp add_source(iodata, []), do: iodata
defp add_source(iodata, source), do: [[?\s, source] | iodata] defp add_source(iodata, source), do: [[?\s, source] | iodata]
defp add_csp_param(csp_iodata, nil), do: csp_iodata defp add_csp_param(csp_iodata, nil), do: csp_iodata

View File

@ -7,37 +7,18 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
import Plug.Conn import Plug.Conn
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth
def init(options) do def init(options) do
options options
end end
def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
token = assigns[:token]
cond do
not Pleroma.Config.enforce_oauth_admin_scope_usage?() ->
conn conn
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
# Admin might opt out of admin scope for some apps to block any admin actions from them.
conn
true ->
fail(conn)
end
end end
def call(conn, _) do def call(conn, _) do
fail(conn)
end
defp fail(conn) do
conn conn
|> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") |> render_error(:forbidden, "User is not an admin.")
|> halt() |> halt()
end end
end end

View File

@ -60,7 +60,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
if score < 0.8 do if score < 0.8 do
{:ok, message} {:ok, message}
else else
{:reject, nil} {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end end
end end

View File

@ -39,14 +39,13 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
{:ok, message} {:ok, message}
{:old_user, false} -> {:old_user, false} ->
{:reject, nil} {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
{:error, _} -> {:error, _} ->
{:reject, nil} {:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
e -> e ->
Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}") {:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
{:reject, nil}
end end
end end

View File

@ -43,7 +43,7 @@ defp delist_message(message, _threshold), do: {:ok, message}
defp reject_message(message, threshold) when threshold > 0 do defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do if recipients > threshold do
{:reject, nil} {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else else
{:ok, message} {:ok, message}
end end
@ -87,7 +87,7 @@ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message
{:ok, message} <- delist_message(message, delist_threshold) do {:ok, message} <- delist_message(message, delist_threshold) do
{:ok, message} {:ok, message}
else else
_e -> {:reject, nil} e -> e
end end
end end

View File

@ -24,7 +24,7 @@ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} =
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern) string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do end) do
{:reject, nil} {:reject, "[KeywordPolicy] Matches with rejected keyword"}
else else
{:ok, message} {:ok, message}
end end
@ -89,8 +89,9 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
{:ok, message} <- check_replace(message) do {:ok, message} <- check_replace(message) do
{:ok, message} {:ok, message}
else else
_e -> {:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, nil} {:reject, _} = e -> e
_e -> {:reject, "[KeywordPolicy] "}
end end
end end

View File

@ -12,8 +12,9 @@ def filter(%{"type" => "Create"} = message) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || []) recipients = (message["to"] || []) ++ (message["cc"] || [])
if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do if rejected_mention =
{:reject, nil} Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else else
{:ok, message} {:ok, message}
end end

View File

@ -28,7 +28,7 @@ defp check_date(%{"object" => %{"published" => published}} = message) do
defp check_reject(message, actions) do defp check_reject(message, actions) do
if :reject in actions do if :reject in actions do
{:reject, nil} {:reject, "[ObjectAgePolicy]"}
else else
{:ok, message} {:ok, message}
end end
@ -47,9 +47,8 @@ defp check_delist(message, actions) do
{:ok, message} {:ok, message}
else else
# Unhandleable error: somebody is messing around, just drop the message.
_e -> _e ->
{:reject, nil} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, message}
@ -69,9 +68,8 @@ defp check_strip_followers(message, actions) do
{:ok, message} {:ok, message}
else else
# Unhandleable error: somebody is messing around, just drop the message.
_e -> _e ->
{:reject, nil} {:reject, "[ObjectAgePolicy] Unhandled error"}
end end
else else
{:ok, message} {:ok, message}

View File

@ -38,7 +38,7 @@ def filter(%{"type" => "Create"} = object) do
{:ok, object} {:ok, object}
true -> true ->
{:reject, nil} {:reject, "[RejectNonPublic] visibility: #{visibility}"}
end end
end end

View File

@ -21,7 +21,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, object}
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil} true -> {:reject, "[SimplePolicy] host not in accept list"}
end end
end end
@ -31,7 +31,7 @@ defp check_reject(%{host: actor_host} = _actor_info, object) do
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in reject list"}
else else
{:ok, object} {:ok, object}
end end
@ -114,7 +114,7 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in report_removal list"}
else else
{:ok, object} {:ok, object}
end end
@ -159,7 +159,7 @@ def filter(%{"type" => "Delete", "actor" => actor} = object) do
|> MRF.subdomains_regex() |> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, nil} {:reject, "[SimplePolicy] host in reject_deletes list"}
else else
{:ok, object} {:ok, object}
end end
@ -177,7 +177,9 @@ def filter(%{"actor" => actor} = object) do
{:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} <- check_report_removal(actor_info, object) do
{:ok, object} {:ok, object}
else else
_e -> {:reject, nil} {:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end end
end end
@ -191,7 +193,9 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
{:ok, object} <- check_banner_removal(actor_info, object) do {:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object} {:ok, object}
else else
_e -> {:reject, nil} {:reject, nil} -> {:reject, "[SimplePolicy]"}
{:reject, _} = e -> e
_ -> {:reject, "[SimplePolicy]"}
end end
end end

View File

@ -134,12 +134,13 @@ defp process_tag(
if user.local == true do if user.local == true do
{:ok, message} {:ok, message}
else else
{:reject, nil} {:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
end end
end end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, nil} do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message} defp process_tag(_, message), do: {:ok, message}

View File

@ -14,7 +14,7 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do
if actor in allow_list do if actor in allow_list do
{:ok, object} {:ok, object}
else else
{:reject, nil} {:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end end
end end

View File

@ -11,22 +11,26 @@ def filter(%{"type" => "Undo", "object" => child_message} = message) do
with {:ok, _} <- filter(child_message) do with {:ok, _} <- filter(child_message) do
{:ok, message} {:ok, message}
else else
{:reject, nil} -> {:reject, _} = e -> e
{:reject, nil}
end end
end end
def filter(%{"type" => message_type} = message) do def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
true <- {_, true} <-
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type), {:accepted,
false <- Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type), {_, false} <-
{:rejected,
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
{:ok, _} <- filter(message["object"]) do {:ok, _} <- filter(message["object"]) do
{:ok, message} {:ok, message}
else else
_ -> {:reject, nil} {:reject, _} = e -> e
{:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
{:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
_ -> {:reject, "[VocabularyPolicy]"}
end end
end end

View File

@ -49,7 +49,8 @@ def is_representable?(%Activity{} = activity) do
""" """
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}") Logger.debug("Federating #{id} to #{inbox}")
%{host: host, path: path} = URI.parse(inbox)
uri = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -57,8 +58,8 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
signature = signature =
Pleroma.Signature.sign(actor, %{ Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}", "(request-target)": "post #{uri.path}",
host: host, host: signature_host(uri),
"content-length": byte_size(json), "content-length": byte_size(json),
digest: digest, digest: digest,
date: date date: date
@ -76,8 +77,9 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
{"digest", digest} {"digest", digest}
] ]
) do ) do
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since], if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
do: Instances.set_reachable(inbox) Instances.set_reachable(inbox)
end
result result
else else
@ -96,6 +98,14 @@ def publish_one(%{actor_id: actor_id} = params) do
|> publish_one() |> publish_one()
end end
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
else
"#{host}:#{port}"
end
end
defp should_federate?(inbox, public) do defp should_federate?(inbox, public) do
if public do if public do
true true

View File

@ -62,15 +62,17 @@ def fix_summary(%{"summary" => _} = object) do
def fix_summary(object), do: Map.put(object, "summary", "") def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do def fix_addressing_list(map, field) do
cond do addrs = map[field]
is_binary(map[field]) ->
Map.put(map, field, [map[field]])
is_nil(map[field]) -> cond do
Map.put(map, field, []) is_list(addrs) ->
Map.put(map, field, Enum.filter(addrs, &is_binary/1))
is_binary(addrs) ->
Map.put(map, field, [addrs])
true -> true ->
map Map.put(map, field, [])
end end
end end

View File

@ -29,6 +29,10 @@ def request_body(description, schema_ref, opts \\ []) do
} }
end end
def admin_api_params do
[Operation.parameter(:admin_token, :query, :string, "Allows authorization via admin token.")]
end
def pagination_params do def pagination_params do
[ [
Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),

View File

@ -26,6 +26,7 @@ def show_operation do
%Schema{type: :boolean, default: false}, %Schema{type: :boolean, default: false},
"Get only saved in database settings" "Get only saved in database settings"
) )
| admin_api_params()
], ],
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
responses: %{ responses: %{
@ -41,6 +42,7 @@ def update_operation do
summary: "Update config settings", summary: "Update config settings",
operationId: "AdminAPI.ConfigController.update", operationId: "AdminAPI.ConfigController.update",
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -73,6 +75,7 @@ def descriptions_operation do
summary: "Get JSON with config descriptions.", summary: "Get JSON with config descriptions.",
operationId: "AdminAPI.ConfigController.descriptions", operationId: "AdminAPI.ConfigController.descriptions",
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Config Descriptions", "application/json", %Schema{ Operation.response("Config Descriptions", "application/json", %Schema{

View File

@ -20,6 +20,7 @@ def index_operation do
summary: "Get a list of generated invites", summary: "Get a list of generated invites",
operationId: "AdminAPI.InviteController.index", operationId: "AdminAPI.InviteController.index",
security: [%{"oAuth" => ["read:invites"]}], security: [%{"oAuth" => ["read:invites"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Invites", "application/json", %Schema{ Operation.response("Invites", "application/json", %Schema{
@ -51,6 +52,7 @@ def create_operation do
summary: "Create an account registration invite token", summary: "Create an account registration invite token",
operationId: "AdminAPI.InviteController.create", operationId: "AdminAPI.InviteController.create",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -71,6 +73,7 @@ def revoke_operation do
summary: "Revoke invite by token", summary: "Revoke invite by token",
operationId: "AdminAPI.InviteController.revoke", operationId: "AdminAPI.InviteController.revoke",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",
@ -97,6 +100,7 @@ def email_operation do
summary: "Sends registration invite via email", summary: "Sends registration invite via email",
operationId: "AdminAPI.InviteController.email", operationId: "AdminAPI.InviteController.email",
security: [%{"oAuth" => ["write:invites"]}], security: [%{"oAuth" => ["write:invites"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",

View File

@ -33,6 +33,7 @@ def index_operation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of statuses to return" "Number of statuses to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => success_response() 200 => success_response()
@ -46,6 +47,7 @@ def delete_operation do
summary: "Remove a banned MediaProxy URL from Cachex", summary: "Remove a banned MediaProxy URL from Cachex",
operationId: "AdminAPI.MediaProxyCacheController.delete", operationId: "AdminAPI.MediaProxyCacheController.delete",
security: [%{"oAuth" => ["write:media_proxy_caches"]}], security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",
@ -71,6 +73,7 @@ def purge_operation do
summary: "Purge and optionally ban a MediaProxy URL", summary: "Purge and optionally ban a MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.purge", operationId: "AdminAPI.MediaProxyCacheController.purge",
security: [%{"oAuth" => ["write:media_proxy_caches"]}], security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body( request_body(
"Parameters", "Parameters",

View File

@ -36,6 +36,7 @@ def index_operation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of apps to return" "Number of apps to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -72,6 +73,7 @@ def create_operation do
summary: "Create OAuth App", summary: "Create OAuth App",
operationId: "AdminAPI.OAuthAppController.create", operationId: "AdminAPI.OAuthAppController.create",
requestBody: request_body("Parameters", create_request()), requestBody: request_body("Parameters", create_request()),
parameters: admin_api_params(),
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
responses: %{ responses: %{
200 => Operation.response("App", "application/json", oauth_app()), 200 => Operation.response("App", "application/json", oauth_app()),
@ -85,7 +87,7 @@ def update_operation do
tags: ["Admin", "oAuth Apps"], tags: ["Admin", "oAuth Apps"],
summary: "Update OAuth App", summary: "Update OAuth App",
operationId: "AdminAPI.OAuthAppController.update", operationId: "AdminAPI.OAuthAppController.update",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_request()), requestBody: request_body("Parameters", update_request()),
responses: %{ responses: %{
@ -103,7 +105,7 @@ def delete_operation do
tags: ["Admin", "oAuth Apps"], tags: ["Admin", "oAuth Apps"],
summary: "Delete OAuth App", summary: "Delete OAuth App",
operationId: "AdminAPI.OAuthAppController.delete", operationId: "AdminAPI.OAuthAppController.delete",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
responses: %{ responses: %{
204 => no_content_response(), 204 => no_content_response(),

View File

@ -19,6 +19,7 @@ def index_operation do
summary: "List Relays", summary: "List Relays",
operationId: "AdminAPI.RelayController.index", operationId: "AdminAPI.RelayController.index",
security: [%{"oAuth" => ["read"]}], security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Response", "application/json", %Schema{ Operation.response("Response", "application/json", %Schema{
@ -41,6 +42,7 @@ def follow_operation do
summary: "Follow a Relay", summary: "Follow a Relay",
operationId: "AdminAPI.RelayController.follow", operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}], security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -64,6 +66,7 @@ def unfollow_operation do
summary: "Unfollow a Relay", summary: "Unfollow a Relay",
operationId: "AdminAPI.RelayController.unfollow", operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}], security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,

View File

@ -48,6 +48,7 @@ def index_operation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number number of log entries per page" "Number number of log entries per page"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -71,7 +72,7 @@ def show_operation do
tags: ["Admin", "Reports"], tags: ["Admin", "Reports"],
summary: "Get an individual report", summary: "Get an individual report",
operationId: "AdminAPI.ReportController.show", operationId: "AdminAPI.ReportController.show",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:reports"]}], security: [%{"oAuth" => ["read:reports"]}],
responses: %{ responses: %{
200 => Operation.response("Report", "application/json", report()), 200 => Operation.response("Report", "application/json", report()),
@ -86,6 +87,7 @@ def update_operation do
summary: "Change the state of one or multiple reports", summary: "Change the state of one or multiple reports",
operationId: "AdminAPI.ReportController.update", operationId: "AdminAPI.ReportController.update",
security: [%{"oAuth" => ["write:reports"]}], security: [%{"oAuth" => ["write:reports"]}],
parameters: admin_api_params(),
requestBody: request_body("Parameters", update_request(), required: true), requestBody: request_body("Parameters", update_request(), required: true),
responses: %{ responses: %{
204 => no_content_response(), 204 => no_content_response(),
@ -100,7 +102,7 @@ def notes_create_operation do
tags: ["Admin", "Reports"], tags: ["Admin", "Reports"],
summary: "Create report note", summary: "Create report note",
operationId: "AdminAPI.ReportController.notes_create", operationId: "AdminAPI.ReportController.notes_create",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
requestBody: requestBody:
request_body("Parameters", %Schema{ request_body("Parameters", %Schema{
type: :object, type: :object,
@ -124,6 +126,7 @@ def notes_delete_operation do
parameters: [ parameters: [
Operation.parameter(:report_id, :path, :string, "Report ID"), Operation.parameter(:report_id, :path, :string, "Report ID"),
Operation.parameter(:id, :path, :string, "Note ID") Operation.parameter(:id, :path, :string, "Note ID")
| admin_api_params()
], ],
security: [%{"oAuth" => ["write:reports"]}], security: [%{"oAuth" => ["write:reports"]}],
responses: %{ responses: %{

View File

@ -55,6 +55,7 @@ def index_operation do
%Schema{type: :integer, default: 50}, %Schema{type: :integer, default: 50},
"Number of statuses to return" "Number of statuses to return"
) )
| admin_api_params()
], ],
responses: %{ responses: %{
200 => 200 =>
@ -71,7 +72,7 @@ def show_operation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Show Status", summary: "Show Status",
operationId: "AdminAPI.StatusController.show", operationId: "AdminAPI.StatusController.show",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:statuses"]}], security: [%{"oAuth" => ["read:statuses"]}],
responses: %{ responses: %{
200 => Operation.response("Status", "application/json", status()), 200 => Operation.response("Status", "application/json", status()),
@ -85,7 +86,7 @@ def update_operation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Change the scope of an individual reported status", summary: "Change the scope of an individual reported status",
operationId: "AdminAPI.StatusController.update", operationId: "AdminAPI.StatusController.update",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}], security: [%{"oAuth" => ["write:statuses"]}],
requestBody: request_body("Parameters", update_request(), required: true), requestBody: request_body("Parameters", update_request(), required: true),
responses: %{ responses: %{
@ -100,7 +101,7 @@ def delete_operation do
tags: ["Admin", "Statuses"], tags: ["Admin", "Statuses"],
summary: "Delete an individual reported status", summary: "Delete an individual reported status",
operationId: "AdminAPI.StatusController.delete", operationId: "AdminAPI.StatusController.delete",
parameters: [id_param()], parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}], security: [%{"oAuth" => ["write:statuses"]}],
responses: %{ responses: %{
200 => empty_object_response(), 200 => empty_object_response(),

View File

@ -172,6 +172,11 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
with_direct_conversation_id: true with_direct_conversation_id: true
) )
else else
{:error, {:reject, message}} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
{:error, message} -> {:error, message} ->
conn conn
|> put_status(:unprocessable_entity) |> put_status(:unprocessable_entity)

View File

@ -60,22 +60,28 @@ defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
defp whitelisted?(url) do defp whitelisted?(url) do
%{host: domain} = URI.parse(url) %{host: domain} = URI.parse(url)
mediaproxy_whitelist = Config.get([:media_proxy, :whitelist]) mediaproxy_whitelist_domains =
[:media_proxy, :whitelist]
|> Config.get()
|> Enum.map(&maybe_get_domain_from_url/1)
upload_base_url_domain = whitelist_domains =
if !is_nil(Config.get([Upload, :base_url])) do if base_url = Config.get([Upload, :base_url]) do
[URI.parse(Config.get([Upload, :base_url])).host] %{host: base_domain} = URI.parse(base_url)
[base_domain | mediaproxy_whitelist_domains]
else else
[] mediaproxy_whitelist_domains
end end
whitelist = mediaproxy_whitelist ++ upload_base_url_domain domain in whitelist_domains
Enum.any?(whitelist, fn pattern ->
String.equivalent?(domain, pattern)
end)
end end
defp maybe_get_domain_from_url("http" <> _ = url) do
URI.parse(url).host
end
defp maybe_get_domain_from_url(domain), do: domain
def encode_url(url) do def encode_url(url) do
base64 = Base.url_encode64(url, @base64_opts) base64 = Base.url_encode64(url, @base64_opts)

View File

@ -90,8 +90,6 @@ defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false defp warnings_as_errors(:prod), do: false
# Uncomment this if you need testing configurable_from_database logic
# defp warnings_as_errors(:dev), do: false
defp warnings_as_errors(_), do: true defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies. # Specifies OAuth dependencies.

View File

@ -90,110 +90,100 @@ msgid "must be equal to %{number}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:421 #: lib/pleroma/web/common_api/common_api.ex:505
msgid "Account not found" msgid "Account not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:249 #: lib/pleroma/web/common_api/common_api.ex:339
msgid "Already voted" msgid "Already voted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:360 #: lib/pleroma/web/oauth/oauth_controller.ex:359
msgid "Bad request" msgid "Bad request"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426
msgid "Can't delete object" msgid "Can't delete object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196 #: lib/pleroma/web/controller_helper.ex:105
msgid "Can't delete this post" #: lib/pleroma/web/controller_helper.ex:111
msgstr ""
#, elixir-format
#: lib/pleroma/web/controller_helper.ex:95
#: lib/pleroma/web/controller_helper.ex:101
msgid "Can't display this activity" msgid "Can't display this activity"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
msgid "Can't find user" msgid "Can't find user"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114 #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61
msgid "Can't get favorites" msgid "Can't get favorites"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438
msgid "Can't like object" msgid "Can't like object"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:556 #: lib/pleroma/web/common_api/utils.ex:563
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:504 #: lib/pleroma/web/common_api/utils.ex:511
msgid "Comment must be up to %{max_size} characters" msgid "Comment must be up to %{max_size} characters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/config/config_db.ex:222 #: lib/pleroma/config/config_db.ex:191
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:95 #: lib/pleroma/web/common_api/common_api.ex:181
#: lib/pleroma/web/common_api/common_api.ex:185
msgid "Could not delete" msgid "Could not delete"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:141 #: lib/pleroma/web/common_api/common_api.ex:231
msgid "Could not favorite" msgid "Could not favorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:370 #: lib/pleroma/web/common_api/common_api.ex:453
msgid "Could not pin" msgid "Could not pin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:112 #: lib/pleroma/web/common_api/common_api.ex:278
msgid "Could not repeat"
msgstr ""
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:188
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380 #: lib/pleroma/web/common_api/common_api.ex:463
msgid "Could not unpin" msgid "Could not unpin"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:126 #: lib/pleroma/web/common_api/common_api.ex:216
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:428 #: lib/pleroma/web/common_api/common_api.ex:512
#: lib/pleroma/web/common_api/common_api.ex:437 #: lib/pleroma/web/common_api/common_api.ex:521
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207
msgid "Error." msgid "Error."
msgstr "" msgstr ""
@ -203,8 +193,8 @@ msgid "Invalid CAPTCHA"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:569 #: lib/pleroma/web/oauth/oauth_controller.ex:568
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
@ -214,22 +204,22 @@ msgid "Invalid credentials."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:265 #: lib/pleroma/web/common_api/common_api.ex:355
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:411 #: lib/pleroma/web/common_api/utils.ex:414
msgid "Invalid password." msgid "Invalid password."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220
msgid "Invalid request" msgid "Invalid request"
msgstr "" msgstr ""
@ -239,44 +229,44 @@ msgid "Kocaptcha service unavailable"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112
msgid "Missing parameters" msgid "Missing parameters"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:540 #: lib/pleroma/web/common_api/utils.ex:547
msgid "No such conversation" msgid "No such conversation"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:74 #: lib/pleroma/plugs/uploaded_media.ex:84
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143
msgid "Not found" msgid "Not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:241 #: lib/pleroma/web/common_api/common_api.ex:331
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32 #: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr ""
@ -287,7 +277,7 @@ msgid "The message visibility must be direct"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/utils.ex:566 #: lib/pleroma/web/common_api/utils.ex:573
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr ""
@ -302,65 +292,65 @@ msgid "Throttled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:266 #: lib/pleroma/web/common_api/common_api.ex:356
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:218 #: lib/pleroma/web/oauth/oauth_controller.ex:221
#: lib/pleroma/web/oauth/oauth_controller.ex:309 #: lib/pleroma/web/oauth/oauth_controller.ex:308
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:180 #: lib/pleroma/web/oauth/oauth_controller.ex:183
#: lib/pleroma/web/oauth/oauth_controller.ex:332 #: lib/pleroma/web/oauth/oauth_controller.ex:331
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:388 #: lib/pleroma/web/common_api/common_api.ex:471
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492
msgid "error" msgid "error"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62
msgid "not found" msgid "not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:395 #: lib/pleroma/web/oauth/oauth_controller.ex:394
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr ""
@ -375,17 +365,17 @@ msgid "CAPTCHA expired"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:55 #: lib/pleroma/plugs/uploaded_media.ex:57
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:411 #: lib/pleroma/web/oauth/oauth_controller.ex:410
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:442 #: lib/pleroma/web/oauth/oauth_controller.ex:441
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr ""
@ -395,7 +385,7 @@ msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:94 #: lib/pleroma/plugs/uploaded_media.ex:104
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr ""
@ -411,12 +401,12 @@ msgid "Invalid answer data"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:169 #: lib/pleroma/web/oauth/oauth_controller.ex:172
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr ""
@ -426,13 +416,13 @@ msgid "Unknown error, please check the details and try again."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:116 #: lib/pleroma/web/oauth/oauth_controller.ex:119
#: lib/pleroma/web/oauth/oauth_controller.ex:155 #: lib/pleroma/web/oauth/oauth_controller.ex:158
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:391 #: lib/pleroma/web/oauth/oauth_controller.ex:390
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr ""
@ -452,12 +442,12 @@ msgid "CAPTCHA Error"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:200 #: lib/pleroma/web/common_api/common_api.ex:290
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:211 #: lib/pleroma/web/common_api/common_api.ex:301
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr ""
@ -472,39 +462,45 @@ msgid "List not found"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123
msgid "Missing parameter: %{name}" msgid "Missing parameter: %{name}"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:207 #: lib/pleroma/web/oauth/oauth_controller.ex:210
#: lib/pleroma/web/oauth/oauth_controller.ex:322 #: lib/pleroma/web/oauth/oauth_controller.ex:321
msgid "Password reset is required" msgid "Password reset is required"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6
#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6
#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 #: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 #: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 #: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2
#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 #: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8
#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10 #: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6
#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6 #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2 #: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6 #: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6
#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6
#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 #: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 #: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 #: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
@ -519,46 +515,56 @@ msgid "Two-factor authentication enabled, you must use a access token."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210
msgid "Unexpected error occurred while adding file to pack." msgid "Unexpected error occurred while adding file to pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138
msgid "Unexpected error occurred while creating pack." msgid "Unexpected error occurred while creating pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278
msgid "Unexpected error occurred while removing file from pack." msgid "Unexpected error occurred while removing file from pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250
msgid "Unexpected error occurred while updating file in pack." msgid "Unexpected error occurred while updating file in pack."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179 #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
msgid "User is not an admin or OAuth admin scope is not granted."
msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
msgid "Web push subscription is disabled on this Pleroma instance" msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451
msgid "You can't revoke your own admin/moderator status." msgid "You can't revoke your own admin/moderator status."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126
msgid "authorization required for timeline view" msgid "authorization required for timeline view"
msgstr "" msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24
msgid "Access denied"
msgstr ""
#, elixir-format
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282
msgid "This API requires an authenticated user"
msgstr ""
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
msgid "User is not an admin."
msgstr ""

View File

@ -562,11 +562,11 @@ msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto." msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
"L'utente non è un amministratore o non ha ricevuto questa autorizzazione " "L'utente non è un amministratore."
"OAuth." "OAuth."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -559,9 +559,9 @@ msgstr ""
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr ""
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -566,9 +566,9 @@ msgstr "Nieoczekiwany błąd podczas zmieniania pliku w paczce."
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki." msgstr "Nieoczekiwany błąd podczas zmieniania metadanych paczki."
#: lib/pleroma/plugs/user_is_admin_plug.ex:40 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted." msgid "User is not an admin."
msgstr "" msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61

View File

@ -0,0 +1,27 @@
defmodule Elixir.Pleroma.Repo.Migrations.Oban20ConfigChanges do
use Ecto.Migration
import Ecto.Query
alias Pleroma.ConfigDB
alias Pleroma.Repo
def change do
config_entry =
from(c in ConfigDB, where: c.group == ^":pleroma" and c.key == ^"Oban")
|> select([c], struct(c, [:value, :id]))
|> Repo.one()
if config_entry do
%{value: value} = config_entry
value =
case Keyword.fetch(value, :verbose) do
{:ok, log} -> Keyword.put_new(value, :log, log)
_ -> value
end
|> Keyword.drop([:verbose, :prune])
Ecto.Changeset.change(config_entry, %{value: value})
|> Repo.update()
end
end
end

View File

@ -54,4 +54,12 @@ test "move_namespace_and_warn/2" do
assert Pleroma.Config.get(new_group2) == 2 assert Pleroma.Config.get(new_group2) == 2
assert Pleroma.Config.get(new_group3) == 3 assert Pleroma.Config.get(new_group3) == 3
end end
test "check_media_proxy_whitelist_config/0" do
clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"])
assert capture_log(fn ->
Pleroma.Config.DeprecationWarnings.check_media_proxy_whitelist_config()
end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option"
end
end end

View File

@ -4,9 +4,14 @@
defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do
use Pleroma.Web.ConnCase, async: true use Pleroma.Web.ConnCase, async: true
import Mock
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Plugs.AdminSecretAuthenticationPlug alias Pleroma.Plugs.AdminSecretAuthenticationPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.PlugHelper
alias Pleroma.Plugs.RateLimiter
test "does nothing if a user is assigned", %{conn: conn} do test "does nothing if a user is assigned", %{conn: conn} do
user = insert(:user) user = insert(:user)
@ -25,6 +30,10 @@ test "does nothing if a user is assigned", %{conn: conn} do
describe "when secret set it assigns an admin user" do describe "when secret set it assigns an admin user" do
setup do: clear_config([:admin_token]) setup do: clear_config([:admin_token])
setup_with_mocks([{RateLimiter, [:passthrough], []}]) do
:ok
end
test "with `admin_token` query parameter", %{conn: conn} do test "with `admin_token` query parameter", %{conn: conn} do
Pleroma.Config.put(:admin_token, "password123") Pleroma.Config.put(:admin_token, "password123")
@ -33,12 +42,14 @@ test "with `admin_token` query parameter", %{conn: conn} do
|> AdminSecretAuthenticationPlug.call(%{}) |> AdminSecretAuthenticationPlug.call(%{})
refute conn.assigns[:user] refute conn.assigns[:user]
assert called(RateLimiter.call(conn, name: :authentication))
conn = conn =
%{conn | params: %{"admin_token" => "password123"}} %{conn | params: %{"admin_token" => "password123"}}
|> AdminSecretAuthenticationPlug.call(%{}) |> AdminSecretAuthenticationPlug.call(%{})
assert conn.assigns[:user].is_admin assert conn.assigns[:user].is_admin
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
end end
test "with `x-admin-token` HTTP header", %{conn: conn} do test "with `x-admin-token` HTTP header", %{conn: conn} do
@ -50,6 +61,7 @@ test "with `x-admin-token` HTTP header", %{conn: conn} do
|> AdminSecretAuthenticationPlug.call(%{}) |> AdminSecretAuthenticationPlug.call(%{})
refute conn.assigns[:user] refute conn.assigns[:user]
assert called(RateLimiter.call(conn, name: :authentication))
conn = conn =
conn conn
@ -57,6 +69,7 @@ test "with `x-admin-token` HTTP header", %{conn: conn} do
|> AdminSecretAuthenticationPlug.call(%{}) |> AdminSecretAuthenticationPlug.call(%{})
assert conn.assigns[:user].is_admin assert conn.assigns[:user].is_admin
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
end end
end end
end end

View File

@ -4,17 +4,12 @@
defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
alias Pleroma.Config alias Pleroma.Config
alias Plug.Conn alias Plug.Conn
setup do: clear_config([:http_securiy, :enabled])
setup do: clear_config([:http_security, :sts])
setup do: clear_config([:http_security, :referrer_policy])
describe "http security enabled" do describe "http security enabled" do
setup do setup do: clear_config([:http_security, :enabled], true)
Config.put([:http_security, :enabled], true)
end
test "it sends CSP headers when enabled", %{conn: conn} do test "it sends CSP headers when enabled", %{conn: conn} do
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
@ -29,7 +24,7 @@ test "it sends CSP headers when enabled", %{conn: conn} do
end end
test "it sends STS headers when enabled", %{conn: conn} do test "it sends STS headers when enabled", %{conn: conn} do
Config.put([:http_security, :sts], true) clear_config([:http_security, :sts], true)
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
@ -38,7 +33,7 @@ test "it sends STS headers when enabled", %{conn: conn} do
end end
test "it does not send STS headers when disabled", %{conn: conn} do test "it does not send STS headers when disabled", %{conn: conn} do
Config.put([:http_security, :sts], false) clear_config([:http_security, :sts], false)
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
@ -47,23 +42,19 @@ test "it does not send STS headers when disabled", %{conn: conn} do
end end
test "referrer-policy header reflects configured value", %{conn: conn} do test "referrer-policy header reflects configured value", %{conn: conn} do
conn = get(conn, "/api/v1/instance") resp = get(conn, "/api/v1/instance")
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"] assert Conn.get_resp_header(resp, "referrer-policy") == ["same-origin"]
Config.put([:http_security, :referrer_policy], "no-referrer") clear_config([:http_security, :referrer_policy], "no-referrer")
conn = resp = get(conn, "/api/v1/instance")
build_conn()
|> get("/api/v1/instance")
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"] assert Conn.get_resp_header(resp, "referrer-policy") == ["no-referrer"]
end end
test "it sends `report-to` & `report-uri` CSP response headers" do test "it sends `report-to` & `report-uri` CSP response headers", %{conn: conn} do
conn = conn = get(conn, "/api/v1/instance")
build_conn()
|> get("/api/v1/instance")
[csp] = Conn.get_resp_header(conn, "content-security-policy") [csp] = Conn.get_resp_header(conn, "content-security-policy")
@ -74,10 +65,67 @@ test "it sends `report-to` & `report-uri` CSP response headers" do
assert reply_to == assert reply_to ==
"{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}" "{\"endpoints\":[{\"url\":\"https://endpoint.com\"}],\"group\":\"csp-endpoint\",\"max-age\":10886400}"
end end
test "default values for img-src and media-src with disabled media proxy", %{conn: conn} do
conn = get(conn, "/api/v1/instance")
[csp] = Conn.get_resp_header(conn, "content-security-policy")
assert csp =~ "media-src 'self' https:;"
assert csp =~ "img-src 'self' data: blob: https:;"
end
end
describe "img-src and media-src" do
setup do
clear_config([:http_security, :enabled], true)
clear_config([:media_proxy, :enabled], true)
clear_config([:media_proxy, :proxy_opts, :redirect_on_failure], false)
end
test "media_proxy with base_url", %{conn: conn} do
url = "https://example.com"
clear_config([:media_proxy, :base_url], url)
assert_media_img_src(conn, url)
end
test "upload with base url", %{conn: conn} do
url = "https://example2.com"
clear_config([Pleroma.Upload, :base_url], url)
assert_media_img_src(conn, url)
end
test "with S3 public endpoint", %{conn: conn} do
url = "https://example3.com"
clear_config([Pleroma.Uploaders.S3, :public_endpoint], url)
assert_media_img_src(conn, url)
end
test "with captcha endpoint", %{conn: conn} do
clear_config([Pleroma.Captcha.Mock, :endpoint], "https://captcha.com")
assert_media_img_src(conn, "https://captcha.com")
end
test "with media_proxy whitelist", %{conn: conn} do
clear_config([:media_proxy, :whitelist], ["https://example6.com", "https://example7.com"])
assert_media_img_src(conn, "https://example7.com https://example6.com")
end
# TODO: delete after removing support bare domains for media proxy whitelist
test "with media_proxy bare domains whitelist (deprecated)", %{conn: conn} do
clear_config([:media_proxy, :whitelist], ["example4.com", "example5.com"])
assert_media_img_src(conn, "example5.com example4.com")
end
end
defp assert_media_img_src(conn, url) do
conn = get(conn, "/api/v1/instance")
[csp] = Conn.get_resp_header(conn, "content-security-policy")
assert csp =~ "media-src 'self' #{url};"
assert csp =~ "img-src 'self' data: blob: #{url};"
end end
test "it does not send CSP headers when disabled", %{conn: conn} do test "it does not send CSP headers when disabled", %{conn: conn} do
Config.put([:http_security, :enabled], false) clear_config([:http_security, :enabled], false)
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")

View File

@ -8,9 +8,6 @@ defmodule Pleroma.Plugs.UserIsAdminPlugTest do
alias Pleroma.Plugs.UserIsAdminPlug alias Pleroma.Plugs.UserIsAdminPlug
import Pleroma.Factory import Pleroma.Factory
describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
test "accepts a user that is an admin" do test "accepts a user that is an admin" do
user = insert(:user, is_admin: true) user = insert(:user, is_admin: true)
@ -38,82 +35,3 @@ test "denies when a user isn't set" do
assert conn.status == 403 assert conn.status == 403
end end
end end
describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
setup do
admin_user = insert(:user, is_admin: true)
non_admin_user = insert(:user, is_admin: false)
blank_user = nil
{:ok, %{users: [admin_user, non_admin_user, blank_user]}}
end
test "if token has any of admin scopes, accepts a user that is an admin", %{conn: conn} do
user = insert(:user, is_admin: true)
token = insert(:oauth_token, user: user, scopes: ["admin:something"])
conn =
conn
|> assign(:user, user)
|> assign(:token, token)
ret_conn = UserIsAdminPlug.call(conn, %{})
assert conn == ret_conn
end
test "if token has any of admin scopes, denies a user that isn't an admin", %{conn: conn} do
user = insert(:user, is_admin: false)
token = insert(:oauth_token, user: user, scopes: ["admin:something"])
conn =
conn
|> assign(:user, user)
|> assign(:token, token)
|> UserIsAdminPlug.call(%{})
assert conn.status == 403
end
test "if token has any of admin scopes, denies when a user isn't set", %{conn: conn} do
token = insert(:oauth_token, scopes: ["admin:something"])
conn =
conn
|> assign(:user, nil)
|> assign(:token, token)
|> UserIsAdminPlug.call(%{})
assert conn.status == 403
end
test "if token lacks admin scopes, denies users regardless of is_admin flag",
%{users: users} do
for user <- users do
token = insert(:oauth_token, user: user)
conn =
build_conn()
|> assign(:user, user)
|> assign(:token, token)
|> UserIsAdminPlug.call(%{})
assert conn.status == 403
end
end
test "if token is missing, denies users regardless of is_admin flag", %{users: users} do
for user <- users do
conn =
build_conn()
|> assign(:user, user)
|> assign(:token, nil)
|> UserIsAdminPlug.call(%{})
assert conn.status == 403
end
end
end
end

View File

@ -1082,6 +1082,45 @@ test "it increases like count when receiving a like action", %{conn: conn} do
assert object = Object.get_by_ap_id(note_object.data["id"]) assert object = Object.get_by_ap_id(note_object.data["id"])
assert object.data["like_count"] == 1 assert object.data["like_count"] == 1
end end
test "it doesn't spreads faulty attributedTo or actor fields", %{
conn: conn,
activity: activity
} do
reimu = insert(:user, nickname: "reimu")
cirno = insert(:user, nickname: "cirno")
assert reimu.ap_id
assert cirno.ap_id
activity =
activity
|> put_in(["object", "actor"], reimu.ap_id)
|> put_in(["object", "attributedTo"], reimu.ap_id)
|> put_in(["actor"], reimu.ap_id)
|> put_in(["attributedTo"], reimu.ap_id)
_reimu_outbox =
conn
|> assign(:user, cirno)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reimu.nickname}/outbox", activity)
|> json_response(403)
cirno_outbox =
conn
|> assign(:user, cirno)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{cirno.nickname}/outbox", activity)
|> json_response(201)
assert cirno_outbox["attributedTo"] == nil
assert cirno_outbox["actor"] == cirno.ap_id
assert cirno_object = Object.normalize(cirno_outbox["object"])
assert cirno_object.data["actor"] == cirno.ap_id
assert cirno_object.data["attributedTo"] == cirno.ap_id
end
end end
describe "/relay/followers" do describe "/relay/followers" do

View File

@ -21,7 +21,7 @@ test "matches followbots by nickname" do
"id" => "https://example.com/activities/1234" "id" => "https://example.com/activities/1234"
} }
{:reject, nil} = AntiFollowbotPolicy.filter(message) assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
end end
test "matches followbots by display name" do test "matches followbots by display name" do
@ -36,7 +36,7 @@ test "matches followbots by display name" do
"id" => "https://example.com/activities/1234" "id" => "https://example.com/activities/1234"
} }
{:reject, nil} = AntiFollowbotPolicy.filter(message) assert {:reject, "[AntiFollowbotPolicy]" <> _} = AntiFollowbotPolicy.filter(message)
end end
end end

View File

@ -50,7 +50,8 @@ test "rejects the message if the recipient count is above reject_threshold", %{
} do } do
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2}) Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2})
{:reject, nil} = filter(message) assert {:reject, "[HellthreadPolicy] 3 recipients is over the limit of 2"} ==
filter(message)
end end
test "does not reject the message if the recipient count is below reject_threshold", %{ test "does not reject the message if the recipient count is below reject_threshold", %{

View File

@ -25,7 +25,8 @@ test "rejects if string matches in content" do
} }
} }
assert {:reject, nil} == KeywordPolicy.filter(message) assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
KeywordPolicy.filter(message)
end end
test "rejects if string matches in summary" do test "rejects if string matches in summary" do
@ -39,7 +40,8 @@ test "rejects if string matches in summary" do
} }
} }
assert {:reject, nil} == KeywordPolicy.filter(message) assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} =
KeywordPolicy.filter(message)
end end
test "rejects if regex matches in content" do test "rejects if regex matches in content" do
@ -55,7 +57,8 @@ test "rejects if regex matches in content" do
} }
} }
{:reject, nil} == KeywordPolicy.filter(message) {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
KeywordPolicy.filter(message)
end) end)
end end
@ -72,7 +75,8 @@ test "rejects if regex matches in summary" do
} }
} }
{:reject, nil} == KeywordPolicy.filter(message) {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
KeywordPolicy.filter(message)
end) end)
end end
end end

View File

@ -76,7 +76,8 @@ test "to" do
"to" => ["https://example.com/blocked"] "to" => ["https://example.com/blocked"]
} }
assert MentionPolicy.filter(message) == {:reject, nil} assert MentionPolicy.filter(message) ==
{:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"}
end end
test "cc" do test "cc" do
@ -88,7 +89,8 @@ test "cc" do
"cc" => ["https://example.com/blocked"] "cc" => ["https://example.com/blocked"]
} }
assert MentionPolicy.filter(message) == {:reject, nil} assert MentionPolicy.filter(message) ==
{:reject, "[MentionPolicy] Rejected for mention of https://example.com/blocked"}
end end
end end
end end

View File

@ -64,7 +64,7 @@ test "it's rejected when addrer of message in the follower addresses of user and
} }
Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], false) Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], false)
assert {:reject, nil} = RejectNonPublic.filter(message) assert {:reject, _} = RejectNonPublic.filter(message)
end end
end end
@ -94,7 +94,7 @@ test "it's reject when direct messages aren't allow" do
} }
Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], false) Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], false)
assert {:reject, nil} = RejectNonPublic.filter(message) assert {:reject, _} = RejectNonPublic.filter(message)
end end
end end
end end

View File

@ -124,7 +124,7 @@ test "has a matching host" do
report_message = build_report_message() report_message = build_report_message()
local_message = build_local_message() local_message = build_local_message()
assert SimplePolicy.filter(report_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(report_message)
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
@ -133,7 +133,7 @@ test "match with wildcard domain" do
report_message = build_report_message() report_message = build_report_message()
local_message = build_local_message() local_message = build_local_message()
assert SimplePolicy.filter(report_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(report_message)
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
end end
@ -241,7 +241,7 @@ test "activity has a matching host" do
remote_message = build_remote_message() remote_message = build_remote_message()
assert SimplePolicy.filter(remote_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(remote_message)
end end
test "activity matches with wildcard domain" do test "activity matches with wildcard domain" do
@ -249,7 +249,7 @@ test "activity matches with wildcard domain" do
remote_message = build_remote_message() remote_message = build_remote_message()
assert SimplePolicy.filter(remote_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(remote_message)
end end
test "actor has a matching host" do test "actor has a matching host" do
@ -257,7 +257,7 @@ test "actor has a matching host" do
remote_user = build_remote_user() remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(remote_user)
end end
end end
@ -279,7 +279,7 @@ test "is not empty but activity doesn't have a matching host" do
remote_message = build_remote_message() remote_message = build_remote_message()
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(remote_message)
end end
test "activity has a matching host" do test "activity has a matching host" do
@ -429,7 +429,7 @@ test "it accepts deletions even from non-whitelisted servers" do
test "it rejects the deletion" do test "it rejects the deletion" do
deletion_message = build_remote_deletion_message() deletion_message = build_remote_deletion_message()
assert SimplePolicy.filter(deletion_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(deletion_message)
end end
end end
@ -439,7 +439,7 @@ test "it rejects the deletion" do
test "it rejects the deletion" do test "it rejects the deletion" do
deletion_message = build_remote_deletion_message() deletion_message = build_remote_deletion_message()
assert SimplePolicy.filter(deletion_message) == {:reject, nil} assert {:reject, _} = SimplePolicy.filter(deletion_message)
end end
end end

View File

@ -12,8 +12,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do
describe "mrf_tag:disable-any-subscription" do describe "mrf_tag:disable-any-subscription" do
test "rejects message" do test "rejects message" do
actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"]) actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"])
message = %{"object" => actor.ap_id, "type" => "Follow"} message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => actor.ap_id}
assert {:reject, nil} = TagPolicy.filter(message) assert {:reject, _} = TagPolicy.filter(message)
end end
end end
@ -22,7 +22,7 @@ test "rejects non-local follow requests" do
actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"]) actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"])
follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false) follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false)
message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id} message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id}
assert {:reject, nil} = TagPolicy.filter(message) assert {:reject, _} = TagPolicy.filter(message)
end end
test "allows non-local follow requests" do test "allows non-local follow requests" do

View File

@ -26,6 +26,6 @@ test "rejected if allow list isn't empty and user not in allow list" do
actor = insert(:user) actor = insert(:user)
Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]}) Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]})
message = %{"actor" => actor.ap_id} message = %{"actor" => actor.ap_id}
assert UserAllowListPolicy.filter(message) == {:reject, nil} assert {:reject, _} = UserAllowListPolicy.filter(message)
end end
end end

View File

@ -46,7 +46,7 @@ test "it does not accept disallowed child objects" do
} }
} }
{:reject, nil} = VocabularyPolicy.filter(message) {:reject, _} = VocabularyPolicy.filter(message)
end end
test "it does not accept disallowed parent types" do test "it does not accept disallowed parent types" do
@ -60,7 +60,7 @@ test "it does not accept disallowed parent types" do
} }
} }
{:reject, nil} = VocabularyPolicy.filter(message) {:reject, _} = VocabularyPolicy.filter(message)
end end
end end
@ -75,7 +75,7 @@ test "it rejects based on parent activity type" do
"object" => "whatever" "object" => "whatever"
} }
{:reject, nil} = VocabularyPolicy.filter(message) {:reject, _} = VocabularyPolicy.filter(message)
end end
test "it rejects based on child object type" do test "it rejects based on child object type" do
@ -89,7 +89,7 @@ test "it rejects based on child object type" do
} }
} }
{:reject, nil} = VocabularyPolicy.filter(message) {:reject, _} = VocabularyPolicy.filter(message)
end end
test "it passes through objects that aren't disallowed" do test "it passes through objects that aren't disallowed" do

View File

@ -123,6 +123,39 @@ test "it returns inbox for messages involving single recipients in total" do
end end
describe "publish_one/1" do describe "publish_one/1" do
test "publish to url with with different ports" do
inbox80 = "http://42.site/users/nick1/inbox"
inbox42 = "http://42.site:42/users/nick1/inbox"
mock(fn
%{method: :post, url: "http://42.site:42/users/nick1/inbox"} ->
{:ok, %Tesla.Env{status: 200, body: "port 42"}}
%{method: :post, url: "http://42.site/users/nick1/inbox"} ->
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
end)
actor = insert(:user)
assert {:ok, %{body: "port 42"}} =
Publisher.publish_one(%{
inbox: inbox42,
json: "{}",
actor: actor,
id: 1,
unreachable_since: true
})
assert {:ok, %{body: "port 80"}} =
Publisher.publish_one(%{
inbox: inbox80,
json: "{}",
actor: actor,
id: 1,
unreachable_since: true
})
end
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
Instances, Instances,
[:passthrough], [:passthrough],
@ -131,7 +164,6 @@ test "it returns inbox for messages involving single recipients in total" do
inbox = "http://200.site/users/nick1/inbox" inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_reachable(inbox)) assert called(Instances.set_reachable(inbox))
end end

View File

@ -774,6 +774,29 @@ test "it correctly processes messages with non-array cc field" do
assert [user.follower_address] == activity.data["to"] assert [user.follower_address] == activity.data["to"]
end end
test "it correctly processes messages with weirdness in address fields" do
user = insert(:user)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => [nil, user.follower_address],
"cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
"type" => "Create",
"object" => %{
"content" => "",
"type" => "Note",
"attributedTo" => user.ap_id,
"inReplyTo" => nil
},
"actor" => user.ap_id
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
assert [user.follower_address] == activity.data["to"]
end
test "it accepts Move activities" do test "it accepts Move activities" do
old_user = insert(:user) old_user = insert(:user)
new_user = insert(:user) new_user = insert(:user)

View File

@ -41,6 +41,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
{:ok, %{admin: admin, token: token, conn: conn}} {:ok, %{admin: admin, token: token, conn: conn}}
end end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:admin_token], "password123")
user = insert(:user)
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
assert json_response(conn, 200)
end
describe "with [:auth, :enforce_oauth_admin_scope_usage]," do describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true) setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)

View File

@ -152,6 +152,14 @@ test "subkeys with full update right merge", %{conn: conn} do
assert emoji_val[:groups] == [a: 1, b: 2] assert emoji_val[:groups] == [a: 1, b: 2]
assert assets_val[:mascots] == [a: 1, b: 2] assert assets_val[:mascots] == [a: 1, b: 2]
end end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:admin_token], "password123")
build_conn()
|> get("/api/pleroma/admin/config?admin_token=password123")
|> json_response_and_validate_schema(200)
end
end end
test "POST /api/pleroma/admin/config error", %{conn: conn} do test "POST /api/pleroma/admin/config error", %{conn: conn} do

View File

@ -297,7 +297,7 @@ test "returns 403 when requested by a non-admin" do
|> get("/api/pleroma/admin/reports") |> get("/api/pleroma/admin/reports")
assert json_response(conn, :forbidden) == assert json_response(conn, :forbidden) ==
%{"error" => "User is not an admin or OAuth admin scope is not granted."} %{"error" => "User is not an admin."}
end end
test "returns 403 when requested by anonymous" do test "returns 403 when requested by anonymous" do

View File

@ -22,6 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
setup do: clear_config([:instance, :federating]) setup do: clear_config([:instance, :federating])
setup do: clear_config([:instance, :allow_relay]) setup do: clear_config([:instance, :allow_relay])
setup do: clear_config([:rich_media, :enabled]) setup do: clear_config([:rich_media, :enabled])
setup do: clear_config([:mrf, :policies])
setup do: clear_config([:mrf_keyword, :reject])
describe "posting statuses" do describe "posting statuses" do
setup do: oauth_access(["write:statuses"]) setup do: oauth_access(["write:statuses"])
@ -157,6 +159,17 @@ test "it fails to create a status if `expires_in` is less or equal than an hour"
|> json_response_and_validate_schema(422) |> json_response_and_validate_schema(422)
end end
test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
Pleroma.Config.put([:mrf_keyword, :reject], ["GNO"])
Pleroma.Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
conn
|> put_req_header("content-type", "application/json")
|> post("api/v1/statuses", %{"status" => "GNO/Linux"})
|> json_response_and_validate_schema(422)
end
test "posting an undefined status with an attachment", %{user: user, conn: conn} do test "posting an undefined status with an attachment", %{user: user, conn: conn} do
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpg", content_type: "image/jpg",

View File

@ -4,82 +4,118 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Mock
alias Pleroma.Config
setup do: clear_config(:media_proxy) import Mock
setup do: clear_config([Pleroma.Web.Endpoint, :secret_key_base])
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.MediaProxy.MediaProxyController
alias Plug.Conn
setup do setup do
on_exit(fn -> Cachex.clear(:banned_urls_cache) end) on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
end end
test "it returns 404 when MediaProxy disabled", %{conn: conn} do test "it returns 404 when MediaProxy disabled", %{conn: conn} do
Config.put([:media_proxy, :enabled], false) clear_config([:media_proxy, :enabled], false)
assert %Plug.Conn{ assert %Conn{
status: 404, status: 404,
resp_body: "Not Found" resp_body: "Not Found"
} = get(conn, "/proxy/hhgfh/eeeee") } = get(conn, "/proxy/hhgfh/eeeee")
assert %Plug.Conn{ assert %Conn{
status: 404, status: 404,
resp_body: "Not Found" resp_body: "Not Found"
} = get(conn, "/proxy/hhgfh/eeee/fff") } = get(conn, "/proxy/hhgfh/eeee/fff")
end end
test "it returns 403 when signature invalidated", %{conn: conn} do describe "" do
Config.put([:media_proxy, :enabled], true) setup do
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") clear_config([:media_proxy, :enabled], true)
path = URI.parse(Pleroma.Web.MediaProxy.encode_url("https://google.fn")).path clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") [url: MediaProxy.encode_url("https://google.fn/test.png")]
end
assert %Plug.Conn{ test "it returns 403 for invalid signature", %{conn: conn, url: url} do
Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000")
%{path: path} = URI.parse(url)
assert %Conn{
status: 403, status: 403,
resp_body: "Forbidden" resp_body: "Forbidden"
} = get(conn, path) } = get(conn, path)
assert %Plug.Conn{ assert %Conn{
status: 403, status: 403,
resp_body: "Forbidden" resp_body: "Forbidden"
} = get(conn, "/proxy/hhgfh/eeee") } = get(conn, "/proxy/hhgfh/eeee")
assert %Plug.Conn{ assert %Conn{
status: 403, status: 403,
resp_body: "Forbidden" resp_body: "Forbidden"
} = get(conn, "/proxy/hhgfh/eeee/fff") } = get(conn, "/proxy/hhgfh/eeee/fff")
end end
test "redirects on valid url when filename invalidated", %{conn: conn} do test "redirects on valid url when filename is invalidated", %{conn: conn, url: url} do
Config.put([:media_proxy, :enabled], true)
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
invalid_url = String.replace(url, "test.png", "test-file.png") invalid_url = String.replace(url, "test.png", "test-file.png")
response = get(conn, invalid_url) response = get(conn, invalid_url)
assert response.status == 302 assert response.status == 302
assert redirected_to(response) == url assert redirected_to(response) == url
end end
test "it performs ReverseProxy.call when signature valid", %{conn: conn} do test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do
Config.put([:media_proxy, :enabled], true) with_mock Pleroma.ReverseProxy,
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") call: fn _conn, _url, _opts -> %Conn{status: :success} end do
url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") assert %Conn{status: :success} = get(conn, url)
end
end
test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do
MediaProxy.put_in_banned_urls("https://google.fn/test.png")
with_mock Pleroma.ReverseProxy, with_mock Pleroma.ReverseProxy,
call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do call: fn _conn, _url, _opts -> %Conn{status: :success} end do
assert %Plug.Conn{status: :success} = get(conn, url) assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
end
end end
end end
test "it returns 404 when url contains in banned_urls cache", %{conn: conn} do describe "filename_matches/3" do
Config.put([:media_proxy, :enabled], true) test "preserves the encoded or decoded path" do
Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") assert MediaProxyController.filename_matches(
url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") %{"filename" => "/Hello world.jpg"},
Pleroma.Web.MediaProxy.put_in_banned_urls("https://google.fn/test.png") "/Hello world.jpg",
"http://pleroma.social/Hello world.jpg"
) == :ok
with_mock Pleroma.ReverseProxy, assert MediaProxyController.filename_matches(
call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do %{"filename" => "/Hello%20world.jpg"},
assert %Plug.Conn{status: 404, resp_body: "Not Found"} = get(conn, url) "/Hello%20world.jpg",
"http://pleroma.social/Hello%20world.jpg"
) == :ok
assert MediaProxyController.filename_matches(
%{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"},
"/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
) == :ok
assert MediaProxyController.filename_matches(
%{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"},
"/my%2Flong%2Furl%2F2019%2F07%2FS.jp",
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
end
test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
# conn.request_path will return encoded url
request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
assert MediaProxyController.filename_matches(
true,
request_path,
"https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
) == :ok
end end
end end
end end

View File

@ -5,38 +5,33 @@
defmodule Pleroma.Web.MediaProxyTest do defmodule Pleroma.Web.MediaProxyTest do
use ExUnit.Case use ExUnit.Case
use Pleroma.Tests.Helpers use Pleroma.Tests.Helpers
import Pleroma.Web.MediaProxy
alias Pleroma.Web.MediaProxy.MediaProxyController
setup do: clear_config([:media_proxy, :enabled]) alias Pleroma.Web.Endpoint
setup do: clear_config(Pleroma.Upload) alias Pleroma.Web.MediaProxy
describe "when enabled" do describe "when enabled" do
setup do setup do: clear_config([:media_proxy, :enabled], true)
Pleroma.Config.put([:media_proxy, :enabled], true)
:ok
end
test "ignores invalid url" do test "ignores invalid url" do
assert url(nil) == nil assert MediaProxy.url(nil) == nil
assert url("") == nil assert MediaProxy.url("") == nil
end end
test "ignores relative url" do test "ignores relative url" do
assert url("/local") == "/local" assert MediaProxy.url("/local") == "/local"
assert url("/") == "/" assert MediaProxy.url("/") == "/"
end end
test "ignores local url" do test "ignores local url" do
local_url = Pleroma.Web.Endpoint.url() <> "/hello" local_url = Endpoint.url() <> "/hello"
local_root = Pleroma.Web.Endpoint.url() local_root = Endpoint.url()
assert url(local_url) == local_url assert MediaProxy.url(local_url) == local_url
assert url(local_root) == local_root assert MediaProxy.url(local_root) == local_root
end end
test "encodes and decodes URL" do test "encodes and decodes URL" do
url = "https://pleroma.soykaf.com/static/logo.png" url = "https://pleroma.soykaf.com/static/logo.png"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.starts_with?( assert String.starts_with?(
encoded, encoded,
@ -50,86 +45,44 @@ test "encodes and decodes URL" do
test "encodes and decodes URL without a path" do test "encodes and decodes URL without a path" do
url = "https://pleroma.soykaf.com" url = "https://pleroma.soykaf.com"
encoded = url(url) encoded = MediaProxy.url(url)
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
test "encodes and decodes URL without an extension" do test "encodes and decodes URL without an extension" do
url = "https://pleroma.soykaf.com/path/" url = "https://pleroma.soykaf.com/path/"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.ends_with?(encoded, "/path") assert String.ends_with?(encoded, "/path")
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
test "encodes and decodes URL and ignores query params for the path" do test "encodes and decodes URL and ignores query params for the path" do
url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true" url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.ends_with?(encoded, "/logo.png") assert String.ends_with?(encoded, "/logo.png")
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
test "validates signature" do test "validates signature" do
secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base]) encoded = MediaProxy.url("https://pleroma.social")
on_exit(fn -> clear_config(
Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], secret_key_base) [Endpoint, :secret_key_base],
end)
encoded = url("https://pleroma.social")
Pleroma.Config.put(
[Pleroma.Web.Endpoint, :secret_key_base],
"00000000000000000000000000000000000000000000000" "00000000000000000000000000000000000000000000000"
) )
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
assert decode_url(sig, base64) == {:error, :invalid_signature} assert MediaProxy.decode_url(sig, base64) == {:error, :invalid_signature}
end
test "filename_matches preserves the encoded or decoded path" do
assert MediaProxyController.filename_matches(
%{"filename" => "/Hello world.jpg"},
"/Hello world.jpg",
"http://pleroma.social/Hello world.jpg"
) == :ok
assert MediaProxyController.filename_matches(
%{"filename" => "/Hello%20world.jpg"},
"/Hello%20world.jpg",
"http://pleroma.social/Hello%20world.jpg"
) == :ok
assert MediaProxyController.filename_matches(
%{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"},
"/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
) == :ok
assert MediaProxyController.filename_matches(
%{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"},
"/my%2Flong%2Furl%2F2019%2F07%2FS.jp",
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
end
test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
# conn.request_path will return encoded url
request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
assert MediaProxyController.filename_matches(
true,
request_path,
"https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
) == :ok
end end
test "uses the configured base_url" do test "uses the configured base_url" do
clear_config([:media_proxy, :base_url], "https://cache.pleroma.social") base_url = "https://cache.pleroma.social"
clear_config([:media_proxy, :base_url], base_url)
url = "https://pleroma.soykaf.com/static/logo.png" url = "https://pleroma.soykaf.com/static/logo.png"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url])) assert String.starts_with?(encoded, base_url)
end end
# Some sites expect ASCII encoded characters in the URL to be preserved even if # Some sites expect ASCII encoded characters in the URL to be preserved even if
@ -140,7 +93,7 @@ test "preserve ASCII encoding" do
url = url =
"https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF" "https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF"
encoded = url(url) encoded = MediaProxy.url(url)
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
@ -151,56 +104,49 @@ test "preserve non-unicode characters per RFC3986" do
url = url =
"https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}" "https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}"
encoded = url(url) encoded = MediaProxy.url(url)
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
test "preserve unicode characters" do test "preserve unicode characters" do
url = "https://ko.wikipedia.org/wiki/위키백과:대문" url = "https://ko.wikipedia.org/wiki/위키백과:대문"
encoded = url(url) encoded = MediaProxy.url(url)
assert decode_result(encoded) == url assert decode_result(encoded) == url
end end
end end
describe "when disabled" do describe "when disabled" do
setup do setup do: clear_config([:media_proxy, :enabled], false)
enabled = Pleroma.Config.get([:media_proxy, :enabled])
if enabled do
Pleroma.Config.put([:media_proxy, :enabled], false)
on_exit(fn ->
Pleroma.Config.put([:media_proxy, :enabled], enabled)
:ok
end)
end
:ok
end
test "does not encode remote urls" do test "does not encode remote urls" do
assert url("https://google.fr") == "https://google.fr" assert MediaProxy.url("https://google.fr") == "https://google.fr"
end end
end end
defp decode_result(encoded) do defp decode_result(encoded) do
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
{:ok, decoded} = decode_url(sig, base64) {:ok, decoded} = MediaProxy.decode_url(sig, base64)
decoded decoded
end end
describe "whitelist" do describe "whitelist" do
setup do setup do: clear_config([:media_proxy, :enabled], true)
Pleroma.Config.put([:media_proxy, :enabled], true)
:ok
end
test "mediaproxy whitelist" do test "mediaproxy whitelist" do
Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) clear_config([:media_proxy, :whitelist], ["https://google.com", "https://feld.me"])
url = "https://feld.me/foo.png" url = "https://feld.me/foo.png"
unencoded = url(url) unencoded = MediaProxy.url(url)
assert unencoded == url
end
# TODO: delete after removing support bare domains for media proxy whitelist
test "mediaproxy whitelist bare domains whitelist (deprecated)" do
clear_config([:media_proxy, :whitelist], ["google.com", "feld.me"])
url = "https://feld.me/foo.png"
unencoded = MediaProxy.url(url)
assert unencoded == url assert unencoded == url
end end
@ -211,17 +157,17 @@ test "does not change whitelisted urls" do
media_url = "https://mycdn.akamai.com" media_url = "https://mycdn.akamai.com"
url = "#{media_url}/static/logo.png" url = "#{media_url}/static/logo.png"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.starts_with?(encoded, media_url) assert String.starts_with?(encoded, media_url)
end end
test "ensure Pleroma.Upload base_url is always whitelisted" do test "ensure Pleroma.Upload base_url is always whitelisted" do
media_url = "https://media.pleroma.social" media_url = "https://media.pleroma.social"
Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) clear_config([Pleroma.Upload, :base_url], media_url)
url = "#{media_url}/static/logo.png" url = "#{media_url}/static/logo.png"
encoded = url(url) encoded = MediaProxy.url(url)
assert String.starts_with?(encoded, media_url) assert String.starts_with?(encoded, media_url)
end end