Merge branch 'release/2.1.2' into 'stable'

Release/2.1.2

See merge request pleroma/secteam/pleroma!17
This commit is contained in:
rinpatch 2020-09-17 19:09:10 +00:00
commit a0f5e8b27e
45 changed files with 336 additions and 89 deletions

View file

@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [2.1.2] - 2020-09-17
### Security
- Fix most MRF rules either crashing or not being applied to objects passed into the Common Pipeline (ChatMessage, Question, Answer, Audio, Event).
### Fixed
- Welcome Chat messages preventing user registration with MRF Simple Policy applied to the local instance.
- Mastodon API: the public timeline returning an error when the `reply_visibility` parameter is set to `self` for an unauthenticated user.
- Mastodon Streaming API: Handler crashes on authentication failures, resulting in error logs.
- Mastodon Streaming API: Error logs on client pings.
- Rich media: Log spam on failures. Now the error is only logged once per attempt.
### Changed
- Rich Media: A HEAD request is now done to the url, to ensure it has the appropriate content type and size before proceeding with a GET.
### Upgrade notes
1. Restart Pleroma
## [2.1.1] - 2020-09-08
### Security

View file

@ -744,7 +744,7 @@ defp restrict_replies(query, %{exclude_replies: true}) do
end
defp restrict_replies(query, %{
reply_filtering_user: user,
reply_filtering_user: %User{} = user,
reply_visibility: "self"
}) do
from(
@ -760,7 +760,7 @@ defp restrict_replies(query, %{
end
defp restrict_replies(query, %{
reply_filtering_user: user,
reply_filtering_user: %User{} = user,
reply_visibility: "following"
}) do
from(

View file

@ -5,16 +5,34 @@
defmodule Pleroma.Web.ActivityPub.MRF do
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
def filter(policies, %{} = object) do
def filter(policies, %{} = message) do
policies
|> Enum.reduce({:ok, object}, fn
policy, {:ok, object} -> policy.filter(object)
|> Enum.reduce({:ok, message}, fn
policy, {:ok, message} -> policy.filter(message)
_, error -> error
end)
end
def filter(%{} = object), do: get_policies() |> filter(object)
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]
ap_id = message["object"]
if object && ap_id do
with {:ok, message} <- filter(Map.put(message, "object", object)) do
meta = Keyword.put(meta, :object_data, message["object"])
{:ok, Map.put(message, "object", ap_id), meta}
else
{err, message} -> {err, message, meta}
end
else
{err, message} = filter(message)
{err, message, meta}
end
end
def get_policies do
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end

View file

@ -20,9 +20,17 @@ defp string_matches?(string, pattern) do
String.match?(string, pattern)
end
defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
defp object_payload(%{} = object) do
[object["content"], object["summary"], object["name"]]
|> Enum.filter(& &1)
|> Enum.join("\n")
end
defp check_reject(%{"object" => %{} = object} = message) do
payload = object_payload(object)
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
string_matches?(payload, pattern)
end) do
{:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
@ -30,12 +38,12 @@ defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} =
end
end
defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do
defp check_ftl_removal(%{"to" => to, "object" => %{} = object} = message) do
payload = object_payload(object)
if Pleroma.Constants.as_public() in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
string_matches?(payload, pattern)
end) do
to = List.delete(to, Pleroma.Constants.as_public())
cc = [Pleroma.Constants.as_public() | message["cc"] || []]
@ -51,35 +59,24 @@ defp check_ftl_removal(
end
end
defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
content =
if is_binary(content) do
content
else
""
end
defp check_replace(%{"object" => %{} = object} = message) do
object =
["content", "name", "summary"]
|> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
|> Enum.reduce(object, fn field, object ->
data =
Enum.reduce(
Pleroma.Config.get([:mrf_keyword, :replace]),
object[field],
fn {pat, repl}, acc -> String.replace(acc, pat, repl) end
)
summary =
if is_binary(summary) do
summary
else
""
end
Map.put(object, field, data)
end)
{content, summary} =
Enum.reduce(
Pleroma.Config.get([:mrf_keyword, :replace]),
{content, summary},
fn {pattern, replacement}, {content_acc, summary_acc} ->
{String.replace(content_acc, pattern, replacement),
String.replace(summary_acc, pattern, replacement)}
end
)
message = Map.put(message, "object", object)
{:ok,
message
|> put_in(["object", "content"], content)
|> put_in(["object", "summary"], summary)}
{:ok, message}
end
@impl true

View file

@ -66,7 +66,8 @@ defp check_media_nsfw(
"type" => "Create",
"object" => child_object
} = object
) do
)
when is_map(child_object) do
media_nsfw =
Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()

View file

@ -28,8 +28,7 @@ def filter(%{"actor" => actor} = message) do
}"
)
subchain
|> MRF.filter(message)
MRF.filter(subchain, message)
else
_e -> {:ok, message}
end

View file

@ -26,13 +26,17 @@ def common_pipeline(object, meta) do
{:error, e} ->
{:error, e}
{:reject, e} ->
{:reject, e}
end
end
def do_common_pipeline(object, meta) do
with {_, {:ok, validated_object, meta}} <-
{:validate_object, ObjectValidator.validate(object, meta)},
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
{_, {:ok, mrfd_object, meta}} <-
{:mrf_object, MRF.pipeline_filter(validated_object, meta)},
{_, {:ok, activity, meta}} <-
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
{_, {:ok, activity, meta}} <-
@ -40,7 +44,7 @@ def do_common_pipeline(object, meta) do
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
{:ok, activity, meta}
else
{:mrf_object, {:reject, _}} -> {:ok, nil, meta}
{:mrf_object, {:reject, message, _}} -> {:reject, message}
e -> {:error, e}
end
end

View file

@ -311,7 +311,7 @@ def fix_url(object), do: object
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
emoji =
tags
|> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end)
|> Enum.reduce(%{}, fn data, mapping ->
name = String.trim(data["name"], ":")

View file

@ -184,7 +184,8 @@ def post_chat_message_operation do
"application/json",
ChatMessage
),
400 => Operation.response("Bad Request", "application/json", ApiError)
400 => Operation.response("Bad Request", "application/json", ApiError),
422 => Operation.response("MRF Rejection", "application/json", ApiError)
},
security: [
%{

View file

@ -55,7 +55,7 @@ def create_operation do
"application/json",
%Schema{oneOf: [Status, ScheduledStatus]}
),
422 => Operation.response("Bad Request", "application/json", ApiError)
422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
}
}
end

View file

@ -49,6 +49,9 @@ def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ [])
local: true
)} do
{:ok, activity}
else
{:common_pipeline, {:reject, _} = e} -> e
e -> e
end
end

View file

@ -37,12 +37,12 @@ def init(%{qs: qs} = req, state) do
else
{:error, :bad_topic} ->
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(404, req)
req = :cowboy_req.reply(404, req)
{:ok, req, state}
{:error, :unauthorized} ->
Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}")
{:ok, req} = :cowboy_req.reply(401, req)
req = :cowboy_req.reply(401, req)
{:ok, req, state}
end
end
@ -64,7 +64,9 @@ def websocket_handle(:pong, state) do
{:ok, %{state | timer: timer()}}
end
# We never receive messages.
# We only receive pings for now
def websocket_handle(:ping, state), do: {:ok, state}
def websocket_handle(frame, state) do
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state}
@ -98,6 +100,10 @@ def websocket_info(:tick, state) do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end
# State can be `[]` only in case we terminate before switching to websocket,
# we already log errors for these cases in `init/1`, so just do nothing here
def terminate(_reason, _req, []), do: :ok
def terminate(reason, _req, state) do
Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{

View file

@ -145,7 +145,10 @@ def create_authorization(
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
"authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
}) do
render(conn, "oob_authorization_created.html", %{auth: auth})
# Enforcing the view to reuse the template when calling from other controllers
conn
|> put_view(OAuthView)
|> render("oob_authorization_created.html", %{auth: auth})
end
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{

View file

@ -90,6 +90,16 @@ def post_chat_message(
conn
|> put_view(MessageReferenceView)
|> render("show.json", chat_message_reference: cm_ref)
else
{:reject, message} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: message})
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{error: message})
end
end

View file

@ -96,6 +96,50 @@ def rich_media_get(url) do
@rich_media_options
end
Pleroma.HTTP.get(url, headers, adapter: options)
head_check =
case Pleroma.HTTP.head(url, headers, adapter: options) do
# If the HEAD request didn't reach the server for whatever reason,
# we assume the GET that comes right after won't either
{:error, _} = e ->
e
{:ok, %Tesla.Env{status: 200, headers: headers}} ->
with :ok <- check_content_type(headers),
:ok <- check_content_length(headers),
do: :ok
_ ->
:ok
end
with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, adapter: options)
end
defp check_content_type(headers) do
case List.keyfind(headers, "content-type", 0) do
{_, content_type} ->
case Plug.Conn.Utils.media_type(content_type) do
{:ok, "text", "html", _} -> :ok
_ -> {:error, {:content_type, content_type}}
end
_ ->
:ok
end
end
@max_body @rich_media_options[:max_body]
defp check_content_length(headers) do
case List.keyfind(headers, "content-length", 0) do
{_, maybe_content_length} ->
case Integer.parse(maybe_content_length) do
{content_length, ""} when content_length <= @max_body -> :ok
{_, ""} -> {:error, :body_too_large}
_ -> :ok
end
_ ->
:ok
end
end
end

View file

@ -31,6 +31,14 @@ defp get_cached_or_parse(url) do
{:ok, _data} = res ->
res
{:error, :body_too_large} = e ->
e
{:error, {:content_type, _}} = e ->
e
# The TTL is not set for the errors above, since they are unlikely to change
# with time
{:error, _} = e ->
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
Cachex.expire(:rich_media_cache, url, ttl)

View file

@ -1,2 +1,2 @@
<h1>Successfully authorized</h1>
<h2>Token code is <%= @auth.token %></h2>
<h2>Token code is <br><%= @auth.token %></h2>

View file

@ -1,2 +1,2 @@
<h1>Authorization exists</h1>
<h2>Access token is <%= @token.token %></h2>
<h2>Access token is <br><%= @token.token %></h2>

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
version: version("2.1.1"),
version: version("2.1.2"),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.77b1644622e3bae24b6b.css rel=stylesheet><link href=/static/fontello.1599568314856.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.90c4af83c1ae68f4cd95.js></script><script type=text/javascript src=/static/js/app.55d173dc5e39519aa518.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.77b1644622e3bae24b6b.css rel=stylesheet><link href=/static/fontello.1600365488745.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.90c4af83c1ae68f4cd95.js></script><script type=text/javascript src=/static/js/app.826c44232e0a76bbd9ba.js></script></body></html>

View file

@ -92,6 +92,8 @@
<glyph glyph-name="block" unicode="&#xe82a;" d="M732 359q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
<glyph glyph-name="megaphone" unicode="&#xe82b;" d="M929 500q29 0 50-21t21-51-21-50-50-21v-214q0-29-22-50t-50-22q-233 194-453 212-32-10-51-36t-17-57 22-51q-11-19-13-37t4-32 19-31 26-28 35-28q-17-32-63-46t-94-7-73 31q-4 13-17 49t-18 53-12 50-9 56 2 55 12 62h-68q-36 0-63 26t-26 63v107q0 37 26 63t63 26h268q243 0 500 215 29 0 50-22t22-50v-214z m-72-337v532q-220-168-428-191v-151q210-23 428-190z" horiz-adv-x="1000" />
<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

View file

@ -1,11 +1,11 @@
@font-face {
font-family: "Icons";
src: url("./font/fontello.1599568314856.eot");
src: url("./font/fontello.1599568314856.eot") format("embedded-opentype"),
url("./font/fontello.1599568314856.woff2") format("woff2"),
url("./font/fontello.1599568314856.woff") format("woff"),
url("./font/fontello.1599568314856.ttf") format("truetype"),
url("./font/fontello.1599568314856.svg") format("svg");
src: url("./font/fontello.1600365488745.eot");
src: url("./font/fontello.1600365488745.eot") format("embedded-opentype"),
url("./font/fontello.1600365488745.woff2") format("woff2"),
url("./font/fontello.1600365488745.woff") format("woff"),
url("./font/fontello.1600365488745.ttf") format("truetype"),
url("./font/fontello.1600365488745.svg") format("svg");
font-weight: normal;
font-style: normal;
}
@ -156,3 +156,5 @@ .icon-music::before { content: "\e828"; }
.icon-doc::before { content: "\e829"; }
.icon-block::before { content: "\e82a"; }
.icon-megaphone::before { content: "\e82b"; }

View file

@ -405,6 +405,12 @@
"css": "block",
"code": 59434,
"src": "fontawesome"
},
{
"uid": "3e674995cacc2b09692c096ea7eb6165",
"css": "megaphone",
"code": 59435,
"src": "fontawesome"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
var serviceWorkerOption = {"assets":["/static/fontello.1599568314856.css","/static/font/fontello.1599568314856.eot","/static/font/fontello.1599568314856.svg","/static/font/fontello.1599568314856.ttf","/static/font/fontello.1599568314856.woff","/static/font/fontello.1599568314856.woff2","/static/img/nsfw.74818f9.png","/static/css/app.77b1644622e3bae24b6b.css","/static/js/app.55d173dc5e39519aa518.js","/static/js/vendors~app.90c4af83c1ae68f4cd95.js","/static/css/2.0778a6a864a1307a6c41.css","/static/js/2.c92f4803ff24726cea58.js","/static/css/3.b2603a50868c68a1c192.css","/static/js/3.7d21accf4e5bd07e3ebf.js","/static/js/4.5719922a4e807145346d.js","/static/js/5.cf05c5ddbdbac890ae35.js","/static/js/6.ecfd3302a692de148391.js","/static/js/7.dd44c3d58fb9dced093d.js","/static/js/8.636322a87bb10a1754f8.js","/static/js/9.6010dbcce7b4d7c05a18.js","/static/js/10.46fbbdfaf0d4800f349b.js","/static/js/11.708cc2513c53879a92cc.js","/static/js/12.b3bf0bc313861d6ec36b.js","/static/js/13.adb8a942514d735722c4.js","/static/js/14.d015d9b2ea16407e389c.js","/static/js/15.19866e6a366ccf982284.js","/static/js/16.38a984effd54736f6a2c.js","/static/js/17.9c25507194320db2e85b.js","/static/js/18.94946caca48930c224c7.js","/static/js/19.233c81ac2c28d55e9f13.js","/static/js/20.818c38d27369c3a4d677.js","/static/js/21.ce4cda179d888ca6bc2a.js","/static/js/22.2ea93c6cc569ef0256ab.js","/static/js/23.a57a7845cc20fafd06d1.js","/static/js/24.35eb55a657b5485f8491.js","/static/js/25.5a9efe20e3ae1352e6d2.js","/static/js/26.cf13231d524e5ca3b3e6.js","/static/js/27.fca8d4f6e444bd14f376.js","/static/js/28.e0f9f164e0bfd890dc61.js","/static/js/29.0b69359f0fe5c0785746.js","/static/js/30.fce58be0b52ca3e32fa4.js"]};
var serviceWorkerOption = {"assets":["/static/fontello.1600365488745.css","/static/font/fontello.1600365488745.eot","/static/font/fontello.1600365488745.svg","/static/font/fontello.1600365488745.ttf","/static/font/fontello.1600365488745.woff","/static/font/fontello.1600365488745.woff2","/static/img/nsfw.74818f9.png","/static/css/app.77b1644622e3bae24b6b.css","/static/js/app.826c44232e0a76bbd9ba.js","/static/js/vendors~app.90c4af83c1ae68f4cd95.js","/static/css/2.0778a6a864a1307a6c41.css","/static/js/2.e852a6b4b3bba752b838.js","/static/css/3.b2603a50868c68a1c192.css","/static/js/3.7d21accf4e5bd07e3ebf.js","/static/js/4.5719922a4e807145346d.js","/static/js/5.cf05c5ddbdbac890ae35.js","/static/js/6.ecfd3302a692de148391.js","/static/js/7.dd44c3d58fb9dced093d.js","/static/js/8.636322a87bb10a1754f8.js","/static/js/9.6010dbcce7b4d7c05a18.js","/static/js/10.46fbbdfaf0d4800f349b.js","/static/js/11.708cc2513c53879a92cc.js","/static/js/12.b3bf0bc313861d6ec36b.js","/static/js/13.adb8a942514d735722c4.js","/static/js/14.d015d9b2ea16407e389c.js","/static/js/15.19866e6a366ccf982284.js","/static/js/16.38a984effd54736f6a2c.js","/static/js/17.9c25507194320db2e85b.js","/static/js/18.94946caca48930c224c7.js","/static/js/19.233c81ac2c28d55e9f13.js","/static/js/20.818c38d27369c3a4d677.js","/static/js/21.ce4cda179d888ca6bc2a.js","/static/js/22.2ea93c6cc569ef0256ab.js","/static/js/23.a57a7845cc20fafd06d1.js","/static/js/24.35eb55a657b5485f8491.js","/static/js/25.5a9efe20e3ae1352e6d2.js","/static/js/26.cf13231d524e5ca3b3e6.js","/static/js/27.fca8d4f6e444bd14f376.js","/static/js/28.e0f9f164e0bfd890dc61.js","/static/js/29.0b69359f0fe5c0785746.js","/static/js/30.fce58be0b52ca3e32fa4.js"]};
!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/",n(n.s=196)}([function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){t.exports=n(53)},function(t,e,n){var r=n(32),o="object"==typeof self&&self&&self.Object===Object&&self,i=r||o||Function("return this")();t.exports=i},function(t,e,n){var r=n(97),o=n(100);t.exports=function(t,e){var n=o(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(8),o=n(64),i=n(65),a="[object Null]",s="[object Undefined]",c=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?s:a:c&&c in Object(t)?o(t):i(t)}},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r=n(31),o=n(20);t.exports=function(t){return null!=t&&o(t.length)&&!r(t)}},function(t,e,n){var r=n(2).Symbol;t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(4),o=n(5),i="[object Symbol]";t.exports=function(t){return"symbol"==typeof t||o(t)&&r(t)==i}},function(t,e,n){var r=n(72),o=n(78),i=n(7);t.exports=function(t){return i(t)?r(t):o(t)}},function(t,e,n){var r=n(87),o=n(88),i=n(89),a=n(90),s=n(91);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}c.prototype.clear=r,c.prototype.delete=o,c.prototype.get=i,c.prototype.has=a,c.prototype.set=s,t.exports=c},function(t,e,n){var r=n(24);t.exports=function(t,e){for(var n=t.length;n--;)if(r(t[n][0],e))return n;return-1}},function(t,e,n){var r=n(3)(Object,"create");t.exports=r},function(t,e,n){var r=n(109);t.exports=function(t,e){var n=t.__data__;return r(e)?n["string"==typeof e?"string":"hash"]:n.map}},function(t,e,n){var r=n(10),o=1/0;t.exports=function(t){if("string"==typeof t||r(t))return t;var e=t+"";return"0"==e&&1/t==-o?"-0":e}},function(t,e){t.exports=function(t){return t}},function(t,e,n){var r=n(42),o=n(164),i=n(37),a=n(0);t.exports=function(t,e){return(a(t)?r:o)(t,i(e,3))}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e){var n=9007199254740991;t.exports=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=n}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length,o=Array(r);++n<r;)o[n]=e(t[n],n,t);return o}},function(t,e,n){var r=n(74),o=n(5),i=Object.prototype,a=i.hasOwnProperty,s=i.propertyIsEnumerable,c=r(function(){return arguments}())?r:function(t){return o(t)&&a.call(t,"callee")&&!s.call(t,"callee")};t.exports=c},function(t,e){var n=9007199254740991,r=/^(?:0|[1-9]\d*)$/;t.exports=function(t,e){var o=typeof t;return!!(e=null==e?n:e)&&("number"==o||"symbol"!=o&&r.test(t))&&t>-1&&t%1==0&&t<e}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(3)(n(2),"Map");t.exports=r},function(t,e,n){var r=n(101),o=n(108),i=n(110),a=n(111),s=n(112);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}c.prototype.clear=r,c.prototype.delete=o,c.prototype.get=i,c.prototype.has=a,c.prototype.set=s,t.exports=c},function(t,e,n){var r=n(0),o=n(10),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,a=/^\w*$/;t.exports=function(t,e){if(r(t))return!1;var n=typeof t;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=t&&!o(t))||a.test(t)||!i.test(t)||null!=e&&t in Object(e)}},function(t,e){
/*!

View file

@ -99,30 +99,30 @@ test "accepts valid tokens", state do
test "accepts the 'user' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
assert capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user")
Process.sleep(30)
end) =~ ":badarg"
capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user")
Process.sleep(30)
end)
end
test "accepts the 'user:notification' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
assert capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user:notification")
Process.sleep(30)
end) =~ ":badarg"
capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user:notification")
Process.sleep(30)
end)
end
test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
assert capture_log(fn ->
assert {:error, {401, _}} =
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
capture_log(fn ->
assert {:error, {401, _}} =
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
Process.sleep(30)
end) =~ ":badarg"
Process.sleep(30)
end)
end
end
end

View file

@ -1436,4 +1436,21 @@ def post(url, query, body, headers) do
inspect(headers)
}"}
end
# Most of the rich media mocks are missing HEAD requests, so we just return 404.
@rich_media_mocks [
"https://example.com/ogp",
"https://example.com/ogp-missing-data",
"https://example.com/twitter-card"
]
def head(url, _query, _body, _headers) when url in @rich_media_mocks do
{:ok, %Tesla.Env{status: 404, body: ""}}
end
def head(url, query, body, headers) do
{:error,
"Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{
inspect(headers)
}"}
end
end

View file

@ -440,6 +440,45 @@ test "it sends a welcome chat message if it is set" do
assert activity.actor == welcome_user.ap_id
end
setup do:
clear_config(:mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
report_removal: [],
reject: [],
followers_only: [],
accept: [],
avatar_removal: [],
banner_removal: [],
reject_deletes: []
)
setup do:
clear_config(:mrf,
policies: [
Pleroma.Web.ActivityPub.MRF.SimplePolicy
]
)
test "it sends a welcome chat message when Simple policy applied to local instance" do
Pleroma.Config.put([:mrf_simple, :media_nsfw], ["localhost"])
welcome_user = insert(:user)
Pleroma.Config.put([:welcome, :chat_message, :enabled], true)
Pleroma.Config.put([:welcome, :chat_message, :sender_nickname], welcome_user.nickname)
Pleroma.Config.put([:welcome, :chat_message, :message], "Hello, this is a chat message")
cng = User.register_changeset(%User{}, @full_user_data)
{:ok, registered_user} = User.register(cng)
ObanHelpers.perform_all()
activity = Repo.one(Pleroma.Activity)
assert registered_user.ap_id in activity.recipients
assert Object.normalize(activity).data["content"] =~ "chat message"
assert activity.actor == welcome_user.ap_id
end
test "it sends a welcome email message if it is set" do
welcome_user = insert(:user)
Pleroma.Config.put([:welcome, :email, :enabled], true)

View file

@ -1773,6 +1773,14 @@ test "public timeline with default reply_visibility `self`", %{users: %{u1: user
|> Enum.map(& &1.id)
assert activities_ids == []
activities_ids =
%{}
|> Map.put(:reply_visibility, "self")
|> Map.put(:reply_filtering_user, nil)
|> ActivityPub.fetch_public_activities()
assert activities_ids == []
end
test "home timeline", %{users: %{u1: user}} do

View file

@ -26,7 +26,7 @@ test "when given an `object_data` in meta, Federation will receive a the origina
{
Pleroma.Web.ActivityPub.MRF,
[],
[filter: fn o -> {:ok, o} end]
[pipeline_filter: fn o, m -> {:ok, o, m} end]
},
{
Pleroma.Web.ActivityPub.ActivityPub,
@ -51,7 +51,7 @@ test "when given an `object_data` in meta, Federation will receive a the origina
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
refute called(Pleroma.Web.Federator.publish(activity))
@ -68,7 +68,7 @@ test "it goes through validation, filtering, persisting, side effects and federa
{
Pleroma.Web.ActivityPub.MRF,
[],
[filter: fn o -> {:ok, o} end]
[pipeline_filter: fn o, m -> {:ok, o, m} end]
},
{
Pleroma.Web.ActivityPub.ActivityPub,
@ -93,7 +93,7 @@ test "it goes through validation, filtering, persisting, side effects and federa
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
assert_called(Pleroma.Web.Federator.publish(activity))
@ -109,7 +109,7 @@ test "it goes through validation, filtering, persisting, side effects without fe
{
Pleroma.Web.ActivityPub.MRF,
[],
[filter: fn o -> {:ok, o} end]
[pipeline_filter: fn o, m -> {:ok, o, m} end]
},
{
Pleroma.Web.ActivityPub.ActivityPub,
@ -131,7 +131,7 @@ test "it goes through validation, filtering, persisting, side effects without fe
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
end
@ -148,7 +148,7 @@ test "it goes through validation, filtering, persisting, side effects without fe
{
Pleroma.Web.ActivityPub.MRF,
[],
[filter: fn o -> {:ok, o} end]
[pipeline_filter: fn o, m -> {:ok, o, m} end]
},
{
Pleroma.Web.ActivityPub.ActivityPub,
@ -170,7 +170,7 @@ test "it goes through validation, filtering, persisting, side effects without fe
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
assert_called(Pleroma.Web.ActivityPub.MRF.pipeline_filter(activity, meta))
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
end

View file

@ -213,6 +213,17 @@ test "it reject messages over the local limit" do
assert message == :content_too_long
end
test "it reject messages via MRF" do
clear_config([:mrf_keyword, :reject], ["GNO"])
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
author = insert(:user)
recipient = insert(:user)
assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
end
end
describe "unblocking" do

View file

@ -100,7 +100,7 @@ test "it fails if there is no content", %{conn: conn, user: user} do
|> post("/api/v1/pleroma/chats/#{chat.id}/messages")
|> json_response_and_validate_schema(400)
assert result
assert %{"error" => "no_content"} == result
end
test "it works with an attachment", %{conn: conn, user: user} do
@ -126,6 +126,23 @@ test "it works with an attachment", %{conn: conn, user: user} do
assert result["attachment"]
end
test "gets MRF reason when rejected", %{conn: conn, user: user} do
clear_config([:mrf_keyword, :reject], ["GNO"])
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
other_user = insert(:user)
{:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
result =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "GNO/Linux"})
|> json_response_and_validate_schema(422)
assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} == result
end
end
describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do

View file

@ -56,6 +56,27 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
%{method: :get, url: "http://example.com/error"} ->
{:error, :overload}
%{
method: :head,
url: "http://example.com/huge-page"
} ->
%Tesla.Env{
status: 200,
headers: [{"content-length", "2000001"}, {"content-type", "text/html"}]
}
%{
method: :head,
url: "http://example.com/pdf-file"
} ->
%Tesla.Env{
status: 200,
headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}]
}
%{method: :head} ->
%Tesla.Env{status: 404, body: "", headers: []}
end)
:ok
@ -146,4 +167,12 @@ test "rejects invalid OGP data" do
test "returns error if getting page was not successful" do
assert {:error, :overload} = Parser.parse("http://example.com/error")
end
test "does a HEAD request to check if the body is too large" do
assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page")
end
test "does a HEAD request to check if the body is html" do
assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file")
end
end