diff --git a/CHANGELOG.md b/CHANGELOG.md index 31156312c..77cd35990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,11 @@ 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/). -## [Unreleased] - +## [unreleased] ### Changed - +- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `
` for newlines - MFR policy to set global expiration for all local Create activities - ## [2.0.0] - 2019-03-08 ### Security - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. diff --git a/config/benchmark.exs b/config/benchmark.exs index 84c6782a2..ff59395cf 100644 --- a/config/benchmark.exs +++ b/config/benchmark.exs @@ -61,8 +61,6 @@ config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock -config :pleroma_job_queue, disabled: true - config :pleroma, Pleroma.ScheduledActivity, daily_user_limit: 2, total_user_limit: 3, diff --git a/config/config.exs b/config/config.exs index 0c8e5e1c5..c976691fb 100644 --- a/config/config.exs +++ b/config/config.exs @@ -506,10 +506,6 @@ federator_outgoing: 5 ] -config :pleroma, :fetch_initial_posts, - enabled: false, - pages: 5 - config :auto_linker, opts: [ extra: true, diff --git a/config/description.exs b/config/description.exs index f113931bd..373995ed6 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1795,25 +1795,6 @@ } ] }, - %{ - group: :pleroma_job_queue, - key: :queues, - type: :group, - description: "[Deprecated] Replaced with `Oban`/`:queues` (keeping the same format)" - }, - %{ - group: :pleroma, - key: Pleroma.Web.Federator.RetryQueue, - type: :group, - description: "[Deprecated] See `Oban` and `:workers` sections for configuration notes", - children: [ - %{ - key: :max_retries, - type: :integer, - description: "[Deprecated] Replaced as `Oban`/`:queues`/`:outgoing_federation` value" - } - ] - }, %{ group: :pleroma, key: Oban, @@ -2022,25 +2003,6 @@ } ] }, - %{ - group: :pleroma, - key: :fetch_initial_posts, - type: :group, - description: "Fetching initial posts settings", - children: [ - %{ - key: :enabled, - type: :boolean, - description: "Fetch posts when a new user is federated with" - }, - %{ - key: :pages, - type: :integer, - description: "The amount of pages to fetch", - suggestions: [5] - } - ] - }, %{ group: :auto_linker, key: :opts, @@ -2611,19 +2573,6 @@ } ] }, - %{ - group: :tesla, - type: :group, - description: "Tesla settings", - children: [ - %{ - key: :adapter, - type: :module, - description: "Tesla adapter", - suggestions: [Tesla.Adapter.Hackney] - } - ] - }, %{ group: :pleroma, key: :chat, diff --git a/config/test.exs b/config/test.exs index a17886265..b8ea63c94 100644 --- a/config/test.exs +++ b/config/test.exs @@ -92,6 +92,8 @@ config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true +config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false + if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" else diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 06de90f71..476a4a2bf 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -180,7 +180,7 @@ Post here request with grant_type=refresh_token to obtain new access token. Retu ## Account Registration `POST /api/v1/accounts` -Has theses additionnal parameters (which are the same as in Pleroma-API): +Has theses additional parameters (which are the same as in Pleroma-API): * `fullname`: optional * `bio`: optional * `captcha_solution`: optional, contains provider-specific captcha solution, diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 761d5c69c..12e63ef9f 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -288,10 +288,11 @@ Pleroma Conversations have the same general structure that Mastodon Conversation 2. Pleroma Conversations statuses can be requested by Conversation id. 3. Pleroma Conversations can be replied to. -Conversations have the additional field "recipients" under the "pleroma" key. This holds a list of all the accounts that will receive a message in this conversation. +Conversations have the additional field `recipients` under the `pleroma` key. This holds a list of all the accounts that will receive a message in this conversation. The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation. +⚠ Conversation IDs can be found in direct messages with the `pleroma.direct_conversation_id` key, do not confuse it with `pleroma.conversation_id`. ## `GET /api/v1/pleroma/conversations/:id/statuses` ### Timeline for a given conversation diff --git a/docs/clients.md b/docs/clients.md index 8ac9ad3de..1eae0f0c6 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -1,5 +1,5 @@ # Pleroma Clients -Note: Additionnal clients may be working but theses are officially supporting Pleroma. +Note: Additional clients may be working but theses are officially supporting Pleroma. Feel free to contact us to be added to this list! ## Desktop diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 3fd372b95..2974312c4 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -156,14 +156,6 @@ config :pleroma, :mrf_user_allowlist, * `sign_object_fetches`: Sign object fetches with HTTP signatures * `authorized_fetch_mode`: Require HTTP signatures for AP fetches -### :fetch_initial_posts - -!!! warning - Be careful with this setting, fetching posts may lead to new users being discovered whose posts will then also be fetched. This can lead to serious load on your instance and database. - -* `enabled`: If enabled, when a new user is discovered by your instance, fetch some of their latest posts. -* `pages`: The amount of pages to fetch - ## Pleroma.ScheduledActivity * `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`) diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index 7f48b614b..688be3e71 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -90,8 +90,6 @@ server { proxy_ignore_client_abort on; proxy_buffering on; chunked_transfer_encoding on; - proxy_ignore_headers Cache-Control; - proxy_hide_header Cache-Control; proxy_pass http://127.0.0.1:4000; } } diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex index 4acc1a3e0..9e65bedad 100644 --- a/lib/pleroma/activity/ir/topics.ex +++ b/lib/pleroma/activity/ir/topics.ex @@ -39,7 +39,7 @@ defp visibility_tags(object, activity) do end end - defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do + defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) end diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex new file mode 100644 index 000000000..6211a3b4a --- /dev/null +++ b/lib/pleroma/earmark_renderer.ex @@ -0,0 +1,256 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only +# +# This file is derived from Earmark, under the following copyright: +# Copyright © 2014 Dave Thomas, The Pragmatic Programmers +# SPDX-License-Identifier: Apache-2.0 +# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex +defmodule Pleroma.EarmarkRenderer do + @moduledoc false + + alias Earmark.Block + alias Earmark.Context + alias Earmark.HtmlRenderer + alias Earmark.Options + + import Earmark.Inline, only: [convert: 3] + import Earmark.Helpers.HtmlHelpers + import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2] + import Earmark.Context, only: [append: 2, set_value: 2] + import Earmark.Options, only: [get_mapper: 1] + + @doc false + def render(blocks, %Context{options: %Options{}} = context) do + messages = get_messages(context) + + {contexts, html} = + get_mapper(context.options).( + blocks, + &render_block(&1, put_in(context.options.messages, [])) + ) + |> Enum.unzip() + + all_messages = + contexts + |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end) + + {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()} + end + + ############# + # Paragraph # + ############# + defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do + lines = convert(lines, lnb, context) + add_attrs(lines, "

#{lines.value}

", attrs, [], lnb) + end + + ######## + # Html # + ######## + defp render_block(%Block.Html{html: html}, context) do + {context, html} + end + + defp render_block(%Block.HtmlComment{lines: lines}, context) do + {context, lines} + end + + defp render_block(%Block.HtmlOneline{html: html}, context) do + {context, html} + end + + ######### + # Ruler # + ######### + defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do + add_attrs(context, "
", attrs, [], lnb) + end + + ########### + # Heading # + ########### + defp render_block( + %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs}, + context + ) do + converted = convert(content, lnb, context) + html = "#{converted.value}" + add_attrs(converted, html, attrs, [], lnb) + end + + ############## + # Blockquote # + ############## + + defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do + {context1, body} = render(blocks, context) + html = "
#{body}
" + add_attrs(context1, html, attrs, [], lnb) + end + + ######### + # Table # + ######### + + defp render_block( + %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs}, + context + ) do + {context1, html} = add_attrs(context, "", attrs, [], lnb) + context2 = set_value(context1, html) + + context3 = + if header do + append(add_trs(append(context2, ""), [header], "th", aligns, lnb), "") + else + # Maybe an error, needed append(context, html) + context2 + end + + context4 = append(add_trs(append(context3, ""), rows, "td", aligns, lnb), "") + + {context4, [context4.value, "
"]} + end + + ######## + # Code # + ######## + + defp render_block( + %Block.Code{lnb: lnb, language: language, attrs: attrs} = block, + %Context{options: options} = context + ) do + class = + if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: "" + + tag = ~s[
]
+    lines = options.render_code.(block)
+    html = ~s[#{tag}#{lines}
] + add_attrs(context, html, attrs, [], lnb) + end + + ######### + # Lists # + ######### + + defp render_block( + %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start}, + context + ) do + {context1, content} = render(items, context) + html = "<#{type}#{start}>#{content}" + add_attrs(context1, html, attrs, [], lnb) + end + + # format a single paragraph list item, and remove the para tags + defp render_block( + %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs}, + context + ) + when length(blocks) == 1 do + {context1, content} = render(blocks, context) + content = Regex.replace(~r{}, content, "") + html = "
  • #{content}
  • " + add_attrs(context1, html, attrs, [], lnb) + end + + # format a spaced list item + defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do + {context1, content} = render(blocks, context) + html = "
  • #{content}
  • " + add_attrs(context1, html, attrs, [], lnb) + end + + ################## + # Footnote Block # + ################## + + defp render_block(%Block.FnList{blocks: footnotes}, context) do + items = + Enum.map(footnotes, fn note -> + blocks = append_footnote_link(note) + %Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks} + end) + + {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context) + {context1, Enum.join([~s[
    ], "
    ", html, "
    "])} + end + + ####################################### + # Isolated IALs are rendered as paras # + ####################################### + + defp render_block(%Block.Ial{verbatim: verbatim}, context) do + {context, "

    {:#{verbatim}}

    "} + end + + #################### + # IDDef is ignored # + #################### + + defp render_block(%Block.IdDef{}, context), do: {context, ""} + + ##################################### + # And here are the inline renderers # + ##################################### + + defdelegate br, to: HtmlRenderer + defdelegate codespan(text), to: HtmlRenderer + defdelegate em(text), to: HtmlRenderer + defdelegate strong(text), to: HtmlRenderer + defdelegate strikethrough(text), to: HtmlRenderer + + defdelegate link(url, text), to: HtmlRenderer + defdelegate link(url, text, title), to: HtmlRenderer + + defdelegate image(path, alt, title), to: HtmlRenderer + + defdelegate footnote_link(ref, backref, number), to: HtmlRenderer + + # Table rows + defp add_trs(context, rows, tag, aligns, lnb) do + numbered_rows = + rows + |> Enum.zip(Stream.iterate(lnb, &(&1 + 1))) + + numbered_rows + |> Enum.reduce(context, fn {row, lnb}, ctx -> + append(add_tds(append(ctx, ""), row, tag, aligns, lnb), "") + end) + end + + defp add_tds(context, row, tag, aligns, lnb) do + Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb)) + end + + defp add_td_fn(row, tag, aligns, lnb) do + fn n, ctx -> + style = + case Enum.at(aligns, n - 1, :default) do + :default -> "" + align -> " style=\"text-align: #{align}\"" + end + + col = Enum.at(row, n - 1) + converted = convert(col, lnb, set_messages(ctx, [])) + append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}") + end + end + + ############################### + # Append Footnote Return Link # + ############################### + + defdelegate append_footnote_link(note), to: HtmlRenderer + defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer + + defdelegate render_code(lines), to: HtmlRenderer + + defp code_classes(language, prefix) do + ["" | String.split(prefix || "")] + |> Enum.map(fn pfx -> "#{pfx}#{language}" end) + |> Enum.join(" ") + end +end diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 6f9b840a9..054d2297f 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -15,9 +15,24 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do conn end - def call(conn, _) do + def call(conn, options) do + perform = + cond do + options[:if_func] -> options[:if_func].() + options[:unless_func] -> !options[:unless_func].() + true -> true + end + + if perform do + fail(conn) + else + conn + end + end + + def fail(conn) do conn |> render_error(:forbidden, "Invalid credentials.") - |> halt + |> halt() end end diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index d3943586d..7d947339f 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -10,14 +10,20 @@ def init(options) do end def call(conn, _opts) do - if Pleroma.Config.get([:instance, :federating]) do + if federating?() do conn else - conn - |> put_status(404) - |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView) - |> Phoenix.Controller.render("404.json") - |> halt() + fail(conn) end end + + def federating?, do: Pleroma.Config.get([:instance, :federating]) + + defp fail(conn) do + conn + |> put_status(404) + |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView) + |> Phoenix.Controller.render("404.json") + |> halt() + end end diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index c3f6351c8..1529da717 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -78,7 +78,7 @@ def init(plug_opts) do end def call(conn, plug_opts) do - if disabled?() do + if disabled?(conn) do handle_disabled(conn) else action_settings = action_settings(plug_opts) @@ -87,9 +87,9 @@ def call(conn, plug_opts) do end defp handle_disabled(conn) do - if Config.get(:env) == :prod do - Logger.warn("Rate limiter is disabled for localhost/socket") - end + Logger.warn( + "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter." + ) conn end @@ -109,16 +109,21 @@ defp handle(conn, action_settings) do end end - def disabled? do + def disabled?(conn) do localhost_or_socket = - Config.get([Pleroma.Web.Endpoint, :http, :ip]) - |> Tuple.to_list() - |> Enum.join(".") - |> String.match?(~r/^local|^127.0.0.1/) + case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do + {127, 0, 0, 1} -> true + {0, 0, 0, 0, 0, 0, 0, 1} -> true + {:local, _} -> true + _ -> false + end - remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled]) + remote_ip_not_found = + if Map.has_key?(conn.assigns, :remote_ip_found), + do: !conn.assigns.remote_ip_found, + else: false - localhost_or_socket and remote_ip_disabled + localhost_or_socket and remote_ip_not_found end @inspect_bucket_not_found {:error, :not_found} diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 2eca4f8f6..0ac9050d0 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -7,6 +7,8 @@ defmodule Pleroma.Plugs.RemoteIp do This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. """ + import Plug.Conn + @behaviour Plug @headers ~w[ @@ -26,11 +28,12 @@ defmodule Pleroma.Plugs.RemoteIp do def init(_), do: nil - def call(conn, _) do + def call(%{remote_ip: original_remote_ip} = conn, _) do config = Pleroma.Config.get(__MODULE__, []) if Keyword.get(config, :enabled, false) do - RemoteIp.call(conn, remote_ip_opts(config)) + %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) + assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) else conn end diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/plugs/static_fe_plug.ex index deebe4879..156e6788e 100644 --- a/lib/pleroma/plugs/static_fe_plug.ex +++ b/lib/pleroma/plugs/static_fe_plug.ex @@ -21,6 +21,9 @@ def call(conn, _) do defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false) defp accepts_html?(conn) do - conn |> get_req_header("accept") |> List.first() |> String.contains?("text/html") + case get_req_header(conn, "accept") do + [accept | _] -> String.contains?(accept, "text/html") + _ -> false + end end end diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex index f372829a2..36ff024a7 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/plugs/uploaded_media.ex @@ -14,9 +14,14 @@ defmodule Pleroma.Plugs.UploadedMedia do # no slashes @path "media" + @default_cache_control_header "public, max-age=1209600" + def init(_opts) do static_plug_opts = - [] + [ + headers: %{"cache-control" => @default_cache_control_header}, + cache_control_for_etags: @default_cache_control_header + ] |> Keyword.put(:from, "__unconfigured_media_plug") |> Keyword.put(:at, "/__unconfigured_media_plug") |> Plug.Static.init() diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index a281a00dc..8b713b8f4 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -7,7 +7,7 @@ defmodule Pleroma.ReverseProxy do @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ ~w(if-unmodified-since if-none-match if-range range) - @resp_cache_headers ~w(etag date last-modified cache-control) + @resp_cache_headers ~w(etag date last-modified) @keep_resp_headers @resp_cache_headers ++ ~w(content-type content-disposition content-encoding content-range) ++ ~w(accept-ranges vary) @@ -34,9 +34,6 @@ defmodule Pleroma.ReverseProxy do * request: `#{inspect(@keep_req_headers)}` * response: `#{inspect(@keep_resp_headers)}` - If no caching headers (`#{inspect(@resp_cache_headers)}`) are returned by upstream, `cache-control` will be - set to `#{inspect(@default_cache_control_header)}`. - Options: * `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP @@ -297,16 +294,17 @@ defp build_resp_headers(headers, opts) do defp build_resp_cache_headers(headers, _opts) do has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end) - has_cache_control? = List.keymember?(headers, "cache-control", 0) cond do - has_cache? && has_cache_control? -> - headers - has_cache? -> - # There's caching header present but no cache-control -- we need to explicitely override it - # to public as Plug defaults to "max-age=0, private, must-revalidate" - List.keystore(headers, "cache-control", 0, {"cache-control", "public"}) + # There's caching header present but no cache-control -- we need to set our own + # as Plug defaults to "max-age=0, private, must-revalidate" + List.keystore( + headers, + "cache-control", + 0, + {"cache-control", @default_cache_control_header} + ) true -> List.keystore( diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7531757f5..911dde6e2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -16,6 +16,7 @@ defmodule Pleroma.User do alias Pleroma.Conversation.Participation alias Pleroma.Delivery alias Pleroma.FollowingRelationship + alias Pleroma.HTML alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object @@ -839,10 +840,6 @@ def get_or_fetch_by_nickname(nickname) do _e -> with [_nick, _domain] <- String.split(nickname, "@"), {:ok, user} <- fetch_by_nickname(nickname) do - if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do - fetch_initial_posts(user) - end - {:ok, user} else _e -> {:error, "not found " <> nickname} @@ -850,11 +847,6 @@ def get_or_fetch_by_nickname(nickname) do end end - @doc "Fetch some posts when the user has just been federated with" - def fetch_initial_posts(user) do - BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id}) - end - @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() def get_followers_query(%User{} = user, nil) do User.Query.build(%{followers: user, deactivated: false}) @@ -1320,16 +1312,6 @@ def perform(:delete, %User{} = user) do Repo.delete(user) end - def perform(:fetch_initial_posts, %User{} = user) do - pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) - - # Insert all the posts in reverse order, so they're in the right order on the timeline - user.source_data["outbox"] - |> Utils.fetch_ordered_collection(pages) - |> Enum.reverse() - |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1) - end - def perform(:deactivate_async, user, status), do: deactivate(user, status) @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} @@ -1458,18 +1440,7 @@ def get_or_fetch_by_ap_id(ap_id) do if !is_nil(user) and !needs_update?(user) do {:ok, user} else - # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled) - should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled]) - - resp = fetch_by_ap_id(ap_id) - - if should_fetch_initial do - with {:ok, %User{} = user} <- resp do - fetch_initial_posts(user) - end - end - - resp + fetch_by_ap_id(ap_id) end end @@ -2062,4 +2033,27 @@ def set_invisible(user, invisible) do |> validate_required([:invisible]) |> update_and_set_cache() end + + def sanitize_html(%User{} = user) do + sanitize_html(user, nil) + end + + # User data that mastodon isn't filtering (treated as plaintext): + # - field name + # - display name + def sanitize_html(%User{} = user, filter) do + fields = + user + |> User.fields() + |> Enum.map(fn %{"name" => name, "value" => value} -> + %{ + "name" => name, + "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + } + end) + + user + |> Map.put(:bio, HTML.filter_tags(user.bio, filter)) + |> Map.put(:fields, fields) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 779de0e4d..8b9eb4a2c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Delivery alias Pleroma.Object alias Pleroma.Object.Fetcher + alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.InternalFetchActor @@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.FederatingPlug alias Pleroma.Web.Federator require Logger action_fallback(:errors) + @federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers] + + plug(FederatingPlug when action in @federating_only_actions) + + plug( + EnsureAuthenticatedPlug, + [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions + ) + + plug( + EnsureAuthenticatedPlug + when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers] + ) + plug( Pleroma.Plugs.Cache, [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] when action in [:activity, :object] ) - plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay]) plug(:set_requester_reachable when action in [:inbox]) plug(:relay_active? when action in [:relay]) - def relay_active?(conn, _) do + defp relay_active?(conn, _) do if Pleroma.Config.get([:instance, :allow_relay]) do conn else @@ -127,11 +142,13 @@ defp set_cache_ttl_for(conn, entity) do end # GET /relay/following - def following(%{assigns: %{relay: true}} = conn, _params) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("following.json", %{user: Relay.get_actor()}) + def relay_following(conn, _params) do + with %{halted: false} = conn <- FederatingPlug.call(conn, []) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("following.json", %{user: Relay.get_actor()}) + end end def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -164,11 +181,13 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d end # GET /relay/followers - def followers(%{assigns: %{relay: true}} = conn, _params) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("followers.json", %{user: Relay.get_actor()}) + def relay_followers(conn, _params) do + with %{halted: false} = conn <- FederatingPlug.call(conn, []) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("followers.json", %{user: Relay.get_actor()}) + end end def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -200,13 +219,16 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d end end - def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) + def outbox( + %{assigns: %{user: for_user}} = conn, + %{"nickname" => nickname, "page" => page?} = params + ) when page? in [true, "true"] do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- User.ensure_keys_present(user) do activities = if params["max_id"] do - ActivityPub.fetch_user_activities(user, nil, %{ + ActivityPub.fetch_user_activities(user, for_user, %{ "max_id" => params["max_id"], # This is a hack because postgres generates inefficient queries when filtering by # 'Answer', poll votes will be hidden by the visibility filter in this case anyway @@ -214,7 +236,7 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) "limit" => 10 }) else - ActivityPub.fetch_user_activities(user, nil, %{ + ActivityPub.fetch_user_activities(user, for_user, %{ "limit" => 10, "include_poll_votes" => true }) @@ -255,8 +277,16 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do json(conn, "ok") end - # only accept relayed Creates - def inbox(conn, %{"type" => "Create"} = params) do + # POST /relay/inbox -or- POST /internal/fetch/inbox + def inbox(conn, params) do + if params["type"] == "Create" && FederatingPlug.federating?() do + post_inbox_relayed_create(conn, params) + else + post_inbox_fallback(conn, params) + end + end + + defp post_inbox_relayed_create(conn, params) do Logger.debug( "Signature missing or not from author, relayed Create message, fetching object from source" ) @@ -266,10 +296,11 @@ def inbox(conn, %{"type" => "Create"} = params) do json(conn, "ok") end - def inbox(conn, params) do + defp post_inbox_fallback(conn, params) do headers = Enum.into(conn.req_headers, %{}) - if String.contains?(headers["signature"], params["actor"]) do + if headers["signature"] && params["actor"] && + String.contains?(headers["signature"], params["actor"]) do Logger.debug( "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" ) @@ -277,7 +308,9 @@ def inbox(conn, params) do Logger.debug(inspect(conn.req_headers)) end - json(conn, dgettext("errors", "error")) + conn + |> put_status(:bad_request) + |> json(dgettext("errors", "error")) end defp represent_service_actor(%User{} = user, conn) do @@ -311,10 +344,8 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do |> render("user.json", %{user: user}) end - def whoami(_conn, _params), do: {:error, :not_found} - def read_inbox( - %{assigns: %{user: %{nickname: nickname} = user}} = conn, + %{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do @@ -337,7 +368,7 @@ def read_inbox( }) end - def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ + def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{ "nickname" => nickname }) do with {:ok, user} <- User.ensure_keys_present(user) do @@ -348,15 +379,7 @@ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ end end - def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do - err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname) - - conn - |> put_status(:forbidden) - |> json(err) - end - - def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ + def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ "nickname" => nickname }) do err = @@ -370,7 +393,7 @@ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ |> json(err) end - def handle_user_activity(user, %{"type" => "Create"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do object = params["object"] |> Map.merge(Map.take(params, ["to", "cc"])) @@ -386,7 +409,7 @@ def handle_user_activity(user, %{"type" => "Create"} = params) do }) end - def handle_user_activity(user, %{"type" => "Delete"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do with %Object{} = object <- Object.normalize(params["object"]), true <- user.is_moderator || user.ap_id == object.data["actor"], {:ok, delete} <- ActivityPub.delete(object) do @@ -396,7 +419,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do end end - def handle_user_activity(user, %{"type" => "Like"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do with %Object{} = object <- Object.normalize(params["object"]), {:ok, activity, _object} <- ActivityPub.like(user, object) do {:ok, activity} @@ -405,7 +428,7 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do end end - def handle_user_activity(_, _) do + defp handle_user_activity(_, _) do {:error, dgettext("errors", "Unhandled activity type")} end @@ -434,7 +457,7 @@ def update_outbox( end end - def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do + def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do err = dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}", nickname: nickname, @@ -446,13 +469,13 @@ def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = |> json(err) end - def errors(conn, {:error, :not_found}) do + defp errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> json(dgettext("errors", "Not found")) end - def errors(conn, _e) do + defp errors(conn, _e) do conn |> put_status(:internal_server_error) |> json(dgettext("errors", "error")) @@ -492,7 +515,7 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do - HTTP Code: 201 Created - HTTP Body: ActivityPub object to be inserted into another's `attachment` field """ - def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do + def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do with {:ok, object} <- ActivityPub.upload( file, diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2bc958670..15dd2ed45 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -784,45 +784,6 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do defp build_flag_object(_), do: [] - @doc """ - Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after - the first one to `pages_left` pages. - If the amount of pages is higher than the collection has, it returns whatever was there. - """ - def fetch_ordered_collection(from, pages_left, acc \\ []) do - with {:ok, response} <- Tesla.get(from), - {:ok, collection} <- Jason.decode(response.body) do - case collection["type"] do - "OrderedCollection" -> - # If we've encountered the OrderedCollection and not the page, - # just call the same function on the page address - fetch_ordered_collection(collection["first"], pages_left) - - "OrderedCollectionPage" -> - if pages_left > 0 do - # There are still more pages - if Map.has_key?(collection, "next") do - # There are still more pages, go deeper saving what we have into the accumulator - fetch_ordered_collection( - collection["next"], - pages_left - 1, - acc ++ collection["orderedItems"] - ) - else - # No more pages left, just return whatever we already have - acc ++ collection["orderedItems"] - end - else - # Got the amount of pages needed, add them all to the accumulator - acc ++ collection["orderedItems"] - end - - _ -> - {:error, "Not an OrderedCollection or OrderedCollectionPage"} - end - end - end - #### Report-related helpers def get_reports(params, page, page_size) do params = diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index c0358b678..bc21ac6c7 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -73,6 +73,7 @@ def render("user.json", %{user: user}) do {:ok, _, public_key} = Keys.keys_from_pem(user.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) + user = User.sanitize_html(user) endpoints = render("endpoints.json", %{user: user}) @@ -81,12 +82,6 @@ def render("user.json", %{user: user}) do fields = user |> User.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => Pleroma.HTML.strip_tags(name), - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) |> Enum.map(&Map.put(&1, "type", "PropertyValue")) %{ diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 47b7d2da3..175260bc2 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -745,14 +745,14 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{ end end - def list_statuses(%{assigns: %{user: admin}} = conn, params) do + def list_statuses(%{assigns: %{user: _admin}} = conn, params) do godmode = params["godmode"] == "true" || params["godmode"] == true local_only = params["local_only"] == "true" || params["local_only"] == true with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true {page, page_size} = page_params(params) activities = - ActivityPub.fetch_statuses(admin, %{ + ActivityPub.fetch_statuses(nil, %{ "godmode" => godmode, "local_only" => local_only, "limit" => page_size, diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 619390ef4..1e03849de 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do use Pleroma.Web, :view - alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.MediaProxy @@ -26,7 +25,8 @@ def render("index.json", %{users: users}) do def render("show.json", %{user: user}) do avatar = User.avatar_url(user) |> MediaProxy.url() - display_name = HTML.strip_tags(user.name || user.nickname) + display_name = Pleroma.HTML.strip_tags(user.name || user.nickname) + user = User.sanitize_html(user, FastSanitize.Sanitizer.StripTags) %{ "id" => user.id, diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 348fdedf1..635e7cd38 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -331,7 +331,7 @@ def format_input(text, "text/html", options) do def format_input(text, "text/markdown", options) do text |> Formatter.mentions_escape(options) - |> Earmark.as_html!() + |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer}) |> Formatter.linkify(options) |> Formatter.html_escape("text/html") end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 118c3ac6f..72cb3ee27 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.HTTPSecurityPlug) plug(Pleroma.Plugs.UploadedMedia) - @static_cache_control "public max-age=86400 must-revalidate" + @static_cache_control "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 59aabb549..9ba602d9f 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -25,7 +25,12 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname def feed_redirect(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do - ActivityPubController.call(conn, :user) + with %{halted: false} = conn <- + Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) do + ActivityPubController.call(conn, :user) + end end def feed_redirect(conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index f165c9965..37b389382 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -86,6 +86,6 @@ defp local_mastodon_root_path(conn) do @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} defp get_or_make_app do %{client_name: @local_mastodon_name, redirect_uris: "."} - |> App.get_or_make(["read", "write", "follow", "push"]) + |> App.get_or_make(["read", "write", "follow", "push", "admin"]) end end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 6dc191250..341dc2c91 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do use Pleroma.Web, :view - alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView @@ -67,6 +66,7 @@ def render("relationships.json", %{user: user, targets: targets}) do end defp do_render("show.json", %{user: user} = opts) do + user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname image = User.avatar_url(user) |> MediaProxy.url() @@ -100,17 +100,6 @@ defp do_render("show.json", %{user: user} = opts) do } end) - fields = - user - |> User.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => name, - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) - - bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) relationship = render("relationship.json", %{user: opts[:for], target: user}) %{ @@ -123,17 +112,17 @@ defp do_render("show.json", %{user: user} = opts) do followers_count: followers_count, following_count: following_count, statuses_count: user.note_count, - note: bio || "", + note: user.bio || "", url: User.profile_url(user), avatar: image, avatar_static: image, header: header, header_static: header, emojis: emojis, - fields: fields, + fields: user.fields, bot: bot, source: %{ - note: HTML.strip_tags((user.bio || "") |> String.replace("
    ", "\n")), + note: Pleroma.HTML.strip_tags((user.bio || "") |> String.replace("
    ", "\n")), sensitive: false, fields: user.raw_fields, pleroma: %{ diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index c443c888c..6fd3cfce5 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Router + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) + plug( RateLimiter, [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] @@ -135,13 +139,13 @@ def notice_player(conn, %{"id" => id}) do end end - def errors(conn, {:error, :not_found}) do + defp errors(conn, {:error, :not_found}) do render_error(conn, :not_found, "Not found") end - def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) + defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) - def errors(conn, _) do + defp errors(conn, _) do render_error(conn, :internal_server_error, "Something went wrong") end end diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 0e160bbfc..dae7f0f2f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -101,6 +101,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) conn |> put_view(ConversationView) |> render("participation.json", %{participation: participation, for: user}) + else + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end @@ -108,9 +113,9 @@ def conversation_statuses( %{assigns: %{user: user}} = conn, %{"id" => participation_id} = params ) do - participation = Participation.get(participation_id, preload: [:conversation]) - - if user.id == participation.user_id do + with %Participation{} = participation <- + Participation.get(participation_id, preload: [:conversation]), + true <- user.id == participation.user_id do params = params |> Map.put("blocking_user", user) @@ -126,6 +131,11 @@ def conversation_statuses( |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) + else + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end @@ -133,15 +143,22 @@ def update_conversation( %{assigns: %{user: user}} = conn, %{"id" => participation_id, "recipients" => recipients} ) do - participation = - participation_id - |> Participation.get() - - with true <- user.id == participation.user_id, + with %Participation{} = participation <- Participation.get(participation_id), + true <- user.id == participation.user_id, {:ok, participation} <- Participation.set_recipients(participation, recipients) do conn |> put_view(ConversationView) |> render("participation.json", %{participation: participation, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 980242c68..e4e3ee704 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -541,6 +541,7 @@ defmodule Pleroma.Web.Router do get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe) end + # Server to Server (S2S) AP interactions pipeline :activitypub do plug(:accepts, ["activity+json", "json"]) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) @@ -554,6 +555,7 @@ defmodule Pleroma.Web.Router do get("/users/:nickname/outbox", ActivityPubController, :outbox) end + # Client to Server (C2S) AP interactions pipeline :activitypub_client do plug(:accepts, ["activity+json", "json"]) plug(:fetch_session) @@ -597,8 +599,8 @@ defmodule Pleroma.Web.Router do post("/inbox", ActivityPubController, :inbox) end - get("/following", ActivityPubController, :following, assigns: %{relay: true}) - get("/followers", ActivityPubController, :followers, assigns: %{relay: true}) + get("/following", ActivityPubController, :relay_following) + get("/followers", ActivityPubController, :relay_followers) end scope "/internal/fetch", Pleroma.Web.ActivityPub do diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 5ac75f1c4..7f9464268 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) plug(:assign_id) + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) + @page_keys ["max_id", "min_id", "limit", "since_id", "order"] defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), @@ -33,7 +37,7 @@ defp not_found(conn, message) do |> render("error.html", %{message: message, meta: ""}) end - def get_counts(%Activity{} = activity) do + defp get_counts(%Activity{} = activity) do %Object{data: data} = Object.normalize(activity) %{ @@ -43,9 +47,9 @@ def get_counts(%Activity{} = activity) do } end - def represent(%Activity{} = activity), do: represent(activity, false) + defp represent(%Activity{} = activity), do: represent(activity, false) - def represent(%Activity{object: %Object{data: data}} = activity, selected) do + defp represent(%Activity{object: %Object{data: data}} = activity, selected) do {:ok, user} = User.get_or_fetch(activity.object.data["actor"]) link = @@ -54,10 +58,17 @@ def represent(%Activity{object: %Object{data: data}} = activity, selected) do _ -> data["url"] || data["external_url"] || data["id"] end + content = + if data["content"] do + Pleroma.HTML.filter_tags(data["content"]) + else + nil + end + %{ - user: user, + user: User.sanitize_html(user), title: get_title(activity.object), - content: data["content"] || nil, + content: content, attachment: data["attachment"], link: link, published: data["published"], @@ -109,7 +120,7 @@ def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do next_page_id = List.last(timeline) && List.last(timeline).id render(conn, "profile.html", %{ - user: user, + user: User.sanitize_html(user), timeline: timeline, prev_page_id: prev_page_id, next_page_id: next_page_id, @@ -147,17 +158,17 @@ def show(%{assigns: %{activity_id: _}} = conn, _params) do end end - def assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), + defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), do: assign(conn, :notice_id, notice_id) - def assign_id(%{path_info: ["users", user_id]} = conn, _opts), + defp assign_id(%{path_info: ["users", user_id]} = conn, _opts), do: assign(conn, :username_or_id, user_id) - def assign_id(%{path_info: ["objects", object_id]} = conn, _opts), + defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts), do: assign(conn, :object_id, object_id) - def assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), + defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), do: assign(conn, :activity_id, activity_id) - def assign_id(conn, _opts), do: conn + defp assign_id(conn, _opts), do: conn end diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index fbf31c7eb..89da760da 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -16,6 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] + plug(Pleroma.Web.FederatingPlug) + # Note: follower can submit the form (with password auth) not being signed in (having no token) plug( OAuthScopesPlug, diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index bca0e26eb..537f9f778 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.WebFinger + plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) + plug( OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 598df6580..0f8ece2c4 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -10,10 +10,6 @@ defmodule Pleroma.Workers.BackgroundWorker do use Pleroma.Workers.WorkerHelper, queue: "background" @impl Oban.Worker - def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do - user = User.get_cached_by_id(user_id) - User.perform(:fetch_initial_posts, user) - end def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do user = User.get_cached_by_id(user_id) diff --git a/mix.exs b/mix.exs index bb86c38d0..dd598345c 100644 --- a/mix.exs +++ b/mix.exs @@ -126,7 +126,7 @@ defp deps do {:ex_aws_s3, "~> 2.0"}, {:sweet_xml, "~> 0.6.6"}, {:earmark, "~> 1.3"}, - {:bbcode, "~> 0.1.1"}, + {:bbcode_pleroma, "~> 0.2.0"}, {:ex_machina, "~> 2.3", only: :test}, {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.3", only: :test}, diff --git a/mix.lock b/mix.lock index c8b30a6f9..62e14924a 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,8 @@ "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, + "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, + "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"}, @@ -110,4 +111,3 @@ "web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"}, "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, } - diff --git a/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs b/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs new file mode 100644 index 000000000..392f531e8 --- /dev/null +++ b/priv/repo/migrations/20200314123607_config_remove_fetch_initial_posts.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.ConfigRemoveFetchInitialPosts do + use Ecto.Migration + + def change do + execute( + "delete from config where config.key = ':fetch_initial_posts' and config.group = ':pleroma';", + "" + ) + end +end diff --git a/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs b/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs new file mode 100644 index 000000000..5b8e3ab91 --- /dev/null +++ b/priv/repo/migrations/20200315125756_delete_fetch_initial_posts_jobs.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.DeleteFetchInitialPostsJobs do + use Ecto.Migration + + def change do + execute( + "delete from oban_jobs where worker = 'Pleroma.Workers.BackgroundWorker' and args->>'op' = 'fetch_initial_posts';", + "" + ) + end +end diff --git a/priv/static/adminfe/chunk-17a5.edcdbe30.css b/priv/static/adminfe/chunk-0d8f.650c8e81.css similarity index 100% rename from priv/static/adminfe/chunk-17a5.edcdbe30.css rename to priv/static/adminfe/chunk-0d8f.650c8e81.css diff --git a/priv/static/adminfe/chunk-2b8b.0f1ee211.css b/priv/static/adminfe/chunk-136a.3936457d.css similarity index 100% rename from priv/static/adminfe/chunk-2b8b.0f1ee211.css rename to priv/static/adminfe/chunk-136a.3936457d.css diff --git a/priv/static/adminfe/chunk-15fa.dc3643e6.css b/priv/static/adminfe/chunk-15fa.5a5f973d.css similarity index 100% rename from priv/static/adminfe/chunk-15fa.dc3643e6.css rename to priv/static/adminfe/chunk-15fa.5a5f973d.css diff --git a/priv/static/adminfe/chunk-46cf.6dd5bbb7.css b/priv/static/adminfe/chunk-46cf.a43e9415.css similarity index 100% rename from priv/static/adminfe/chunk-46cf.6dd5bbb7.css rename to priv/static/adminfe/chunk-46cf.a43e9415.css diff --git a/priv/static/adminfe/chunk-453a.bbab87da.css b/priv/static/adminfe/chunk-46ef.d45db7be.css similarity index 100% rename from priv/static/adminfe/chunk-453a.bbab87da.css rename to priv/static/adminfe/chunk-46ef.d45db7be.css diff --git a/priv/static/adminfe/chunk-293a.a8b5ee5b.css b/priv/static/adminfe/chunk-4e7d.7aace723.css similarity index 57% rename from priv/static/adminfe/chunk-293a.a8b5ee5b.css rename to priv/static/adminfe/chunk-4e7d.7aace723.css index 924633a80..9a35b64a0 100644 Binary files a/priv/static/adminfe/chunk-293a.a8b5ee5b.css and b/priv/static/adminfe/chunk-4e7d.7aace723.css differ diff --git a/priv/static/adminfe/chunk-4e46.ad5e9ff3.css b/priv/static/adminfe/chunk-4ffb.dd09fe2e.css similarity index 100% rename from priv/static/adminfe/chunk-4e46.ad5e9ff3.css rename to priv/static/adminfe/chunk-4ffb.dd09fe2e.css diff --git a/priv/static/adminfe/chunk-6dd6.85f319f7.css b/priv/static/adminfe/chunk-876c.90dffac4.css similarity index 100% rename from priv/static/adminfe/chunk-6dd6.85f319f7.css rename to priv/static/adminfe/chunk-876c.90dffac4.css diff --git a/priv/static/adminfe/chunk-03b0.49362218.css b/priv/static/adminfe/chunk-87b3.2affd602.css similarity index 57% rename from priv/static/adminfe/chunk-03b0.49362218.css rename to priv/static/adminfe/chunk-87b3.2affd602.css index e43c776aa..c4fa46d3e 100644 Binary files a/priv/static/adminfe/chunk-03b0.49362218.css and b/priv/static/adminfe/chunk-87b3.2affd602.css differ diff --git a/priv/static/adminfe/chunk-cf58.80435fa1.css b/priv/static/adminfe/chunk-cf57.4d39576f.css similarity index 75% rename from priv/static/adminfe/chunk-cf58.80435fa1.css rename to priv/static/adminfe/chunk-cf57.4d39576f.css index 8b0f21153..1190aca24 100644 Binary files a/priv/static/adminfe/chunk-cf58.80435fa1.css and b/priv/static/adminfe/chunk-cf57.4d39576f.css differ diff --git a/priv/static/adminfe/chunk-560d.802cfba1.css b/priv/static/adminfe/chunk-e5cf.cba3ae06.css similarity index 100% rename from priv/static/adminfe/chunk-560d.802cfba1.css rename to priv/static/adminfe/chunk-e5cf.cba3ae06.css diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html index e2db408c3..717b0f32d 100644 --- a/priv/static/adminfe/index.html +++ b/priv/static/adminfe/index.html @@ -1 +1 @@ -Admin FE
    \ No newline at end of file +Admin FE
    \ No newline at end of file diff --git a/priv/static/adminfe/static/js/app.55df3157.js b/priv/static/adminfe/static/js/app.55df3157.js deleted file mode 100644 index d1a37af1c..000000000 Binary files a/priv/static/adminfe/static/js/app.55df3157.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.55df3157.js.map b/priv/static/adminfe/static/js/app.55df3157.js.map deleted file mode 100644 index 740783b80..000000000 Binary files a/priv/static/adminfe/static/js/app.55df3157.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.d2c3c6b3.js b/priv/static/adminfe/static/js/app.d2c3c6b3.js new file mode 100644 index 000000000..c527207dd Binary files /dev/null and b/priv/static/adminfe/static/js/app.d2c3c6b3.js differ diff --git a/priv/static/adminfe/static/js/app.d2c3c6b3.js.map b/priv/static/adminfe/static/js/app.d2c3c6b3.js.map new file mode 100644 index 000000000..7b2d4dc05 Binary files /dev/null and b/priv/static/adminfe/static/js/app.d2c3c6b3.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js b/priv/static/adminfe/static/js/chunk-03b0.7a203856.js deleted file mode 100644 index 43ca0e4e6..000000000 Binary files a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map b/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map deleted file mode 100644 index 697a106ac..000000000 Binary files a/priv/static/adminfe/static/js/chunk-03b0.7a203856.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-17a5.13b13757.js rename to priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js index 80e7a8ac7..e3b0ae986 100644 Binary files a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js and b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js differ diff --git a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map rename to priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map index 7da1a0077..cf75f3243 100644 Binary files a/priv/static/adminfe/static/js/chunk-17a5.13b13757.js.map and b/priv/static/adminfe/static/js/chunk-0d8f.a85e3222.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js rename to priv/static/adminfe/static/js/chunk-136a.142aa42a.js index 4b100db60..812089b5f 100644 Binary files a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js and b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js differ diff --git a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map rename to priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map index a7282eaf4..f6b4c84aa 100644 Binary files a/priv/static/adminfe/static/js/chunk-2b8b.e3daf966.js.map and b/priv/static/adminfe/static/js/chunk-136a.142aa42a.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js b/priv/static/adminfe/static/js/chunk-15fa.34070731.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-15fa.15303f3a.js rename to priv/static/adminfe/static/js/chunk-15fa.34070731.js index 7d3e0c56e..937908d00 100644 Binary files a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js and b/priv/static/adminfe/static/js/chunk-15fa.34070731.js differ diff --git a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map b/priv/static/adminfe/static/js/chunk-15fa.34070731.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map rename to priv/static/adminfe/static/js/chunk-15fa.34070731.js.map index f08d1dbf9..d3830be7c 100644 Binary files a/priv/static/adminfe/static/js/chunk-15fa.15303f3a.js.map and b/priv/static/adminfe/static/js/chunk-15fa.34070731.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-293a.a728de01.js b/priv/static/adminfe/static/js/chunk-293a.a728de01.js deleted file mode 100644 index c856e21eb..000000000 Binary files a/priv/static/adminfe/static/js/chunk-293a.a728de01.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map b/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map deleted file mode 100644 index 03f61abcb..000000000 Binary files a/priv/static/adminfe/static/js/chunk-293a.a728de01.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-46cf.104380a9.js rename to priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js index 9e1e1520b..0795a46b6 100644 Binary files a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js and b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js differ diff --git a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map rename to priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map index b9357ca8f..9993be4aa 100644 Binary files a/priv/static/adminfe/static/js/chunk-46cf.104380a9.js.map and b/priv/static/adminfe/static/js/chunk-46cf.3bd3567a.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js b/priv/static/adminfe/static/js/chunk-46ef.215af110.js similarity index 98% rename from priv/static/adminfe/static/js/chunk-453a.2fcd7192.js rename to priv/static/adminfe/static/js/chunk-46ef.215af110.js index b0ee1b6b0..db11c7488 100644 Binary files a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js and b/priv/static/adminfe/static/js/chunk-46ef.215af110.js differ diff --git a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map b/priv/static/adminfe/static/js/chunk-46ef.215af110.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map rename to priv/static/adminfe/static/js/chunk-46ef.215af110.js.map index b43d2f571..2da3dbec6 100644 Binary files a/priv/static/adminfe/static/js/chunk-453a.2fcd7192.js.map and b/priv/static/adminfe/static/js/chunk-46ef.215af110.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js new file mode 100644 index 000000000..ef2379ed9 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js differ diff --git a/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map new file mode 100644 index 000000000..b349f12eb Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-4e7d.a40ad735.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js similarity index 85% rename from priv/static/adminfe/static/js/chunk-4e46.d257e435.js rename to priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js index 39c5dcc4e..5a7aa9f59 100644 Binary files a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js and b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js differ diff --git a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map similarity index 98% rename from priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map rename to priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map index 75d3554ac..7c020768c 100644 Binary files a/priv/static/adminfe/static/js/chunk-4e46.d257e435.js.map and b/priv/static/adminfe/static/js/chunk-4ffb.0e8f3772.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js similarity index 97% rename from priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js rename to priv/static/adminfe/static/js/chunk-876c.e4ceccca.js index 670016168..841ceb9dc 100644 Binary files a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js and b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js differ diff --git a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map rename to priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map index b1438722c..88976a4fe 100644 Binary files a/priv/static/adminfe/static/js/chunk-6dd6.6c139a9c.js.map and b/priv/static/adminfe/static/js/chunk-876c.e4ceccca.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js new file mode 100644 index 000000000..9766fd7d2 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js differ diff --git a/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map new file mode 100644 index 000000000..7472fcd92 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-87b3.4704cadf.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-cf57.42b96339.js b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js new file mode 100644 index 000000000..81122f992 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js differ diff --git a/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map new file mode 100644 index 000000000..7471835b9 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-cf57.42b96339.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js b/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js deleted file mode 100644 index b74c20373..000000000 Binary files a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map b/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map deleted file mode 100644 index 0f3f15299..000000000 Binary files a/priv/static/adminfe/static/js/chunk-cf58.e52693b3.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-560d.a8bb8682.js rename to priv/static/adminfe/static/js/chunk-e5cf.501d7902.js index 0b03305e9..fe5552943 100644 Binary files a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js and b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js differ diff --git a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map rename to priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map index bfab1ade9..60676bfe7 100644 Binary files a/priv/static/adminfe/static/js/chunk-560d.a8bb8682.js.map and b/priv/static/adminfe/static/js/chunk-e5cf.501d7902.js.map differ diff --git a/priv/static/adminfe/static/js/runtime.ae93ea9f.js b/priv/static/adminfe/static/js/runtime.ae93ea9f.js deleted file mode 100644 index ebda2acde..000000000 Binary files a/priv/static/adminfe/static/js/runtime.ae93ea9f.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/runtime.fa19e5d1.js b/priv/static/adminfe/static/js/runtime.fa19e5d1.js new file mode 100644 index 000000000..b905e42e1 Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.fa19e5d1.js differ diff --git a/priv/static/adminfe/static/js/runtime.ae93ea9f.js.map b/priv/static/adminfe/static/js/runtime.fa19e5d1.js.map similarity index 91% rename from priv/static/adminfe/static/js/runtime.ae93ea9f.js.map rename to priv/static/adminfe/static/js/runtime.fa19e5d1.js.map index 6392c981a..6a2565556 100644 Binary files a/priv/static/adminfe/static/js/runtime.ae93ea9f.js.map and b/priv/static/adminfe/static/js/runtime.fa19e5d1.js.map differ diff --git a/test/activity/ir/topics_test.exs b/test/activity/ir/topics_test.exs index e75f83586..44aec1e19 100644 --- a/test/activity/ir/topics_test.exs +++ b/test/activity/ir/topics_test.exs @@ -59,8 +59,8 @@ test "non-local action does not produce public:local topic", %{activity: activit describe "public visibility create events" do setup do activity = %Activity{ - object: %Object{data: %{"type" => "Create", "attachment" => []}}, - data: %{"to" => [Pleroma.Constants.as_public()]} + object: %Object{data: %{"attachment" => []}}, + data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]} } {:ok, activity: activity} @@ -98,8 +98,8 @@ test "only converts strinngs to hash tags", %{ describe "public visibility create events with attachments" do setup do activity = %Activity{ - object: %Object{data: %{"type" => "Create", "attachment" => ["foo"]}}, - data: %{"to" => [Pleroma.Constants.as_public()]} + object: %Object{data: %{"attachment" => ["foo"]}}, + data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]} } {:ok, activity: activity} diff --git a/test/earmark_renderer_test.exs b/test/earmark_renderer_test.exs new file mode 100644 index 000000000..220d97d16 --- /dev/null +++ b/test/earmark_renderer_test.exs @@ -0,0 +1,79 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only +defmodule Pleroma.EarmarkRendererTest do + use ExUnit.Case + + test "Paragraph" do + code = ~s[Hello\n\nWorld!] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "

    Hello

    World!

    " + end + + test "raw HTML" do + code = ~s[OwO] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "

    #{code}

    " + end + + test "rulers" do + code = ~s[before\n\n-----\n\nafter] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "

    before


    after

    " + end + + test "headings" do + code = ~s[# h1\n## h2\n### h3\n] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    h1

    h2

    h3

    ] + end + + test "blockquote" do + code = ~s[> whoms't are you quoting?] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "

    whoms’t are you quoting?

    " + end + + test "code" do + code = ~s[`mix`] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    mix

    ] + + code = ~s[``mix``] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    mix

    ] + + code = ~s[```\nputs "Hello World"\n```] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[
    puts "Hello World"
    ] + end + + test "lists" do + code = ~s[- one\n- two\n- three\n- four] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "" + + code = ~s[1. one\n2. two\n3. three\n4. four\n] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "
    1. one
    2. two
    3. three
    4. four
    " + end + + test "delegated renderers" do + code = ~s[a
    b] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == "

    #{code}

    " + + code = ~s[*aaaa~*] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    aaaa~

    ] + + code = ~s[**aaaa~**] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    aaaa~

    ] + + # strikethrought + code = ~s[aaaa~] + result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer}) + assert result == ~s[

    aaaa~

    ] + end +end diff --git a/test/plugs/cache_control_test.exs b/test/plugs/cache_control_test.exs index 005912ffb..6b567e81d 100644 --- a/test/plugs/cache_control_test.exs +++ b/test/plugs/cache_control_test.exs @@ -9,7 +9,7 @@ defmodule Pleroma.Web.CacheControlTest do test "Verify Cache-Control header on static assets", %{conn: conn} do conn = get(conn, "/index.html") - assert Conn.get_resp_header(conn, "cache-control") == ["public max-age=86400 must-revalidate"] + assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"] end test "Verify Cache-Control header on the API", %{conn: conn} do diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs index 18be5edd0..7f3559b83 100644 --- a/test/plugs/ensure_authenticated_plug_test.exs +++ b/test/plugs/ensure_authenticated_plug_test.exs @@ -8,24 +8,62 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.User - test "it halts if no user is assigned", %{conn: conn} do - conn = - conn - |> EnsureAuthenticatedPlug.call(%{}) + describe "without :if_func / :unless_func options" do + test "it halts if user is NOT assigned", %{conn: conn} do + conn = EnsureAuthenticatedPlug.call(conn, %{}) - assert conn.status == 403 - assert conn.halted == true + assert conn.status == 403 + assert conn.halted == true + end + + test "it continues if a user is assigned", %{conn: conn} do + conn = assign(conn, :user, %User{}) + ret_conn = EnsureAuthenticatedPlug.call(conn, %{}) + + assert ret_conn == conn + end end - test "it continues if a user is assigned", %{conn: conn} do - conn = - conn - |> assign(:user, %User{}) + describe "with :if_func / :unless_func options" do + setup do + %{ + true_fn: fn -> true end, + false_fn: fn -> false end + } + end - ret_conn = - conn - |> EnsureAuthenticatedPlug.call(%{}) + test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do + conn = assign(conn, :user, %User{}) + assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn + assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn + assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn + assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn + end - assert ret_conn == conn + test "it continues if a user is NOT assigned but :if_func evaluates to `false`", + %{conn: conn, false_fn: false_fn} do + assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn + end + + test "it continues if a user is NOT assigned but :unless_func evaluates to `true`", + %{conn: conn, true_fn: true_fn} do + assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn + end + + test "it halts if a user is NOT assigned and :if_func evaluates to `true`", + %{conn: conn, true_fn: true_fn} do + conn = EnsureAuthenticatedPlug.call(conn, if_func: true_fn) + + assert conn.status == 403 + assert conn.halted == true + end + + test "it halts if a user is NOT assigned and :unless_func evaluates to `false`", + %{conn: conn, false_fn: false_fn} do + conn = EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) + + assert conn.status == 403 + assert conn.halted == true + end end end diff --git a/test/plugs/oauth_plug_test.exs b/test/plugs/oauth_plug_test.exs index 8534a5c13..f74c068cd 100644 --- a/test/plugs/oauth_plug_test.exs +++ b/test/plugs/oauth_plug_test.exs @@ -38,7 +38,7 @@ test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do assert conn.assigns[:user] == opts[:user] end - test "with valid token(downcase) in url parameters, it assings the user", opts do + test "with valid token(downcase) in url parameters, it assigns the user", opts do conn = :get |> build_conn("/?access_token=#{opts[:token]}") diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index 8023271e4..81e2009c8 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -3,8 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Plugs.RateLimiterTest do - use ExUnit.Case, async: true - use Plug.Test + use Pleroma.Web.ConnCase alias Pleroma.Config alias Pleroma.Plugs.RateLimiter @@ -36,63 +35,44 @@ test "config is required for plug to work" do |> RateLimiter.init() |> RateLimiter.action_settings() end + end - test "it is disabled for localhost" do - Config.put([:rate_limit, @limiter_name], {1, 1}) - Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) - Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) + test "it is disabled if it remote ip plug is enabled but no remote ip is found" do + Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) + assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false)) + end - assert RateLimiter.disabled?() == true - end + test "it restricts based on config values" do + limiter_name = :test_plug_opts + scale = 80 + limit = 5 - test "it is disabled for socket" do - Config.put([:rate_limit, @limiter_name], {1, 1}) - Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) - Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + Config.put([:rate_limit, limiter_name], {scale, limit}) - assert RateLimiter.disabled?() == true - end - - test "it is enabled for socket when remote ip is enabled" do - Config.put([:rate_limit, @limiter_name], {1, 1}) - Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) - Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) - - assert RateLimiter.disabled?() == false - end - - test "it restricts based on config values" do - limiter_name = :test_plug_opts - scale = 80 - limit = 5 - - Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) - Config.put([:rate_limit, limiter_name], {scale, limit}) - - plug_opts = RateLimiter.init(name: limiter_name) - conn = conn(:get, "/") - - for i <- 1..5 do - conn = RateLimiter.call(conn, plug_opts) - assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) - Process.sleep(10) - end + plug_opts = RateLimiter.init(name: limiter_name) + conn = conn(:get, "/") + for i <- 1..5 do conn = RateLimiter.call(conn, plug_opts) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) - assert conn.halted - - Process.sleep(50) - - conn = conn(:get, "/") - - conn = RateLimiter.call(conn, plug_opts) - assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) - - refute conn.status == Plug.Conn.Status.code(:too_many_requests) - refute conn.resp_body - refute conn.halted + assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + Process.sleep(10) end + + conn = RateLimiter.call(conn, plug_opts) + assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert conn.halted + + Process.sleep(50) + + conn = conn(:get, "/") + + conn = RateLimiter.call(conn, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + + refute conn.status == Plug.Conn.Status.code(:too_many_requests) + refute conn.resp_body + refute conn.halted end describe "options" do diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy_test.exs index 18d70862c..87c6aca4e 100644 --- a/test/reverse_proxy_test.exs +++ b/test/reverse_proxy_test.exs @@ -275,17 +275,6 @@ test "returns 400 on non GET, HEAD requests", %{conn: conn} do end describe "cache resp headers" do - test "returns headers", %{conn: conn} do - ClientMock - |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ -> - {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}} - end) - |> expect(:stream_body, fn _ -> :done end) - - conn = ReverseProxy.call(conn, "/cache/10") - assert {"cache-control", "public, max-age=10"} in conn.resp_headers - end - test "add cache-control", %{conn: conn} do ClientMock |> expect(:request, fn :get, "/cache", _, _, _ -> @@ -294,7 +283,7 @@ test "add cache-control", %{conn: conn} do |> expect(:stream_body, fn _ -> :done end) conn = ReverseProxy.call(conn, "/cache") - assert {"cache-control", "public"} in conn.resp_headers + assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers end end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 0f2e81f9e..064874201 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -26,6 +26,8 @@ defmodule Pleroma.Web.ConnCase do use Pleroma.Tests.Helpers import Pleroma.Web.Router.Helpers + alias Pleroma.Config + # The default endpoint for testing @endpoint Pleroma.Web.Endpoint @@ -48,6 +50,28 @@ defp oauth_access(scopes, opts \\ []) do %{user: user, token: token, conn: conn} end + + defp ensure_federating_or_authenticated(conn, url, user) do + initial_setting = Config.get([:instance, :federating]) + on_exit(fn -> Config.put([:instance, :federating], initial_setting) end) + + Config.put([:instance, :federating], false) + + conn + |> get(url) + |> response(403) + + conn + |> assign(:user, user) + |> get(url) + |> response(200) + + Config.put([:instance, :federating], true) + + conn + |> get(url) + |> response(200) + end end end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index b2352538a..bd8e0b5cc 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do import Pleroma.Factory alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Delivery alias Pleroma.Instances alias Pleroma.Object @@ -25,9 +26,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do :ok end - clear_config_all([:instance, :federating], - do: Pleroma.Config.put([:instance, :federating], true) - ) + clear_config([:instance, :federating]) do + Config.put([:instance, :federating], true) + end describe "/relay" do clear_config([:instance, :allow_relay]) @@ -42,12 +43,21 @@ test "with the relay active, it returns the relay user", %{conn: conn} do end test "with the relay disabled, it returns 404", %{conn: conn} do - Pleroma.Config.put([:instance, :allow_relay], false) + Config.put([:instance, :allow_relay], false) conn |> get(activity_pub_path(conn, :relay)) |> json_response(404) - |> assert + end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get(activity_pub_path(conn, :relay)) + |> json_response(404) end end @@ -60,6 +70,16 @@ test "it returns the internal fetch user", %{conn: conn} do assert res["id"] =~ "/fetch" end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get(activity_pub_path(conn, :internal_fetch)) + |> json_response(404) + end end describe "/users/:nickname" do @@ -123,9 +143,34 @@ test "it returns 404 for remote users", %{ assert json_response(conn, 404) end + + test "it returns error when user is not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + + conn = + put_req_header( + conn, + "accept", + "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + ) + + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user) + end end - describe "/object/:uuid" do + describe "/objects/:uuid" do test "it returns a json representation of the object with accept application/json", %{ conn: conn } do @@ -236,6 +281,18 @@ test "cached purged after object deletion", %{conn: conn} do assert "Not found" == json_response(conn2, :not_found) end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + note = insert(:note) + uuid = String.split(note.data["id"], "/") |> List.last() + + conn = put_req_header(conn, "accept", "application/activity+json") + + ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user) + end end describe "/activities/:uuid" do @@ -307,6 +364,18 @@ test "cached purged after activity deletion", %{conn: conn} do assert "Not found" == json_response(conn2, :not_found) end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + activity = insert(:note_activity) + uuid = String.split(activity.data["id"], "/") |> List.last() + + conn = put_req_header(conn, "accept", "application/activity+json") + + ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user) + end end describe "/inbox" do @@ -379,6 +448,34 @@ test "accept follow activity", %{conn: conn} do :ok = Mix.Tasks.Pleroma.Relay.run(["list"]) assert_receive {:mix_shell, :info, ["relay.mastodon.host"]} end + + test "without valid signature, " <> + "it only accepts Create activities and requires enabled federation", + %{conn: conn} do + data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() + + conn = put_req_header(conn, "content-type", "application/activity+json") + + Config.put([:instance, :federating], false) + + conn + |> post("/inbox", data) + |> json_response(403) + + conn + |> post("/inbox", non_create_data) + |> json_response(403) + + Config.put([:instance, :federating], true) + + ret_conn = post(conn, "/inbox", data) + assert "ok" == json_response(ret_conn, 200) + + conn + |> post("/inbox", non_create_data) + |> json_response(400) + end end describe "/users/:nickname/inbox" do @@ -517,22 +614,11 @@ test "it accepts messages from actors that are followed by the user", %{ test "it rejects reads from other users", %{conn: conn} do user = insert(:user) - otheruser = insert(:user) - - conn = - conn - |> assign(:user, otheruser) - |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/inbox") - - assert json_response(conn, 403) - end - - test "it doesn't crash without an authenticated user", %{conn: conn} do - user = insert(:user) + other_user = insert(:user) conn = conn + |> assign(:user, other_user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/inbox") @@ -613,14 +699,30 @@ test "it removes all follower collections but actor's", %{conn: conn} do refute recipient.follower_address in activity.data["cc"] refute recipient.follower_address in activity.data["to"] end + + test "it requires authentication", %{conn: conn} do + user = insert(:user) + conn = put_req_header(conn, "accept", "application/activity+json") + + ret_conn = get(conn, "/users/#{user.nickname}/inbox") + assert json_response(ret_conn, 403) + + ret_conn = + conn + |> assign(:user, user) + |> get("/users/#{user.nickname}/inbox") + + assert json_response(ret_conn, 200) + end end - describe "/users/:nickname/outbox" do - test "it will not bomb when there is no activity", %{conn: conn} do + describe "GET /users/:nickname/outbox" do + test "it returns 200 even if there're no activities", %{conn: conn} do user = insert(:user) conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox") @@ -635,6 +737,7 @@ test "it returns a note activity in a collection", %{conn: conn} do conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox?page=true") @@ -647,24 +750,38 @@ test "it returns an announce activity in a collection", %{conn: conn} do conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ announce_activity.data["object"] end - test "it rejects posts from other users", %{conn: conn} do + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + conn = put_req_header(conn, "accept", "application/activity+json") + + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user) + end + end + + describe "POST /users/:nickname/outbox" do + test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() user = insert(:user) - otheruser = insert(:user) + other_user = insert(:user) + conn = put_req_header(conn, "content-type", "application/activity+json") - conn = - conn - |> assign(:user, otheruser) - |> put_req_header("content-type", "application/activity+json") - |> post("/users/#{user.nickname}/outbox", data) + conn + |> post("/users/#{user.nickname}/outbox", data) + |> json_response(403) - assert json_response(conn, 403) + conn + |> assign(:user, other_user) + |> post("/users/#{user.nickname}/outbox", data) + |> json_response(403) end test "it inserts an incoming create activity into the database", %{conn: conn} do @@ -779,24 +896,42 @@ test "it returns relay followers", %{conn: conn} do result = conn - |> assign(:relay, true) |> get("/relay/followers") |> json_response(200) assert result["first"]["orderedItems"] == [user.ap_id] end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get("/relay/followers") + |> json_response(404) + end end describe "/relay/following" do test "it returns relay following", %{conn: conn} do result = conn - |> assign(:relay, true) |> get("/relay/following") |> json_response(200) assert result["first"]["orderedItems"] == [] end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get("/relay/following") + |> json_response(404) + end end describe "/users/:nickname/followers" do @@ -807,32 +942,36 @@ test "it returns the followers in a collection", %{conn: conn} do result = conn + |> assign(:user, user_two) |> get("/users/#{user_two.nickname}/followers") |> json_response(200) assert result["first"]["orderedItems"] == [user.ap_id] end - test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do + test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do user = insert(:user) user_two = insert(:user, hide_followers: true) User.follow(user, user_two) result = conn + |> assign(:user, user) |> get("/users/#{user_two.nickname}/followers") |> json_response(200) assert is_binary(result["first"]) end - test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated", + test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user", %{conn: conn} do - user = insert(:user, hide_followers: true) + user = insert(:user) + other_user = insert(:user, hide_followers: true) result = conn - |> get("/users/#{user.nickname}/followers?page=1") + |> assign(:user, user) + |> get("/users/#{other_user.nickname}/followers?page=1") assert result.status == 403 assert result.resp_body == "" @@ -864,6 +1003,7 @@ test "it works for more than 10 users", %{conn: conn} do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/followers") |> json_response(200) @@ -873,12 +1013,21 @@ test "it works for more than 10 users", %{conn: conn} do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/followers?page=2") |> json_response(200) assert length(result["orderedItems"]) == 5 assert result["totalItems"] == 15 end + + test "returns 403 if requester is not logged in", %{conn: conn} do + user = insert(:user) + + conn + |> get("/users/#{user.nickname}/followers") + |> json_response(403) + end end describe "/users/:nickname/following" do @@ -889,6 +1038,7 @@ test "it returns the following in a collection", %{conn: conn} do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following") |> json_response(200) @@ -896,25 +1046,28 @@ test "it returns the following in a collection", %{conn: conn} do end test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do - user = insert(:user, hide_follows: true) - user_two = insert(:user) + user = insert(:user) + user_two = insert(:user, hide_follows: true) User.follow(user, user_two) result = conn - |> get("/users/#{user.nickname}/following") + |> assign(:user, user) + |> get("/users/#{user_two.nickname}/following") |> json_response(200) assert is_binary(result["first"]) end - test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated", + test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user", %{conn: conn} do - user = insert(:user, hide_follows: true) + user = insert(:user) + user_two = insert(:user, hide_follows: true) result = conn - |> get("/users/#{user.nickname}/following?page=1") + |> assign(:user, user) + |> get("/users/#{user_two.nickname}/following?page=1") assert result.status == 403 assert result.resp_body == "" @@ -947,6 +1100,7 @@ test "it works for more than 10 users", %{conn: conn} do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following") |> json_response(200) @@ -956,12 +1110,21 @@ test "it works for more than 10 users", %{conn: conn} do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following?page=2") |> json_response(200) assert length(result["orderedItems"]) == 5 assert result["totalItems"] == 15 end + + test "returns 403 if requester is not logged in", %{conn: conn} do + user = insert(:user) + + conn + |> get("/users/#{user.nickname}/following") + |> json_response(403) + end end describe "delivery tracking" do @@ -1046,8 +1209,8 @@ test "it tracks a signed activity fetch when the json is cached", %{conn: conn} end end - describe "Additionnal ActivityPub C2S endpoints" do - test "/api/ap/whoami", %{conn: conn} do + describe "Additional ActivityPub C2S endpoints" do + test "GET /api/ap/whoami", %{conn: conn} do user = insert(:user) conn = @@ -1058,12 +1221,16 @@ test "/api/ap/whoami", %{conn: conn} do user = User.get_cached_by_id(user.id) assert UserView.render("user.json", %{user: user}) == json_response(conn, 200) + + conn + |> get("/api/ap/whoami") + |> json_response(403) end clear_config([:media_proxy]) clear_config([Pleroma.Upload]) - test "uploadMedia", %{conn: conn} do + test "POST /api/ap/upload_media", %{conn: conn} do user = insert(:user) desc = "Description of the image" @@ -1083,6 +1250,10 @@ test "uploadMedia", %{conn: conn} do assert object["name"] == desc assert object["type"] == "Document" assert object["actor"] == user.ap_id + + conn + |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + |> json_response(403) end end end diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs index 3404848d4..da26b13f7 100644 --- a/test/web/activity_pub/publisher_test.exs +++ b/test/web/activity_pub/publisher_test.exs @@ -23,6 +23,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do :ok end + clear_config_all([:instance, :federating]) do + Pleroma.Config.put([:instance, :federating], true) + end + describe "gather_webfinger_links/1" do test "it returns links" do user = insert(:user) diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index e5ab54dd4..e913a5148 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -177,71 +177,6 @@ test "does not adress actor's follower address if the activity is not public", % end end - describe "fetch_ordered_collection" do - import Tesla.Mock - - test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do - mock(fn - %{method: :get, url: "http://mastodon.com/outbox"} -> - json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"}) - - %{method: :get, url: "http://mastodon.com/outbox?page=true"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]}) - end) - - assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"] - end - - test "fetches several pages in the right order one after another, but only the specified amount" do - mock(fn - %{method: :get, url: "http://example.com/outbox"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [0], - "next" => "http://example.com/outbox?page=1" - }) - - %{method: :get, url: "http://example.com/outbox?page=1"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [1], - "next" => "http://example.com/outbox?page=2" - }) - - %{method: :get, url: "http://example.com/outbox?page=2"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]}) - end) - - assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0] - assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1] - end - - test "returns an error if the url doesn't have an OrderedCollection/Page" do - mock(fn - %{method: :get, url: "http://example.com/not-an-outbox"} -> - json(%{"type" => "NotAnOutbox"}) - end) - - assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1) - end - - test "returns the what was collected if there are less pages than specified" do - mock(fn - %{method: :get, url: "http://example.com/outbox"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [0], - "next" => "http://example.com/outbox?page=1" - }) - - %{method: :get, url: "http://example.com/outbox?page=1"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]}) - end) - - assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1] - end - end - test "make_json_ld_header/0" do assert Utils.make_json_ld_header() == %{ "@context" => [ diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 8009d4386..e4c152fb7 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -3066,7 +3066,7 @@ test "pleroma restarts", %{conn: conn} do end describe "GET /api/pleroma/admin/statuses" do - test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: admin} do + test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do blocked = insert(:user) user = insert(:user) User.block(admin, blocked) @@ -3085,7 +3085,7 @@ test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: a |> json_response(200) refute "private" in Enum.map(response, & &1["visibility"]) - assert length(response) == 4 + assert length(response) == 3 end test "returns only local statuses with local_only on", %{conn: conn} do @@ -3102,12 +3102,16 @@ test "returns only local statuses with local_only on", %{conn: conn} do assert length(response) == 1 end - test "returns private statuses with godmode on", %{conn: conn} do + test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do user = insert(:user) + + {:ok, _} = + CommonAPI.post(user, %{"status" => "@#{admin.nickname}", "visibility" => "direct"}) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") - assert json_response(conn, 200) |> length() == 2 + assert json_response(conn, 200) |> length() == 3 end end diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index b380d10d8..45fc94522 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -89,8 +89,8 @@ test "works for bare text/html" do assert output == expected - text = "

    hello world!

    \n\n

    second paragraph

    " - expected = "

    hello world!

    \n\n

    second paragraph

    " + text = "

    hello world!


    \n

    second paragraph

    " + expected = "

    hello world!


    \n

    second paragraph

    " {output, [], []} = Utils.format_input(text, "text/html") @@ -99,14 +99,14 @@ test "works for bare text/html" do test "works for bare text/markdown" do text = "**hello world**" - expected = "

    hello world

    \n" + expected = "

    hello world

    " {output, [], []} = Utils.format_input(text, "text/markdown") assert output == expected text = "**hello world**\n\n*another paragraph*" - expected = "

    hello world

    \n

    another paragraph

    \n" + expected = "

    hello world

    another paragraph

    " {output, [], []} = Utils.format_input(text, "text/markdown") @@ -118,7 +118,7 @@ test "works for bare text/markdown" do by someone """ - expected = "

    cool quote

    \n
    \n

    by someone

    \n" + expected = "

    cool quote

    by someone

    " {output, [], []} = Utils.format_input(text, "text/markdown") @@ -134,7 +134,7 @@ test "works for bare text/bbcode" do assert output == expected text = "[b]hello world![/b]\n\nsecond paragraph!" - expected = "hello world!
    \n
    \nsecond paragraph!" + expected = "hello world!

    second paragraph!" {output, [], []} = Utils.format_input(text, "text/bbcode") @@ -143,7 +143,7 @@ test "works for bare text/bbcode" do text = "[b]hello world![/b]\n\nsecond paragraph!" expected = - "hello world!
    \n
    \n<strong>second paragraph!</strong>" + "hello world!

    <strong>second paragraph!</strong>" {output, [], []} = Utils.format_input(text, "text/bbcode") @@ -156,16 +156,14 @@ test "works for text/markdown with mentions" do text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*" - expected = - ~s(

    hello world

    \n

    another @user__test and @user__test google.com paragraph

    \n) - {output, _, _} = Utils.format_input(text, "text/markdown") - assert output == expected + assert output == + ~s(

    hello world

    another @user__test and @user__test google.com paragraph

    ) end end diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs index 19a019060..00c50f003 100644 --- a/test/web/feed/user_controller_test.exs +++ b/test/web/feed/user_controller_test.exs @@ -8,222 +8,83 @@ defmodule Pleroma.Web.Feed.UserControllerTest do import Pleroma.Factory import SweetXml + alias Pleroma.Config alias Pleroma.Object alias Pleroma.User - clear_config([:feed]) - - test "gets a feed", %{conn: conn} do - Pleroma.Config.put( - [:feed, :post_title], - %{max_length: 10, omission: "..."} - ) - - activity = insert(:note_activity) - - note = - insert(:note, - data: %{ - "content" => "This is :moominmamma: note ", - "attachment" => [ - %{ - "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}] - } - ], - "inReplyTo" => activity.data["id"] - } - ) - - note_activity = insert(:note_activity, note: note) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - note2 = - insert(:note, - user: user, - data: %{"content" => "42 This is :moominmamma: note ", "inReplyTo" => activity.data["id"]} - ) - - _note_activity2 = insert(:note_activity, note: note2) - object = Object.normalize(note_activity) - - resp = - conn - |> put_req_header("content-type", "application/atom+xml") - |> get(user_feed_path(conn, :feed, user.nickname)) - |> response(200) - - activity_titles = - resp - |> SweetXml.parse() - |> SweetXml.xpath(~x"//entry/title/text()"l) - - assert activity_titles == ['42 This...', 'This is...'] - assert resp =~ object.data["content"] + clear_config([:instance, :federating]) do + Config.put([:instance, :federating], true) end - test "returns 404 for a missing feed", %{conn: conn} do - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> get(user_feed_path(conn, :feed, "nonexisting")) + describe "feed" do + clear_config([:feed]) - assert response(conn, 404) + test "gets a feed", %{conn: conn} do + Config.put( + [:feed, :post_title], + %{max_length: 10, omission: "..."} + ) + + activity = insert(:note_activity) + + note = + insert(:note, + data: %{ + "content" => "This is :moominmamma: note ", + "attachment" => [ + %{ + "url" => [ + %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"} + ] + } + ], + "inReplyTo" => activity.data["id"] + } + ) + + note_activity = insert(:note_activity, note: note) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + note2 = + insert(:note, + user: user, + data: %{ + "content" => "42 This is :moominmamma: note ", + "inReplyTo" => activity.data["id"] + } + ) + + _note_activity2 = insert(:note_activity, note: note2) + object = Object.normalize(note_activity) + + resp = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(200) + + activity_titles = + resp + |> SweetXml.parse() + |> SweetXml.xpath(~x"//entry/title/text()"l) + + assert activity_titles == ['42 This...', 'This is...'] + assert resp =~ object.data["content"] + end + + test "returns 404 for a missing feed", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(user_feed_path(conn, :feed, "nonexisting")) + + assert response(conn, 404) + end end + # Note: see ActivityPubControllerTest for JSON format tests describe "feed_redirect" do - test "undefined format. it redirects to feed", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/xml") - |> get("/users/#{user.nickname}") - |> response(302) - - assert response == - "You are being redirected." - end - - test "undefined format. it returns error when user not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/xml") - |> get(user_feed_path(conn, :feed, "jimm")) - |> response(404) - - assert response == ~S({"error":"Not found"}) - end - - test "activity+json format. it redirects on actual feed of user", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}") - |> json_response(200) - - assert response["endpoints"] == %{ - "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", - "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", - "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", - "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", - "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" - } - - assert response["@context"] == [ - "https://www.w3.org/ns/activitystreams", - "http://localhost:4001/schemas/litepub-0.1.jsonld", - %{"@language" => "und"} - ] - - assert Map.take(response, [ - "followers", - "following", - "id", - "inbox", - "manuallyApprovesFollowers", - "name", - "outbox", - "preferredUsername", - "summary", - "tag", - "type", - "url" - ]) == %{ - "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", - "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", - "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", - "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", - "manuallyApprovesFollowers" => false, - "name" => user.name, - "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", - "preferredUsername" => user.nickname, - "summary" => user.bio, - "tag" => [], - "type" => "Person", - "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" - } - end - - test "activity+json format. it returns error whe use not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/activity+json") - |> get("/users/jimm") - |> json_response(404) - - assert response == "Not found" - end - - test "json format. it redirects on actual feed of user", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/json") - |> get("/users/#{user.nickname}") - |> json_response(200) - - assert response["endpoints"] == %{ - "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", - "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", - "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", - "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", - "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" - } - - assert response["@context"] == [ - "https://www.w3.org/ns/activitystreams", - "http://localhost:4001/schemas/litepub-0.1.jsonld", - %{"@language" => "und"} - ] - - assert Map.take(response, [ - "followers", - "following", - "id", - "inbox", - "manuallyApprovesFollowers", - "name", - "outbox", - "preferredUsername", - "summary", - "tag", - "type", - "url" - ]) == %{ - "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", - "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", - "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", - "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", - "manuallyApprovesFollowers" => false, - "name" => user.name, - "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", - "preferredUsername" => user.nickname, - "summary" => user.bio, - "tag" => [], - "type" => "Person", - "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" - } - end - - test "json format. it returns error whe use not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/json") - |> get("/users/jimm") - |> json_response(404) - - assert response == "Not found" - end - - test "html format. it redirects on actual feed of user", %{conn: conn} do + test "with html format, it redirects to user feed", %{conn: conn} do note_activity = insert(:note_activity) user = User.get_cached_by_ap_id(note_activity.data["actor"]) @@ -239,7 +100,7 @@ test "html format. it redirects on actual feed of user", %{conn: conn} do ).resp_body end - test "html format. it returns error when user not found", %{conn: conn} do + test "with html format, it returns error when user is not found", %{conn: conn} do response = conn |> get("/users/jimm") @@ -247,5 +108,30 @@ test "html format. it returns error when user not found", %{conn: conn} do assert response == %{"error" => "Not found"} end + + test "with non-html / non-json format, it redirects to user feed in atom format", %{ + conn: conn + } do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + conn = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/#{user.nickname}") + + assert conn.status == 302 + assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom" + end + + test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/xml") + |> get(user_feed_path(conn, :feed, "jimm")) + |> response(404) + + assert response == ~S({"error":"Not found"}) + end end end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 7f7d8cea3..7efccd9c4 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -756,10 +756,6 @@ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_ end describe "create account by app / rate limit" do - clear_config([Pleroma.Plugs.RemoteIp, :enabled]) do - Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) - end - clear_config([:rate_limit, :app_account_creation]) do Pleroma.Config.put([:rate_limit, :app_account_creation], {10_000, 2}) end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs index f035dfeee..7ac7e4af1 100644 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -52,9 +52,8 @@ test "redirects on valid url when filename invalidated", %{conn: conn} do url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") invalid_url = String.replace(url, "test.png", "test-file.png") response = get(conn, invalid_url) - html = "You are being redirected." assert response.status == 302 - assert response.resp_body == html + assert redirected_to(response) == url end test "it performs ReverseProxy.call when signature valid", %{conn: conn} do diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 2051841c2..3b84358e4 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do import Pleroma.Factory + alias Pleroma.Config alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -16,22 +17,24 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do :ok end - clear_config_all([:instance, :federating]) do - Pleroma.Config.put([:instance, :federating], true) + clear_config([:instance, :federating]) do + Config.put([:instance, :federating], true) end - describe "GET object/2" do + # Note: see ActivityPubControllerTest for JSON format tests + describe "GET /objects/:uuid (text/html)" do + setup %{conn: conn} do + conn = put_req_header(conn, "accept", "text/html") + %{conn: conn} + end + test "redirects to /notice/id for html format", %{conn: conn} do note_activity = insert(:note_activity) object = Object.normalize(note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) url = "/objects/#{uuid}" - conn = - conn - |> put_req_header("accept", "text/html") - |> get(url) - + conn = get(conn, url) assert redirected_to(conn) == "/notice/#{note_activity.id}" end @@ -45,23 +48,25 @@ test "404s on private objects", %{conn: conn} do |> response(404) end - test "404s on nonexisting objects", %{conn: conn} do + test "404s on non-existing objects", %{conn: conn} do conn |> get("/objects/123") |> response(404) end end - describe "GET activity/2" do + # Note: see ActivityPubControllerTest for JSON format tests + describe "GET /activities/:uuid (text/html)" do + setup %{conn: conn} do + conn = put_req_header(conn, "accept", "text/html") + %{conn: conn} + end + test "redirects to /notice/id for html format", %{conn: conn} do note_activity = insert(:note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/activities/#{uuid}") - + conn = get(conn, "/activities/#{uuid}") assert redirected_to(conn) == "/notice/#{note_activity.id}" end @@ -79,19 +84,6 @@ test "404s on nonexistent activities", %{conn: conn} do |> get("/activities/123") |> response(404) end - - test "gets an activity in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - url = "/activities/#{uuid}" - - conn = - conn - |> put_req_header("accept", "application/activity+json") - |> get(url) - - assert json_response(conn, 200) - end end describe "GET notice/2" do @@ -170,7 +162,7 @@ test "404s a private notice", %{conn: conn} do assert response(conn, 404) end - test "404s a nonexisting notice", %{conn: conn} do + test "404s a non-existing notice", %{conn: conn} do url = "/notice/123" conn = @@ -179,10 +171,21 @@ test "404s a nonexisting notice", %{conn: conn} do assert response(conn, 404) end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + note_activity = insert(:note_activity) + + conn = put_req_header(conn, "accept", "text/html") + + ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user) + end end describe "GET /notice/:id/embed_player" do - test "render embed player", %{conn: conn} do + setup do note_activity = insert(:note_activity) object = Pleroma.Object.normalize(note_activity) @@ -204,9 +207,11 @@ test "render embed player", %{conn: conn} do |> Ecto.Changeset.change(data: object_data) |> Pleroma.Repo.update() - conn = - conn - |> get("/notice/#{note_activity.id}/embed_player") + %{note_activity: note_activity} + end + + test "renders embed player", %{conn: conn, note_activity: note_activity} do + conn = get(conn, "/notice/#{note_activity.id}/embed_player") assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"] @@ -272,9 +277,19 @@ test "404s when attachment isn't audio or video", %{conn: conn} do |> Ecto.Changeset.change(data: object_data) |> Pleroma.Repo.update() - assert conn - |> get("/notice/#{note_activity.id}/embed_player") - |> response(404) + conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn, + note_activity: note_activity + } do + user = insert(:user) + conn = put_req_header(conn, "accept", "text/html") + + ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user) end end end diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs index 2ce8f9fa3..c3d2ae3b4 100644 --- a/test/web/static_fe/static_fe_controller_test.exs +++ b/test/web/static_fe/static_fe_controller_test.exs @@ -1,56 +1,46 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI import Pleroma.Factory clear_config_all([:static_fe, :enabled]) do - Pleroma.Config.put([:static_fe, :enabled], true) + Config.put([:static_fe, :enabled], true) end - describe "user profile page" do - test "just the profile as HTML", %{conn: conn} do - user = insert(:user) + clear_config([:instance, :federating]) do + Config.put([:instance, :federating], true) + end - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/users/#{user.nickname}") + setup %{conn: conn} do + conn = put_req_header(conn, "accept", "text/html") + user = insert(:user) + + %{conn: conn, user: user} + end + + describe "user profile html" do + test "just the profile as HTML", %{conn: conn, user: user} do + conn = get(conn, "/users/#{user.nickname}") assert html_response(conn, 200) =~ user.nickname end - test "renders json unless there's an html accept header", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> put_req_header("accept", "application/json") - |> get("/users/#{user.nickname}") - - assert json_response(conn, 200) - end - test "404 when user not found", %{conn: conn} do - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/users/limpopo") + conn = get(conn, "/users/limpopo") assert html_response(conn, 404) =~ "not found" end - test "profile does not include private messages", %{conn: conn} do - user = insert(:user) + test "profile does not include private messages", %{conn: conn, user: user} do CommonAPI.post(user, %{"status" => "public"}) CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/users/#{user.nickname}") + conn = get(conn, "/users/#{user.nickname}") html = html_response(conn, 200) @@ -58,14 +48,10 @@ test "profile does not include private messages", %{conn: conn} do refute html =~ ">private<" end - test "pagination", %{conn: conn} do - user = insert(:user) + test "pagination", %{conn: conn, user: user} do Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/users/#{user.nickname}") + conn = get(conn, "/users/#{user.nickname}") html = html_response(conn, 200) @@ -75,15 +61,11 @@ test "pagination", %{conn: conn} do refute html =~ ">test1<" end - test "pagination, page 2", %{conn: conn} do - user = insert(:user) + test "pagination, page 2", %{conn: conn, user: user} do activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) {:ok, a11} = Enum.at(activities, 11) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/users/#{user.nickname}?max_id=#{a11.id}") + conn = get(conn, "/users/#{user.nickname}?max_id=#{a11.id}") html = html_response(conn, 200) @@ -92,17 +74,17 @@ test "pagination, page 2", %{conn: conn} do refute html =~ ">test20<" refute html =~ ">test29<" end + + test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user) + end end - describe "notice rendering" do - test "single notice page", %{conn: conn} do - user = insert(:user) + describe "notice html" do + test "single notice page", %{conn: conn, user: user} do {:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"}) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/notice/#{activity.id}") + conn = get(conn, "/notice/#{activity.id}") html = html_response(conn, 200) assert html =~ "
    " @@ -110,79 +92,68 @@ test "single notice page", %{conn: conn} do assert html =~ "testing a thing!" end - test "shows the whole thread", %{conn: conn} do + test "filters HTML tags", %{conn: conn} do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"}) - - CommonAPI.post(user, %{ - "status" => "these are the voyages or something", - "in_reply_to_status_id" => activity.id - }) + {:ok, activity} = CommonAPI.post(user, %{"status" => ""}) conn = conn |> put_req_header("accept", "text/html") |> get("/notice/#{activity.id}") + html = html_response(conn, 200) + assert html =~ ~s[<script>alert('xss')</script>] + end + + test "shows the whole thread", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"}) + + CommonAPI.post(user, %{ + "status" => "these are the voyages or something", + "in_reply_to_status_id" => activity.id + }) + + conn = get(conn, "/notice/#{activity.id}") + html = html_response(conn, 200) assert html =~ "the final frontier" assert html =~ "voyages" end - test "redirect by AP object ID", %{conn: conn} do - user = insert(:user) - + test "redirect by AP object ID", %{conn: conn, user: user} do {:ok, %Activity{data: %{"object" => object_url}}} = CommonAPI.post(user, %{"status" => "beam me up"}) - conn = - conn - |> put_req_header("accept", "text/html") - |> get(URI.parse(object_url).path) + conn = get(conn, URI.parse(object_url).path) assert html_response(conn, 302) =~ "redirected" end - test "redirect by activity ID", %{conn: conn} do - user = insert(:user) - + test "redirect by activity ID", %{conn: conn, user: user} do {:ok, %Activity{data: %{"id" => id}}} = CommonAPI.post(user, %{"status" => "I'm a doctor, not a devops!"}) - conn = - conn - |> put_req_header("accept", "text/html") - |> get(URI.parse(id).path) + conn = get(conn, URI.parse(id).path) assert html_response(conn, 302) =~ "redirected" end test "404 when notice not found", %{conn: conn} do - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/notice/88c9c317") + conn = get(conn, "/notice/88c9c317") assert html_response(conn, 404) =~ "not found" end - test "404 for private status", %{conn: conn} do - user = insert(:user) - + test "404 for private status", %{conn: conn, user: user} do {:ok, activity} = CommonAPI.post(user, %{"status" => "don't show me!", "visibility" => "private"}) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/notice/#{activity.id}") + conn = get(conn, "/notice/#{activity.id}") assert html_response(conn, 404) =~ "not found" end - test "302 for remote cached status", %{conn: conn} do - user = insert(:user) - + test "302 for remote cached status", %{conn: conn, user: user} do message = %{ "@context" => "https://www.w3.org/ns/activitystreams", "to" => user.follower_address, @@ -199,12 +170,15 @@ test "302 for remote cached status", %{conn: conn} do assert {:ok, activity} = Transmogrifier.handle_incoming(message) - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/notice/#{activity.id}") + conn = get(conn, "/notice/#{activity.id}") assert html_response(conn, 302) =~ "redirected" end + + test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"}) + + ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user) + end end end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index 80a42989d..73062f18f 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -5,8 +5,10 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Config alias Pleroma.User alias Pleroma.Web.CommonAPI + import ExUnit.CaptureLog import Pleroma.Factory @@ -15,6 +17,10 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do :ok end + clear_config_all([:instance, :federating]) do + Config.put([:instance, :federating], true) + end + clear_config([:instance]) clear_config([:frontend_configurations, :pleroma_fe]) clear_config([:user, :deny_follow_blocked]) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index d464ce215..9d757b5ef 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Config alias Pleroma.Tests.ObanHelpers alias Pleroma.User @@ -178,7 +179,7 @@ test "it updates notification privacy option", %{user: user, conn: conn} do describe "GET /api/statusnet/config" do test "it returns config in xml format", %{conn: conn} do - instance = Pleroma.Config.get(:instance) + instance = Config.get(:instance) response = conn @@ -195,12 +196,12 @@ test "it returns config in xml format", %{conn: conn} do end test "it returns config in json format", %{conn: conn} do - instance = Pleroma.Config.get(:instance) - Pleroma.Config.put([:instance, :managed_config], true) - Pleroma.Config.put([:instance, :registrations_open], false) - Pleroma.Config.put([:instance, :invites_enabled], true) - Pleroma.Config.put([:instance, :public], false) - Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) + instance = Config.get(:instance) + Config.put([:instance, :managed_config], true) + Config.put([:instance, :registrations_open], false) + Config.put([:instance, :invites_enabled], true) + Config.put([:instance, :public], false) + Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) response = conn @@ -234,7 +235,7 @@ test "it returns config in json format", %{conn: conn} do end test "returns the state of safe_dm_mentions flag", %{conn: conn} do - Pleroma.Config.put([:instance, :safe_dm_mentions], true) + Config.put([:instance, :safe_dm_mentions], true) response = conn @@ -243,7 +244,7 @@ test "returns the state of safe_dm_mentions flag", %{conn: conn} do assert response["site"]["safeDMMentionsEnabled"] == "1" - Pleroma.Config.put([:instance, :safe_dm_mentions], false) + Config.put([:instance, :safe_dm_mentions], false) response = conn @@ -254,8 +255,8 @@ test "returns the state of safe_dm_mentions flag", %{conn: conn} do end test "it returns the managed config", %{conn: conn} do - Pleroma.Config.put([:instance, :managed_config], false) - Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) + Config.put([:instance, :managed_config], false) + Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) response = conn @@ -264,7 +265,7 @@ test "it returns the managed config", %{conn: conn} do refute response["site"]["pleromafe"] - Pleroma.Config.put([:instance, :managed_config], true) + Config.put([:instance, :managed_config], true) response = conn @@ -287,7 +288,7 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d } ] - Pleroma.Config.put(:frontend_configurations, config) + Config.put(:frontend_configurations, config) response = conn @@ -320,7 +321,7 @@ test "returns json with custom emoji with tags", %{conn: conn} do clear_config([:instance, :healthcheck]) test "returns 503 when healthcheck disabled", %{conn: conn} do - Pleroma.Config.put([:instance, :healthcheck], false) + Config.put([:instance, :healthcheck], false) response = conn @@ -331,7 +332,7 @@ test "returns 503 when healthcheck disabled", %{conn: conn} do end test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do - Pleroma.Config.put([:instance, :healthcheck], true) + Config.put([:instance, :healthcheck], true) with_mock Pleroma.Healthcheck, system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do @@ -351,7 +352,7 @@ test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do end test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do - Pleroma.Config.put([:instance, :healthcheck], true) + Config.put([:instance, :healthcheck], true) with_mock Pleroma.Healthcheck, system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do @@ -426,6 +427,10 @@ test "it returns version in json format", %{conn: conn} do end describe "POST /main/ostatus - remote_subscribe/2" do + clear_config([:instance, :federating]) do + Config.put([:instance, :federating], true) + end + test "renders subscribe form", %{conn: conn} do user = insert(:user)