Nothing much just a build up of things
[anni] / patches / 1(2.5.2).diff
1 diff --git a/CHANGELOG.md b/CHANGELOG.md
2 index 6a7ec1032876ec7c4aa4043bf095599749f68160..f6fc6aaee23c312a43f67b5210688b28e1060554 100644
3 --- a/CHANGELOG.md
4 +++ b/CHANGELOG.md
5 @@ -14,6 +14,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6  
7  ### Removed
8  
9 +## 2.5.2
10 +
11 +### Security
12 +- `/proxy` endpoint now sets a Content-Security-Policy (sandbox)
13 +- WebSocket endpoint now respects unauthenticated restrictions for streams of public posts
14 +- OEmbed HTML tags are now filtered
15 +
16 +### Changed
17 +- docs: Be more explicit about the level of compatibility of OTP releases
18 +- Set default background worker timeout to 15 minutes
19 +
20 +### Fixed
21 +- Atom/RSS formatting (HTML truncation, published, missing summary)
22 +- Remove `static_fe` pipeline for `/users/:nickname/feed`
23 +- Stop oban from retrying if validating errors occur when processing incoming data
24 +- Make sure object refetching as used by already received polls follows MRF rules
25 +
26 +### Removed
27 +- BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact)
28 +
29  ## 2.5.1
30  
31  ### Added
32 diff --git a/changelog.d/3126.fix b/changelog.d/3126.fix
33 new file mode 100644
34 index 0000000000000000000000000000000000000000..91d396c89d4fbc9ee4c572006a56f376d309d241
35 --- /dev/null
36 +++ b/changelog.d/3126.fix
37 @@ -0,0 +1 @@
38 +MediaProxy responses now return a sandbox CSP header
39 diff --git a/changelog.d/3883.fix b/changelog.d/3883.fix
40 new file mode 100644
41 index 0000000000000000000000000000000000000000..6824f201345db5fd24305782dc6b842692216f4e
42 --- /dev/null
43 +++ b/changelog.d/3883.fix
44 @@ -0,0 +1 @@
45 +Fix abnormal behaviour when refetching a poll
46 diff --git a/changelog.d/3891.fix b/changelog.d/3891.fix
47 new file mode 100644
48 index 0000000000000000000000000000000000000000..f1fb62d826a3a3f5e442a9f2a585550975175570
49 --- /dev/null
50 +++ b/changelog.d/3891.fix
51 @@ -0,0 +1 @@
52 +OEmbed HTML tags are now filtered
53 diff --git a/changelog.d/fix-object-test.fix b/changelog.d/fix-object-test.fix
54 new file mode 100644
55 index 0000000000000000000000000000000000000000..5eea719f0bd89557ab2052d925036bf4802442bb
56 --- /dev/null
57 +++ b/changelog.d/fix-object-test.fix
58 @@ -0,0 +1 @@
59 +Correctly handle the situation when a poll has both "anyOf" and "oneOf" but one of them being empty
60 diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
61 index 8c02201e67699c54b694ddef13d4e54520751c98..f2812346b1348cdf5c383e1812d841a4ffaaf10c 100644
62 --- a/docs/installation/otp_en.md
63 +++ b/docs/installation/otp_en.md
64 @@ -2,15 +2,16 @@
65  
66  {! backend/installation/otp_vs_from_source.include !}
67  
68 -This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro.
69 +This guide covers a installation using OTP releases as built by the Pleroma project, it is meant as a fallback to distribution packages/recipes which are the preferred installation method.  
70 +To install Pleroma from source, please check out the corresponding guide for your distro.
71  
72  ## Pre-requisites
73 -* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
74 +* A machine you have root access to running Debian GNU/Linux or compatible (eg. Ubuntu), or Alpine on `x86_64`, `aarch64` or `armv7l` CPU. If you are not sure what you are running see [Detecting flavour section](#detecting-flavour) below
75  * A (sub)domain pointed to the machine
76  
77 -You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`.
78 +You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo -i`/`su`.
79  
80 -While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
81 +Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine.
82  
83  ### Detecting flavour
84  
85 @@ -19,7 +20,7 @@ Paste the following into the shell:
86  arch="$(uname -m)";if [ "$arch" = "x86_64" ];then arch="amd64";elif [ "$arch" = "armv7l" ];then arch="arm";elif [ "$arch" = "aarch64" ];then arch="arm64";else echo "Unsupported arch: $arch">&2;fi;if getconf GNU_LIBC_VERSION>/dev/null;then libc_postfix="";elif [ "$(ldd 2>&1|head -c 9)" = "musl libc" ];then libc_postfix="-musl";elif [ "$(find /lib/libc.musl*|wc -l)" ];then libc_postfix="-musl";else echo "Unsupported libc">&2;fi;echo "$arch$libc_postfix"
87  ```
88  
89 -If your platform is supported the output will contain the flavour string, you will need it later. If not, this just means that we don't build releases for your platform, you can still try installing from source.
90 +This should give your flavour string. If not this just means that we don't build releases for your platform, you can still try installing from source.
91  
92  ### Installing the required packages
93  
94 diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
95 index a9a9eeeed17adaacc51a5cd6927e5f0daa65e79d..cc3772563b8380af0da1a64ceba238ea144a05ac 100644
96 --- a/lib/pleroma/object/fetcher.ex
97 +++ b/lib/pleroma/object/fetcher.ex
98 @@ -8,77 +8,30 @@ defmodule Pleroma.Object.Fetcher do
99    alias Pleroma.Maps
100    alias Pleroma.Object
101    alias Pleroma.Object.Containment
102 -  alias Pleroma.Repo
103    alias Pleroma.Signature
104    alias Pleroma.Web.ActivityPub.InternalFetchActor
105 +  alias Pleroma.Web.ActivityPub.MRF
106    alias Pleroma.Web.ActivityPub.ObjectValidator
107 +  alias Pleroma.Web.ActivityPub.Pipeline
108    alias Pleroma.Web.ActivityPub.Transmogrifier
109    alias Pleroma.Web.Federator
110  
111    require Logger
112    require Pleroma.Constants
113  
114 -  defp touch_changeset(changeset) do
115 -    updated_at =
116 -      NaiveDateTime.utc_now()
117 -      |> NaiveDateTime.truncate(:second)
118 -
119 -    Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
120 -  end
121 -
122 -  defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do
123 -    has_history? = fn
124 -      %{"formerRepresentations" => %{"orderedItems" => list}} when is_list(list) -> true
125 -      _ -> false
126 -    end
127 -
128 -    internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields())
129 -
130 -    remote_history_exists? = has_history?.(new_data)
131 -
132 -    # If the remote history exists, we treat that as the only source of truth.
133 -    new_data =
134 -      if has_history?.(old_data) and not remote_history_exists? do
135 -        Map.put(new_data, "formerRepresentations", old_data["formerRepresentations"])
136 -      else
137 -        new_data
138 -      end
139 -
140 -    # If the remote does not have history information, we need to manage it ourselves
141 -    new_data =
142 -      if not remote_history_exists? do
143 -        changed? =
144 -          Pleroma.Constants.status_updatable_fields()
145 -          |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end)
146 -
147 -        %{updated_object: updated_object} =
148 -          new_data
149 -          |> Object.Updater.maybe_update_history(old_data,
150 -            updated: changed?,
151 -            use_history_in_new_object?: false
152 -          )
153 -
154 -        updated_object
155 -      else
156 -        new_data
157 -      end
158 -
159 -    Map.merge(new_data, internal_fields)
160 -  end
161 -
162 -  defp maybe_reinject_internal_fields(_, new_data), do: new_data
163 -
164    @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
165 -  defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do
166 +  defp reinject_object(%Object{data: %{}} = object, new_data) do
167      Logger.debug("Reinjecting object #{new_data["id"]}")
168  
169 -    with data <- maybe_reinject_internal_fields(object, new_data),
170 -         {:ok, data, _} <- ObjectValidator.validate(data, %{}),
171 -         changeset <- Object.change(object, %{data: data}),
172 -         changeset <- touch_changeset(changeset),
173 -         {:ok, object} <- Repo.insert_or_update(changeset),
174 -         {:ok, object} <- Object.set_cache(object) do
175 -      {:ok, object}
176 +    with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}),
177 +         {:ok, new_data} <- MRF.filter(new_data),
178 +         {:ok, new_object, _} <-
179 +           Object.Updater.do_update_and_invalidate_cache(
180 +             object,
181 +             new_data,
182 +             _touch_changeset? = true
183 +           ) do
184 +      {:ok, new_object}
185      else
186        e ->
187          Logger.error("Error while processing object: #{inspect(e)}")
188 @@ -86,20 +39,11 @@ defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data)
189      end
190    end
191  
192 -  defp reinject_object(%Object{} = object, new_data) do
193 -    Logger.debug("Reinjecting object #{new_data["id"]}")
194 -
195 -    with new_data <- Transmogrifier.fix_object(new_data),
196 -         data <- maybe_reinject_internal_fields(object, new_data),
197 -         changeset <- Object.change(object, %{data: data}),
198 -         changeset <- touch_changeset(changeset),
199 -         {:ok, object} <- Repo.insert_or_update(changeset),
200 -         {:ok, object} <- Object.set_cache(object) do
201 +  defp reinject_object(_, new_data) do
202 +    with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do
203        {:ok, object}
204      else
205 -      e ->
206 -        Logger.error("Error while processing object: #{inspect(e)}")
207 -        {:error, e}
208 +      e -> e
209      end
210    end
211  
212 diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex
213 index ab38d3ed2b65c843b9268e6b01cb214dbc26b3bb..b1e4870bac4c7900966a57d7dd6774606c0f6109 100644
214 --- a/lib/pleroma/object/updater.ex
215 +++ b/lib/pleroma/object/updater.ex
216 @@ -5,6 +5,9 @@
217  defmodule Pleroma.Object.Updater do
218    require Pleroma.Constants
219  
220 +  alias Pleroma.Object
221 +  alias Pleroma.Repo
222 +
223    def update_content_fields(orig_object_data, updated_object) do
224      Pleroma.Constants.status_updatable_fields()
225      |> Enum.reduce(
226 @@ -97,12 +100,14 @@ def maybe_update_history(
227    end
228  
229    defp maybe_update_poll(to_be_updated, updated_object) do
230 -    choice_key = fn data ->
231 -      if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
232 +    choice_key = fn
233 +      %{"anyOf" => [_ | _]} -> "anyOf"
234 +      %{"oneOf" => [_ | _]} -> "oneOf"
235 +      _ -> nil
236      end
237  
238      with true <- to_be_updated["type"] == "Question",
239 -         key <- choice_key.(updated_object),
240 +         key when not is_nil(key) <- choice_key.(updated_object),
241           true <- key == choice_key.(to_be_updated),
242           orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
243           new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
244 @@ -237,4 +242,49 @@ def do_with_history(object, fun) do
245        {:history_items, e} -> e
246      end
247    end
248 +
249 +  defp maybe_touch_changeset(changeset, true) do
250 +    updated_at =
251 +      NaiveDateTime.utc_now()
252 +      |> NaiveDateTime.truncate(:second)
253 +
254 +    Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
255 +  end
256 +
257 +  defp maybe_touch_changeset(changeset, _), do: changeset
258 +
259 +  def do_update_and_invalidate_cache(orig_object, updated_object, touch_changeset? \\ false) do
260 +    orig_object_ap_id = updated_object["id"]
261 +    orig_object_data = orig_object.data
262 +
263 +    %{
264 +      updated_data: updated_object_data,
265 +      updated: updated,
266 +      used_history_in_new_object?: used_history_in_new_object?
267 +    } = make_new_object_data_from_update_object(orig_object_data, updated_object)
268 +
269 +    changeset =
270 +      orig_object
271 +      |> Repo.preload(:hashtags)
272 +      |> Object.change(%{data: updated_object_data})
273 +      |> maybe_touch_changeset(touch_changeset?)
274 +
275 +    with {:ok, new_object} <- Repo.update(changeset),
276 +         {:ok, _} <- Object.invalid_object_cache(new_object),
277 +         {:ok, _} <- Object.set_cache(new_object),
278 +         # The metadata/utils.ex uses the object id for the cache.
279 +         {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
280 +      if used_history_in_new_object? do
281 +        with create_activity when not is_nil(create_activity) <-
282 +               Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
283 +             {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
284 +          nil
285 +        else
286 +          _ -> nil
287 +        end
288 +      end
289 +
290 +      {:ok, new_object, updated}
291 +    end
292 +  end
293  end
294 diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
295 index a2152b945235892d7f87c3fb4b185a740bd4112e..fc5dec3628c7f5e0e19c2c91cdb4baa22f103a39 100644
296 --- a/lib/pleroma/web/activity_pub/side_effects.ex
297 +++ b/lib/pleroma/web/activity_pub/side_effects.ex
298 @@ -428,37 +428,13 @@ defp handle_update_object(
299        end
300  
301      if orig_object_data["type"] in Pleroma.Constants.updatable_object_types() do
302 -      %{
303 -        updated_data: updated_object_data,
304 -        updated: updated,
305 -        used_history_in_new_object?: used_history_in_new_object?
306 -      } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object)
307 -
308 -      changeset =
309 -        orig_object
310 -        |> Repo.preload(:hashtags)
311 -        |> Object.change(%{data: updated_object_data})
312 -
313 -      with {:ok, new_object} <- Repo.update(changeset),
314 -           {:ok, _} <- Object.invalid_object_cache(new_object),
315 -           {:ok, _} <- Object.set_cache(new_object),
316 -           # The metadata/utils.ex uses the object id for the cache.
317 -           {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
318 -        if used_history_in_new_object? do
319 -          with create_activity when not is_nil(create_activity) <-
320 -                 Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
321 -               {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
322 -            nil
323 -          else
324 -            _ -> nil
325 -          end
326 -        end
327 +      {:ok, _, updated} =
328 +        Object.Updater.do_update_and_invalidate_cache(orig_object, updated_object)
329  
330 -        if updated do
331 -          object
332 -          |> Activity.normalize()
333 -          |> ActivityPub.notify_and_stream()
334 -        end
335 +      if updated do
336 +        object
337 +        |> Activity.normalize()
338 +        |> ActivityPub.notify_and_stream()
339        end
340      end
341  
342 diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
343 index 449659f4bbe7b37a3c5f9d11b55fe7036d0d425f..034722eb2e7a7c99fcb1384132d81b6ed7dcd3f1 100644
344 --- a/lib/pleroma/web/feed/feed_view.ex
345 +++ b/lib/pleroma/web/feed/feed_view.ex
346 @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Feed.FeedView do
347    use Phoenix.HTML
348    use Pleroma.Web, :view
349  
350 -  alias Pleroma.Formatter
351    alias Pleroma.Object
352    alias Pleroma.User
353    alias Pleroma.Web.Gettext
354 @@ -72,7 +71,9 @@ def logo(user) do
355  
356    def last_activity(activities), do: List.last(activities)
357  
358 -  def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do
359 +  def activity_title(%{"content" => content} = data, opts \\ %{}) do
360 +    summary = Map.get(data, "summary", "")
361 +
362      title =
363        cond do
364          summary != "" -> summary
365 @@ -81,9 +82,8 @@ def activity_title(%{"content" => content, "summary" => summary} = data, opts \\
366        end
367  
368      title
369 -    |> Pleroma.Web.Metadata.Utils.scrub_html()
370 -    |> Pleroma.Emoji.Formatter.demojify()
371 -    |> Formatter.truncate(opts[:max_length], opts[:omission])
372 +    |> Pleroma.Web.Metadata.Utils.scrub_html_and_truncate(opts[:max_length], opts[:omission])
373 +    |> HtmlEntities.encode()
374    end
375  
376    def activity_description(data) do
377 diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
378 index d2ad62c13910fbb4a53630ff395089ce204ee8dc..bda5b36edcea2341d29052567b0b4638d3d41660 100644
379 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex
380 +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
381 @@ -12,6 +12,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
382    alias Pleroma.Web.MediaProxy
383    alias Plug.Conn
384  
385 +  plug(:sandbox)
386 +
387    def remote(conn, %{"sig" => sig64, "url" => url64}) do
388      with {_, true} <- {:enabled, MediaProxy.enabled?()},
389           {:ok, url} <- MediaProxy.decode_url(sig64, url64),
390 @@ -202,4 +204,9 @@ defp media_preview_proxy_config do
391    defp media_proxy_opts do
392      Config.get([:media_proxy, :proxy_opts], [])
393    end
394 +
395 +  defp sandbox(conn, _params) do
396 +    conn
397 +    |> merge_resp_headers([{"content-security-policy", "sandbox;"}])
398 +  end
399  end
400 diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
401 index 15414a988d72ca36c2ea8e92f6b7528ce4d8df94..80a8be9a2d5e2db48f37d1ff809c1e565517a087 100644
402 --- a/lib/pleroma/web/metadata/utils.ex
403 +++ b/lib/pleroma/web/metadata/utils.ex
404 @@ -30,12 +30,13 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
405      |> scrub_html_and_truncate_object_field(object)
406    end
407  
408 -  def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
409 +  def scrub_html_and_truncate(content, max_length \\ 200, omission \\ "...")
410 +      when is_binary(content) do
411      content
412      |> scrub_html
413      |> Emoji.Formatter.demojify()
414      |> HtmlEntities.decode()
415 -    |> Formatter.truncate(max_length)
416 +    |> Formatter.truncate(max_length, omission)
417    end
418  
419    def scrub_html(content) when is_binary(content) do
420 diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
421 index a7fd697b5d106a5101ea484d79ca05b3cbd3fc97..f912a1542f8a761cb4cee485528beddd2f0cc60c 100644
422 --- a/lib/pleroma/web/plugs/authentication_plug.ex
423 +++ b/lib/pleroma/web/plugs/authentication_plug.ex
424 @@ -38,10 +38,6 @@ def call(
425  
426    def call(conn, _), do: conn
427  
428 -  def checkpw(password, "$6" <> _ = password_hash) do
429 -    :crypt.crypt(password, password_hash) == password_hash
430 -  end
431 -
432    def checkpw(password, "$2" <> _ = password_hash) do
433      # Handle bcrypt passwords for Mastodon migration
434      Bcrypt.verify_pass(password, password_hash)
435 @@ -60,10 +56,6 @@ def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
436      do_update_password(user, password)
437    end
438  
439 -  def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
440 -    do_update_password(user, password)
441 -  end
442 -
443    def maybe_update_password(user, _), do: {:ok, user}
444  
445    defp do_update_password(user, password) do
446 diff --git a/lib/pleroma/web/rich_media/parsers/o_embed.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex
447 index 75318d9c7207bc0cf2890a5193bb0eeed6600418..0f303176ce849ad36587859edf7d1af73773edbe 100644
448 --- a/lib/pleroma/web/rich_media/parsers/o_embed.ex
449 +++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex
450 @@ -6,8 +6,8 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
451    def parse(html, _data) do
452      with elements = [_ | _] <- get_discovery_data(html),
453           oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
454 -         {:ok, oembed_data} <- get_oembed_data(oembed_url) do
455 -      oembed_data
456 +         {:ok, oembed_data = %{"html" => html}} <- get_oembed_data(oembed_url) do
457 +      %{oembed_data | "html" => Pleroma.HTML.filter_tags(html)}
458      else
459        _e -> %{}
460      end
461 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
462 index ba1d64ab23dbf9f21342c239d800708724e9498d..c1a690e28a59009292587d76176a80cd73715fb4 100644
463 --- a/lib/pleroma/web/router.ex
464 +++ b/lib/pleroma/web/router.ex
465 @@ -835,8 +835,7 @@ defmodule Pleroma.Web.Router do
466    end
467  
468    scope "/", Pleroma.Web do
469 -    # Note: html format is supported only if static FE is enabled
470 -    pipe_through([:accepts_html_xml, :static_fe])
471 +    pipe_through([:accepts_html_xml])
472  
473      get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
474    end
475 diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
476 index 3c0da5c276d7aa0936511a9726d001405eee0243..b9a04cc7674408bd063cd0cb5c4d3ef8d055afb8 100644
477 --- a/lib/pleroma/web/streamer.ex
478 +++ b/lib/pleroma/web/streamer.ex
479 @@ -25,6 +25,7 @@ defmodule Pleroma.Web.Streamer do
480    def registry, do: @registry
481  
482    @public_streams ["public", "public:local", "public:media", "public:local:media"]
483 +  @local_streams ["public:local", "public:local:media"]
484    @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
485  
486    @doc "Expands and authorizes a stream, and registers the process for streaming."
487 @@ -41,14 +42,37 @@ def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
488      end
489    end
490  
491 +  defp can_access_stream(user, oauth_token, kind) do
492 +    with {_, true} <- {:restrict?, Config.restrict_unauthenticated_access?(:timelines, kind)},
493 +         {_, %User{id: user_id}, %Token{user_id: user_id}} <- {:user, user, oauth_token},
494 +         {_, true} <-
495 +           {:scopes,
496 +            OAuthScopesPlug.filter_descendants(["read:statuses"], oauth_token.scopes) != []} do
497 +      true
498 +    else
499 +      {:restrict?, _} ->
500 +        true
501 +
502 +      _ ->
503 +        false
504 +    end
505 +  end
506 +
507    @doc "Expand and authorizes a stream"
508    @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
509            {:ok, topic :: String.t()} | {:error, :bad_topic}
510    def get_topic(stream, user, oauth_token, params \\ %{})
511  
512 -  # Allow all public steams.
513 -  def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
514 -    {:ok, stream}
515 +  # Allow all public steams if the instance allows unauthenticated access.
516 +  # Otherwise, only allow users with valid oauth tokens.
517 +  def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do
518 +    kind = if stream in @local_streams, do: :local, else: :federated
519 +
520 +    if can_access_stream(user, oauth_token, kind) do
521 +      {:ok, stream}
522 +    else
523 +      {:error, :unauthorized}
524 +    end
525    end
526  
527    # Allow all hashtags streams.
528 @@ -57,12 +81,20 @@ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
529    end
530  
531    # Allow remote instance streams.
532 -  def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
533 -    {:ok, "public:remote:" <> instance}
534 +  def get_topic("public:remote", user, oauth_token, %{"instance" => instance} = _params) do
535 +    if can_access_stream(user, oauth_token, :federated) do
536 +      {:ok, "public:remote:" <> instance}
537 +    else
538 +      {:error, :unauthorized}
539 +    end
540    end
541  
542 -  def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
543 -    {:ok, "public:remote:media:" <> instance}
544 +  def get_topic("public:remote:media", user, oauth_token, %{"instance" => instance} = _params) do
545 +    if can_access_stream(user, oauth_token, :federated) do
546 +      {:ok, "public:remote:media:" <> instance}
547 +    else
548 +      {:error, :unauthorized}
549 +    end
550    end
551  
552    # Expand user streams.
553 diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
554 index 260338772ac9823de69c4c85fe2eb7765bcd9b9d..b774f7984428c554757867b3d1934b7d24bc1547 100644
555 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
556 +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex
557 @@ -4,8 +4,8 @@
558    <id><%= @data["id"] %></id>
559    <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
560    <content type="html"><%= activity_description(@data) %></content>
561 -  <published><%= to_rfc3339(@activity.data["published"]) %></published>
562 -  <updated><%= to_rfc3339(@activity.data["published"]) %></updated>
563 +  <published><%= to_rfc3339(@data["published"]) %></published>
564 +  <updated><%= to_rfc3339(@data["published"]) %></updated>
565    <ostatus:conversation ref="<%= activity_context(@activity) %>">
566      <%= activity_context(@activity) %>
567    </ostatus:conversation>
568 diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
569 index 5c8f35fe47f44a93d889580334463d884143e32b..7de98f73682965562b1d013cfe17e804c397ba83 100644
570 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
571 +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex
572 @@ -4,7 +4,7 @@
573    <guid><%= @data["id"] %></guid>
574    <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
575    <description><%= activity_description(@data) %></description>
576 -  <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate>
577 +  <pubDate><%= to_rfc2822(@data["published"]) %></pubDate>
578    <ostatus:conversation ref="<%= activity_context(@activity) %>">
579      <%= activity_context(@activity) %>
580    </ostatus:conversation>
581 diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
582 index 25980c1e4292b1eb3e197325229e1f2118747cb6..03c222975ec756c28fbdc35578402ddd0976fbab 100644
583 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
584 +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex
585 @@ -7,8 +7,8 @@
586    <id><%= @data["id"] %></id>
587    <title><%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %></title>
588    <content type="html"><%= activity_description(@data) %></content>
589 -  <published><%= to_rfc3339(@activity.data["published"]) %></published>
590 -  <updated><%= to_rfc3339(@activity.data["published"]) %></updated>
591 +  <published><%= to_rfc3339(@data["published"]) %></published>
592 +  <updated><%= to_rfc3339(@data["published"]) %></updated>
593    <ostatus:conversation ref="<%= activity_context(@activity) %>">
594      <%= activity_context(@activity) %>
595    </ostatus:conversation>
596 diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
597 index d582c83e8c7f497e299899b611a6e522046779f8..1b8c34b87101e664ecdf6c6f6735a53f794ea3db 100644
598 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
599 +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex
600 @@ -4,7 +4,7 @@
601  
602    <guid isPermalink="true"><%= activity_context(@activity) %></guid>
603    <link><%= activity_context(@activity) %></link>
604 -  <pubDate><%= to_rfc2822(@activity.data["published"]) %></pubDate>
605 +  <pubDate><%= to_rfc2822(@data["published"]) %></pubDate>
606  
607    <description><%= activity_description(@data) %></description>
608    <%= for attachment <- @data["attachment"] || [] do %>
609 diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
610 index 3805293bc351f91e80e042bac8c730e6f3a9cbb2..79441761267b8a4aa54a86bb94228440fb73cb15 100644
611 --- a/lib/pleroma/workers/background_worker.ex
612 +++ b/lib/pleroma/workers/background_worker.ex
613 @@ -45,5 +45,5 @@ def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
614    end
615  
616    @impl Oban.Worker
617 -  def timeout(_job), do: :timer.seconds(5)
618 +  def timeout(_job), do: :timer.seconds(900)
619  end
620 diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
621 index 4f513b907481ee86c14ef5fa6552aa32a0eedc31..cf1bb62b44e2d0f26cc66b883d65d3f4bb4c6768 100644
622 --- a/lib/pleroma/workers/receiver_worker.ex
623 +++ b/lib/pleroma/workers/receiver_worker.ex
624 @@ -13,6 +13,9 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
625        {:ok, res}
626      else
627        {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
628 +      {:error, :already_present} -> {:cancel, :already_present}
629 +      {:error, {:validate_object, reason}} -> {:cancel, reason}
630 +      {:error, {:error, {:validate, reason}}} -> {:cancel, reason}
631        {:error, {:reject, reason}} -> {:cancel, reason}
632        e -> e
633      end
634 diff --git a/mix.exs b/mix.exs
635 index ab0be4deb0f6ff76df4a084d9672729c536d07a0..79fd9c9efebee61253cb417aed03fc95b52bf7be 100644
636 --- a/mix.exs
637 +++ b/mix.exs
638 @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
639    def project do
640      [
641        app: :pleroma,
642 -      version: version("2.5.1"),
643 +      version: version("2.5.2"),
644        elixir: "~> 1.11",
645        elixirc_paths: elixirc_paths(Mix.env()),
646        compilers: [:phoenix, :gettext] ++ Mix.compilers(),
647 @@ -150,7 +150,6 @@ defp deps do
648        {:sweet_xml, "~> 0.7.2"},
649        {:earmark, "~> 1.4.22"},
650        {:bbcode_pleroma, "~> 0.2.0"},
651 -      {:crypt, "~> 1.0"},
652        {:cors_plug, "~> 2.0"},
653        {:web_push_encryption, "~> 0.3.1"},
654        {:swoosh, "~> 1.0"},
655 diff --git a/mix.lock b/mix.lock
656 index 3027863262e5fc733b9a11b39d25d918e49515ce..8419dc73972280de7f3f5b5b2b6135ae0f08ace5 100644
657 --- a/mix.lock
658 +++ b/mix.lock
659 @@ -21,7 +21,6 @@
660    "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
661    "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
662    "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
663 -  "crypt": {:hex, :crypt, "1.0.1", "a3567e1c651a2ec42c6650d9f3ab789e0f12a508c060653a9bbb5fafe60f043c", [:rebar3], [], "hexpm", "968dffe321c7a5d9f9b4577c4a4ff56a1c26d1a8a2270eb22c7636a0b43d3982"},
664    "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
665    "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
666    "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
667 diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
668 index c8ad66ddb45eda7658a1d552ab8948cbef004417..53c9277d6778014dbcd8a9f0de26e595375766a9 100644
669 --- a/test/pleroma/object/fetcher_test.exs
670 +++ b/test/pleroma/object/fetcher_test.exs
671 @@ -9,8 +9,12 @@ defmodule Pleroma.Object.FetcherTest do
672    alias Pleroma.Instances
673    alias Pleroma.Object
674    alias Pleroma.Object.Fetcher
675 +  alias Pleroma.Web.ActivityPub.ObjectValidator
676 +
677 +  require Pleroma.Constants
678  
679    import Mock
680 +  import Pleroma.Factory
681    import Tesla.Mock
682  
683    setup do
684 @@ -284,6 +288,8 @@ test "it can refetch pruned objects" do
685  
686    describe "refetching" do
687      setup do
688 +      insert(:user, ap_id: "https://mastodon.social/users/emelie")
689 +
690        object1 = %{
691          "id" => "https://mastodon.social/1",
692          "actor" => "https://mastodon.social/users/emelie",
693 @@ -293,10 +299,14 @@ test "it can refetch pruned objects" do
694          "bcc" => [],
695          "bto" => [],
696          "cc" => [],
697 -        "to" => [],
698 -        "summary" => ""
699 +        "to" => [Pleroma.Constants.as_public()],
700 +        "summary" => "",
701 +        "published" => "2023-05-08 23:43:20Z",
702 +        "updated" => "2023-05-09 23:43:20Z"
703        }
704  
705 +      {:ok, local_object1, _} = ObjectValidator.validate(object1, [])
706 +
707        object2 = %{
708          "id" => "https://mastodon.social/2",
709          "actor" => "https://mastodon.social/users/emelie",
710 @@ -306,8 +316,10 @@ test "it can refetch pruned objects" do
711          "bcc" => [],
712          "bto" => [],
713          "cc" => [],
714 -        "to" => [],
715 +        "to" => [Pleroma.Constants.as_public()],
716          "summary" => "",
717 +        "published" => "2023-05-08 23:43:20Z",
718 +        "updated" => "2023-05-09 23:43:25Z",
719          "formerRepresentations" => %{
720            "type" => "OrderedCollection",
721            "orderedItems" => [
722 @@ -319,14 +331,18 @@ test "it can refetch pruned objects" do
723                "bcc" => [],
724                "bto" => [],
725                "cc" => [],
726 -              "to" => [],
727 -              "summary" => ""
728 +              "to" => [Pleroma.Constants.as_public()],
729 +              "summary" => "",
730 +              "published" => "2023-05-08 23:43:20Z",
731 +              "updated" => "2023-05-09 23:43:21Z"
732              }
733            ],
734            "totalItems" => 1
735          }
736        }
737  
738 +      {:ok, local_object2, _} = ObjectValidator.validate(object2, [])
739 +
740        mock(fn
741          %{
742            method: :get,
743 @@ -335,7 +351,7 @@ test "it can refetch pruned objects" do
744            %Tesla.Env{
745              status: 200,
746              headers: [{"content-type", "application/activity+json"}],
747 -            body: Jason.encode!(object1)
748 +            body: Jason.encode!(object1 |> Map.put("updated", "2023-05-09 23:44:20Z"))
749            }
750  
751          %{
752 @@ -345,7 +361,7 @@ test "it can refetch pruned objects" do
753            %Tesla.Env{
754              status: 200,
755              headers: [{"content-type", "application/activity+json"}],
756 -            body: Jason.encode!(object2)
757 +            body: Jason.encode!(object2 |> Map.put("updated", "2023-05-09 23:44:20Z"))
758            }
759  
760          %{
761 @@ -370,7 +386,7 @@ test "it can refetch pruned objects" do
762            apply(HttpRequestMock, :request, [env])
763        end)
764  
765 -      %{object1: object1, object2: object2}
766 +      %{object1: local_object1, object2: local_object2}
767      end
768  
769      test "it keeps formerRepresentations if remote does not have this attr", %{object1: object1} do
770 @@ -388,8 +404,9 @@ test "it keeps formerRepresentations if remote does not have this attr", %{objec
771                  "bcc" => [],
772                  "bto" => [],
773                  "cc" => [],
774 -                "to" => [],
775 -                "summary" => ""
776 +                "to" => [Pleroma.Constants.as_public()],
777 +                "summary" => "",
778 +                "published" => "2023-05-08 23:43:20Z"
779                }
780              ],
781              "totalItems" => 1
782 @@ -467,6 +484,53 @@ test "it adds to formerRepresentations if the remote does not have one and the o
783                 }
784               } = refetched.data
785      end
786 +
787 +    test "it keeps the history intact if only updated time has changed",
788 +         %{object1: object1} do
789 +      full_object1 =
790 +        object1
791 +        |> Map.merge(%{
792 +          "updated" => "2023-05-08 23:43:47Z",
793 +          "formerRepresentations" => %{
794 +            "type" => "OrderedCollection",
795 +            "orderedItems" => [
796 +              %{"type" => "Note", "content" => "mew mew 1"}
797 +            ],
798 +            "totalItems" => 1
799 +          }
800 +        })
801 +
802 +      {:ok, o} = Object.create(full_object1)
803 +
804 +      assert {:ok, refetched} = Fetcher.refetch_object(o)
805 +
806 +      assert %{
807 +               "content" => "test 1",
808 +               "formerRepresentations" => %{
809 +                 "orderedItems" => [
810 +                   %{"content" => "mew mew 1"}
811 +                 ],
812 +                 "totalItems" => 1
813 +               }
814 +             } = refetched.data
815 +    end
816 +
817 +    test "it goes through ObjectValidator and MRF", %{object2: object2} do
818 +      with_mock Pleroma.Web.ActivityPub.MRF, [:passthrough],
819 +        filter: fn
820 +          %{"type" => "Note"} = object ->
821 +            {:ok, Map.put(object, "content", "MRFd content")}
822 +
823 +          arg ->
824 +            passthrough([arg])
825 +        end do
826 +        {:ok, o} = Object.create(object2)
827 +
828 +        assert {:ok, refetched} = Fetcher.refetch_object(o)
829 +
830 +        assert %{"content" => "MRFd content"} = refetched.data
831 +      end
832 +    end
833    end
834  
835    describe "fetch with history" do
836 diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
837 index 41d1c5d5e842f627668eba496f19b1f50fe9eaa2..1ffe6aae15d129eca24bbdbb1324862ddde0421a 100644
838 --- a/test/pleroma/web/federator_test.exs
839 +++ b/test/pleroma/web/federator_test.exs
840 @@ -133,7 +133,7 @@ test "successfully processes incoming AP docs with correct origin" do
841        assert {:ok, _activity} = ObanHelpers.perform(job)
842  
843        assert {:ok, job} = Federator.incoming_ap_doc(params)
844 -      assert {:error, :already_present} = ObanHelpers.perform(job)
845 +      assert {:cancel, :already_present} = ObanHelpers.perform(job)
846      end
847  
848      test "rejects incoming AP docs with incorrect origin" do
849 diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
850 index de32d3d4bf0398692fd16e4bc15c94cc8ac0a81b..d3c4108de09bc3a1d8a41bb427999026848b6b6e 100644
851 --- a/test/pleroma/web/feed/user_controller_test.exs
852 +++ b/test/pleroma/web/feed/user_controller_test.exs
853 @@ -57,9 +57,23 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
854          )
855  
856        note_activity2 = insert(:note_activity, note: note2)
857 +
858 +      note3 =
859 +        insert(:note,
860 +          user: user,
861 +          data: %{
862 +            "content" => "This note tests whether HTML entities are truncated properly",
863 +            "summary" => "Won't, didn't fail",
864 +            "inReplyTo" => note_activity2.id
865 +          }
866 +        )
867 +
868 +      _note_activity3 = insert(:note_activity, note: note3)
869        object = Object.normalize(note_activity, fetch: false)
870  
871 -      [user: user, object: object, max_id: note_activity2.id]
872 +      encoded_title = FeedView.activity_title(note3.data)
873 +
874 +      [user: user, object: object, max_id: note_activity2.id, encoded_title: encoded_title]
875      end
876  
877      test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do
878 @@ -74,7 +88,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_
879          |> SweetXml.parse()
880          |> SweetXml.xpath(~x"//entry/title/text()"l)
881  
882 -      assert activity_titles == ['2hu', '2hu & as']
883 +      assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
884        assert resp =~ FeedView.escape(object.data["content"])
885        assert resp =~ FeedView.escape(object.data["summary"])
886        assert resp =~ FeedView.escape(object.data["context"])
887 @@ -105,7 +119,7 @@ test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id
888          |> SweetXml.parse()
889          |> SweetXml.xpath(~x"//item/title/text()"l)
890  
891 -      assert activity_titles == ['2hu', '2hu & as']
892 +      assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
893        assert resp =~ FeedView.escape(object.data["content"])
894        assert resp =~ FeedView.escape(object.data["summary"])
895        assert resp =~ FeedView.escape(object.data["context"])
896 @@ -176,6 +190,30 @@ test "does not require authentication on non-federating instances", %{conn: conn
897        |> get("/users/#{user.nickname}/feed.rss")
898        |> response(200)
899      end
900 +
901 +    test "does not mangle HTML entities midway", %{
902 +      conn: conn,
903 +      user: user,
904 +      object: object,
905 +      encoded_title: encoded_title
906 +    } do
907 +      resp =
908 +        conn
909 +        |> put_req_header("accept", "application/atom+xml")
910 +        |> get(user_feed_path(conn, :feed, user.nickname))
911 +        |> response(200)
912 +
913 +      activity_titles =
914 +        resp
915 +        |> SweetXml.parse()
916 +        |> SweetXml.xpath(~x"//entry/title/text()"l)
917 +
918 +      assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
919 +      assert resp =~ FeedView.escape(object.data["content"])
920 +      assert resp =~ FeedView.escape(object.data["summary"])
921 +      assert resp =~ FeedView.escape(object.data["context"])
922 +      assert resp =~ encoded_title
923 +    end
924    end
925  
926    # Note: see ActivityPubControllerTest for JSON format tests
927 diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs
928 index 5246bf0c4b1990e9c8948f1e0bab4b94c64271f7..9ce092fd8fa01c3cb548deffe21cb726ed6c0d77 100644
929 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs
930 +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs
931 @@ -6,7 +6,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
932    use Pleroma.Web.ConnCase
933  
934    import Mock
935 +  import Mox
936  
937 +  alias Pleroma.ReverseProxy.ClientMock
938    alias Pleroma.Web.MediaProxy
939    alias Plug.Conn
940  
941 @@ -74,6 +76,20 @@ test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url}
942          assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
943        end
944      end
945 +
946 +    test "it applies sandbox CSP to MediaProxy requests", %{conn: conn} do
947 +      media_url = "https://lain.com/image.png"
948 +      media_proxy_url = MediaProxy.encode_url(media_url)
949 +
950 +      ClientMock
951 +      |> expect(:request, fn :get, ^media_url, _, _, _ ->
952 +        {:ok, 200, [{"content-type", "image/png"}]}
953 +      end)
954 +
955 +      %Conn{resp_headers: headers} = get(conn, media_proxy_url)
956 +
957 +      assert {"content-security-policy", "sandbox;"} in headers
958 +    end
959    end
960  
961    describe "Media Preview Proxy" do
962 diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs
963 index 85ef6033a7c77b47d7e315ab28272d5efd9f0f5e..3daf852fba8d75c3783ac66b3f2212344b4a40ae 100644
964 --- a/test/pleroma/web/metadata/utils_test.exs
965 +++ b/test/pleroma/web/metadata/utils_test.exs
966 @@ -72,7 +72,7 @@ test "it does not return old content after editing" do
967      end
968    end
969  
970 -  describe "scrub_html_and_truncate/2" do
971 +  describe "scrub_html_and_truncate/3" do
972      test "it returns text without encode HTML" do
973        assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!"
974      end
975 diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs
976 index 41fdb93bc96338125067d525b48c4b9e98ba069e..b8acd01c59232fc1d97f98f4850867da946c1fa4 100644
977 --- a/test/pleroma/web/plugs/authentication_plug_test.exs
978 +++ b/test/pleroma/web/plugs/authentication_plug_test.exs
979 @@ -70,28 +70,6 @@ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
980      assert "$pbkdf2" <> _ = user.password_hash
981    end
982  
983 -  @tag :skip_on_mac
984 -  test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
985 -    user =
986 -      insert(:user,
987 -        password_hash:
988 -          "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
989 -      )
990 -
991 -    conn =
992 -      conn
993 -      |> assign(:auth_user, user)
994 -      |> assign(:auth_credentials, %{password: "password"})
995 -      |> AuthenticationPlug.call(%{})
996 -
997 -    assert conn.assigns.user.id == conn.assigns.auth_user.id
998 -    assert conn.assigns.token == nil
999 -    assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
1000 -
1001 -    user = User.get_by_id(user.id)
1002 -    assert "$pbkdf2" <> _ = user.password_hash
1003 -  end
1004 -
1005    describe "checkpw/2" do
1006      test "check pbkdf2 hash" do
1007        hash =
1008 @@ -101,14 +79,6 @@ test "check pbkdf2 hash" do
1009        refute AuthenticationPlug.checkpw("test-password1", hash)
1010      end
1011  
1012 -    @tag :skip_on_mac
1013 -    test "check sha512-crypt hash" do
1014 -      hash =
1015 -        "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
1016 -
1017 -      assert AuthenticationPlug.checkpw("password", hash)
1018 -    end
1019 -
1020      test "check bcrypt hash" do
1021        hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
1022  
1023 diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
1024 index ffdc4e5d78cf1b00ffbef87b19cde55e2fde5469..9064138a64352f8374eeced43659eabd847b0ab2 100644
1025 --- a/test/pleroma/web/rich_media/parser_test.exs
1026 +++ b/test/pleroma/web/rich_media/parser_test.exs
1027 @@ -129,7 +129,7 @@ test "parses twitter card" do
1028                }}
1029    end
1030  
1031 -  test "parses OEmbed" do
1032 +  test "parses OEmbed and filters HTML tags" do
1033      assert Parser.parse("http://example.com/oembed") ==
1034               {:ok,
1035                %{
1036 @@ -139,7 +139,7 @@ test "parses OEmbed" do
1037                  "flickr_type" => "photo",
1038                  "height" => "768",
1039                  "html" =>
1040 -                  "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by \u202E\u202D\u202Cbees\u202C, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
1041 +                  "<a href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by \u202E\u202D\u202Cbees\u202C, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"/></a>",
1042                  "license" => "All Rights Reserved",
1043                  "license_id" => 0,
1044                  "provider_name" => "Flickr",
1045 diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs
1046 index 8b0c84164dfa20067f9fd96a35225a1dbe93e142..7ab0e379b403b04954c965057a53be8a0f465121 100644
1047 --- a/test/pleroma/web/streamer_test.exs
1048 +++ b/test/pleroma/web/streamer_test.exs
1049 @@ -29,6 +29,26 @@ test "allows public" do
1050        assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
1051      end
1052  
1053 +    test "rejects local public streams if restricted_unauthenticated is on" do
1054 +      clear_config([:restrict_unauthenticated, :timelines, :local], true)
1055 +
1056 +      assert {:error, :unauthorized} = Streamer.get_topic("public:local", nil, nil)
1057 +      assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", nil, nil)
1058 +    end
1059 +
1060 +    test "rejects remote public streams if restricted_unauthenticated is on" do
1061 +      clear_config([:restrict_unauthenticated, :timelines, :federated], true)
1062 +
1063 +      assert {:error, :unauthorized} = Streamer.get_topic("public", nil, nil)
1064 +      assert {:error, :unauthorized} = Streamer.get_topic("public:media", nil, nil)
1065 +
1066 +      assert {:error, :unauthorized} =
1067 +               Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
1068 +
1069 +      assert {:error, :unauthorized} =
1070 +               Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"})
1071 +    end
1072 +
1073      test "allows instance streams" do
1074        assert {:ok, "public:remote:lain.com"} =
1075                 Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
1076 @@ -69,6 +89,63 @@ test "allows public streams (regardless of OAuth token scopes)", %{
1077        end
1078      end
1079  
1080 +    test "allows local public streams if restricted_unauthenticated is on", %{
1081 +      user: user,
1082 +      token: oauth_token
1083 +    } do
1084 +      clear_config([:restrict_unauthenticated, :timelines, :local], true)
1085 +
1086 +      %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user)
1087 +      %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user)
1088 +
1089 +      assert {:ok, "public:local"} = Streamer.get_topic("public:local", user, oauth_token)
1090 +
1091 +      assert {:ok, "public:local:media"} =
1092 +               Streamer.get_topic("public:local:media", user, oauth_token)
1093 +
1094 +      for token <- [read_notifications_token, badly_scoped_token] do
1095 +        assert {:error, :unauthorized} = Streamer.get_topic("public:local", user, token)
1096 +
1097 +        assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", user, token)
1098 +      end
1099 +    end
1100 +
1101 +    test "allows remote public streams if restricted_unauthenticated is on", %{
1102 +      user: user,
1103 +      token: oauth_token
1104 +    } do
1105 +      clear_config([:restrict_unauthenticated, :timelines, :federated], true)
1106 +
1107 +      %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user)
1108 +      %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user)
1109 +
1110 +      assert {:ok, "public"} = Streamer.get_topic("public", user, oauth_token)
1111 +      assert {:ok, "public:media"} = Streamer.get_topic("public:media", user, oauth_token)
1112 +
1113 +      assert {:ok, "public:remote:lain.com"} =
1114 +               Streamer.get_topic("public:remote", user, oauth_token, %{"instance" => "lain.com"})
1115 +
1116 +      assert {:ok, "public:remote:media:lain.com"} =
1117 +               Streamer.get_topic("public:remote:media", user, oauth_token, %{
1118 +                 "instance" => "lain.com"
1119 +               })
1120 +
1121 +      for token <- [read_notifications_token, badly_scoped_token] do
1122 +        assert {:error, :unauthorized} = Streamer.get_topic("public", user, token)
1123 +        assert {:error, :unauthorized} = Streamer.get_topic("public:media", user, token)
1124 +
1125 +        assert {:error, :unauthorized} =
1126 +                 Streamer.get_topic("public:remote", user, token, %{
1127 +                   "instance" => "lain.com"
1128 +                 })
1129 +
1130 +        assert {:error, :unauthorized} =
1131 +                 Streamer.get_topic("public:remote:media", user, token, %{
1132 +                   "instance" => "lain.com"
1133 +                 })
1134 +      end
1135 +    end
1136 +
1137      test "allows user streams (with proper OAuth token scopes)", %{
1138        user: user,
1139        token: read_oauth_token
1140 diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs
1141 index 283beee4d552293f22a01a2bf184e0ad065a43f3..acea0ae0003812e18e5d5b42df72744c58b6f26a 100644
1142 --- a/test/pleroma/workers/receiver_worker_test.exs
1143 +++ b/test/pleroma/workers/receiver_worker_test.exs
1144 @@ -11,7 +11,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
1145  
1146    alias Pleroma.Workers.ReceiverWorker
1147  
1148 -  test "it ignores MRF reject" do
1149 +  test "it does not retry MRF reject" do
1150      params = insert(:note).data
1151  
1152      with_mock Pleroma.Web.ActivityPub.Transmogrifier,
1153 @@ -22,4 +22,31 @@ test "it ignores MRF reject" do
1154                 })
1155      end
1156    end
1157 +
1158 +  test "it does not retry ObjectValidator reject" do
1159 +    params =
1160 +      insert(:note_activity).data
1161 +      |> Map.put("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
1162 +      |> Map.put("object", %{
1163 +        "type" => "Note",
1164 +        "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id()
1165 +      })
1166 +
1167 +    with_mock Pleroma.Web.ActivityPub.ObjectValidator, [:passthrough],
1168 +      validate: fn _, _ -> {:error, %Ecto.Changeset{}} end do
1169 +      assert {:cancel, {:error, %Ecto.Changeset{}}} =
1170 +               ReceiverWorker.perform(%Oban.Job{
1171 +                 args: %{"op" => "incoming_ap_doc", "params" => params}
1172 +               })
1173 +    end
1174 +  end
1175 +
1176 +  test "it does not retry duplicates" do
1177 +    params = insert(:note_activity).data
1178 +
1179 +    assert {:cancel, :already_present} =
1180 +             ReceiverWorker.perform(%Oban.Job{
1181 +               args: %{"op" => "incoming_ap_doc", "params" => params}
1182 +             })
1183 +  end
1184  end