diff options
Diffstat (limited to 'lib/pleroma')
211 files changed, 2558 insertions, 2097 deletions
| diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 53beca5e6..b88f74f47 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -292,7 +292,8 @@ defmodule Pleroma.Activity do      get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))    end -  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) +  def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id) +  def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)    def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)    def normalize(_), do: nil @@ -301,7 +302,7 @@ defmodule Pleroma.Activity do      |> Queries.by_object_id()      |> Queries.exclude_type("Delete")      |> select([u], u) -    |> Repo.delete_all() +    |> Repo.delete_all(timeout: :infinity)      |> elem(1)      |> Enum.find(fn        %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id @@ -313,13 +314,15 @@ defmodule Pleroma.Activity do    def delete_all_by_object_ap_id(_), do: nil -  defp purge_web_resp_cache(%Activity{} = activity) do -    %{path: path} = URI.parse(activity.data["id"]) -    @cachex.del(:web_resp_cache, path) +  defp purge_web_resp_cache(%Activity{data: %{"id" => id}} = activity) when is_binary(id) do +    with %{path: path} <- URI.parse(id) do +      @cachex.del(:web_resp_cache, path) +    end +      activity    end -  defp purge_web_resp_cache(nil), do: nil +  defp purge_web_resp_cache(activity), do: activity    def follow_accepted?(          %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex new file mode 100644 index 000000000..0bf393836 --- /dev/null +++ b/lib/pleroma/activity/html.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Activity.HTML do +  alias Pleroma.HTML +  alias Pleroma.Object + +  @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + +  def get_cached_scrubbed_html_for_activity( +        content, +        scrubbers, +        activity, +        key \\ "", +        callback \\ fn x -> x end +      ) do +    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}" + +    @cachex.fetch!(:scrubber_cache, key, fn _key -> +      object = Object.normalize(activity, fetch: false) +      HTML.ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback) +    end) +  end + +  def get_cached_stripped_html_for_activity(content, activity, key) do +    get_cached_scrubbed_html_for_activity( +      content, +      FastSanitize.Sanitizer.StripTags, +      activity, +      key, +      &HtmlEntities.decode/1 +    ) +  end + +  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do +    generate_scrubber_signature([scrubber]) +  end + +  defp generate_scrubber_signature(scrubbers) do +    Enum.reduce(scrubbers, "", fn scrubber, signature -> +      "#{signature}#{to_string(scrubber)}" +    end) +  end +end diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index ed898ba4f..09671f621 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -26,19 +26,23 @@ defmodule Pleroma.Activity.Search do          :plain        end -    Activity -    |> Activity.with_preloaded_object() -    |> Activity.restrict_deactivated_users() -    |> restrict_public() -    |> query_with(index_type, search_query, search_function) -    |> maybe_restrict_local(user) -    |> maybe_restrict_author(author) -    |> maybe_restrict_blocked(user) -    |> Pagination.fetch_paginated( -      %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum}, -      :offset -    ) -    |> maybe_fetch(user, search_query) +    try do +      Activity +      |> Activity.with_preloaded_object() +      |> Activity.restrict_deactivated_users() +      |> restrict_public() +      |> query_with(index_type, search_query, search_function) +      |> maybe_restrict_local(user) +      |> maybe_restrict_author(author) +      |> maybe_restrict_blocked(user) +      |> Pagination.fetch_paginated( +        %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum}, +        :offset +      ) +      |> maybe_fetch(user, search_query) +    rescue +      _ -> maybe_fetch([], user, search_query) +    end    end    def maybe_restrict_author(query, %User{} = author) do @@ -61,10 +65,17 @@ defmodule Pleroma.Activity.Search do    end    defp query_with(q, :gin, search_query, :plain) do +    %{rows: [[tsc]]} = +      Ecto.Adapters.SQL.query!( +        Pleroma.Repo, +        "select current_setting('default_text_search_config')::regconfig::oid;" +      ) +      from([a, o] in q,        where:          fragment( -          "to_tsvector(?->>'content') @@ plainto_tsquery(?)", +          "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)", +          ^tsc,            o.data,            ^search_query          ) @@ -72,10 +83,17 @@ defmodule Pleroma.Activity.Search do    end    defp query_with(q, :gin, search_query, :websearch) do +    %{rows: [[tsc]]} = +      Ecto.Adapters.SQL.query!( +        Pleroma.Repo, +        "select current_setting('default_text_search_config')::regconfig::oid;" +      ) +      from([a, o] in q,        where:          fragment( -          "to_tsvector(?->>'content') @@ websearch_to_tsquery(?)", +          "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)", +          ^tsc,            o.data,            ^search_query          ) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 06d399b2e..9824e0a4a 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Application do      if Process.whereis(Pleroma.Web.Endpoint) do        case Config.get([:http, :user_agent], :default) do          :default -> -          info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" +          info = "#{Pleroma.Web.Endpoint.url()} <#{Config.get([:instance, :email], "")}>"            named_version() <> "; " <> info          custom -> @@ -102,7 +102,7 @@ defmodule Pleroma.Application do          ] ++          task_children(@mix_env) ++          dont_run_in_test(@mix_env) ++ -        chat_child(chat_enabled?()) ++ +        shout_child(shout_enabled?()) ++          [Pleroma.Gopher.Server]      # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html @@ -216,7 +216,7 @@ defmodule Pleroma.Application do        type: :worker      } -  defp chat_enabled?, do: Config.get([:chat, :enabled]) +  defp shout_enabled?, do: Config.get([:shout, :enabled])    defp dont_run_in_test(env) when env in [:test, :benchmark], do: [] @@ -237,14 +237,14 @@ defmodule Pleroma.Application do      ]    end -  defp chat_child(true) do +  defp shout_child(true) do      [ -      Pleroma.Web.ChatChannel.ChatChannelState, +      Pleroma.Web.ShoutChannel.ShoutChannelState,        {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}      ]    end -  defp chat_child(_), do: [] +  defp shout_child(_), do: []    defp task_children(:test) do      [ diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 6ef65b263..a56311a65 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -34,15 +34,16 @@ defmodule Pleroma.ApplicationRequirements do    defp check_welcome_message_config!(:ok) do      if Pleroma.Config.get([:welcome, :email, :enabled], false) and           not Pleroma.Emails.Mailer.enabled?() do -      Logger.error(""" -      To send welcome email do you need to enable mail. -      \nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true -      """) +      Logger.warn(""" +      To send welcome emails, you need to enable the mailer. +      Welcome emails will NOT be sent with the current config. -      {:error, "The mail disabled."} -    else -      :ok +      Enable the mailer: +        config :pleroma, Pleroma.Emails.Mailer, enabled: true +      """)      end + +    :ok    end    defp check_welcome_message_config!(result), do: result @@ -51,18 +52,21 @@ defmodule Pleroma.ApplicationRequirements do    #    def check_confirmation_accounts!(:ok) do      if Pleroma.Config.get([:instance, :account_activation_required]) && -         not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do -      Logger.error( -        "Account activation enabled, but no Mailer settings enabled.\n" <> -          "Please set config :pleroma, :instance, account_activation_required: false\n" <> -          "Otherwise setup and enable Mailer." -      ) +         not Pleroma.Emails.Mailer.enabled?() do +      Logger.warn(""" +      Account activation is required, but the mailer is disabled. +      Users will NOT be able to confirm their accounts with this config. +      Either disable account activation or enable the mailer. -      {:error, -       "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."} -    else -      :ok +      Disable account activation: +        config :pleroma, :instance, account_activation_required: false + +      Enable the mailer: +        config :pleroma, Pleroma.Emails.Mailer, enabled: true +      """)      end + +    :ok    end    def check_confirmation_accounts!(result), do: result @@ -160,9 +164,12 @@ defmodule Pleroma.ApplicationRequirements do    defp check_system_commands!(:ok) do      filter_commands_statuses = [ -      check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"), -      check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"), -      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify") +      check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"), +      check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"), +      check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"), +      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"), +      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert"), +      check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe")      ]      preview_proxy_commands_status = diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex index 4a2e255f7..a38faa5b8 100644 --- a/lib/pleroma/bbs/handler.ex +++ b/lib/pleroma/bbs/handler.ex @@ -19,9 +19,7 @@ defmodule Pleroma.BBS.Handler do    def on_connect(username, ip, port, method) do      Logger.debug(fn ->        """ -      Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{ -        inspect(port) -      } using #{inspect(method)} +      Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}        """      end)    end diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 24aa5993b..029ee8b65 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -20,6 +20,140 @@ defmodule Pleroma.Config.DeprecationWarnings do       "\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}    ] +  def check_simple_policy_tuples do +    has_strings = +      Config.get([:mrf_simple]) +      |> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end) + +    if has_strings do +      Logger.warn(""" +      !!!DEPRECATION WARNING!!! +      Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: + +      ``` +      config :pleroma, :mrf_simple, +        media_removal: ["instance.tld"], +        media_nsfw: ["instance.tld"], +        federated_timeline_removal: ["instance.tld"], +        report_removal: ["instance.tld"], +        reject: ["instance.tld"], +        followers_only: ["instance.tld"], +        accept: ["instance.tld"], +        avatar_removal: ["instance.tld"], +        banner_removal: ["instance.tld"], +        reject_deletes: ["instance.tld"] +      ``` + +      Is now + + +      ``` +      config :pleroma, :mrf_simple, +        media_removal: [{"instance.tld", "Reason for media removal"}], +        media_nsfw: [{"instance.tld", "Reason for media nsfw"}], +        federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}], +        report_removal: [{"instance.tld", "Reason for report removal"}], +        reject: [{"instance.tld", "Reason for reject"}], +        followers_only: [{"instance.tld", "Reason for followers only"}], +        accept: [{"instance.tld", "Reason for accept"}], +        avatar_removal: [{"instance.tld", "Reason for avatar removal"}], +        banner_removal: [{"instance.tld", "Reason for banner removal"}], +        reject_deletes: [{"instance.tld", "Reason for reject deletes"}] +      ``` +      """) + +      new_config = +        Config.get([:mrf_simple]) +        |> Enum.map(fn {k, v} -> +          {k, +           Enum.map(v, fn +             {instance, reason} -> {instance, reason} +             instance -> {instance, ""} +           end)} +        end) + +      Config.put([:mrf_simple], new_config) + +      :error +    else +      :ok +    end +  end + +  def check_quarantined_instances_tuples do +    has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1) + +    if has_strings do +      Logger.warn(""" +      !!!DEPRECATION WARNING!!! +      Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: + +      ``` +      config :pleroma, :instance, +        quarantined_instances: ["instance.tld"] +      ``` + +      Is now + + +      ``` +      config :pleroma, :instance, +        quarantined_instances: [{"instance.tld", "Reason for quarantine"}] +      ``` +      """) + +      new_config = +        Config.get([:instance, :quarantined_instances]) +        |> Enum.map(fn +          {instance, reason} -> {instance, reason} +          instance -> {instance, ""} +        end) + +      Config.put([:instance, :quarantined_instances], new_config) + +      :error +    else +      :ok +    end +  end + +  def check_transparency_exclusions_tuples do +    has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1) + +    if has_strings do +      Logger.warn(""" +      !!!DEPRECATION WARNING!!! +      Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: + +      ``` +      config :pleroma, :mrf, +        transparency_exclusions: ["instance.tld"] +      ``` + +      Is now + + +      ``` +      config :pleroma, :mrf, +        transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}] +      ``` +      """) + +      new_config = +        Config.get([:mrf, :transparency_exclusions]) +        |> Enum.map(fn +          {instance, reason} -> {instance, reason} +          instance -> {instance, ""} +        end) + +      Config.put([:mrf, :transparency_exclusions], new_config) + +      :error +    else +      :ok +    end +  end +    def check_hellthread_threshold do      if Config.get([:mrf_hellthread, :threshold]) do        Logger.warn(""" @@ -34,19 +168,24 @@ defmodule Pleroma.Config.DeprecationWarnings do    end    def warn do -    with :ok <- check_hellthread_threshold(), -         :ok <- check_old_mrf_config(), -         :ok <- check_media_proxy_whitelist_config(), -         :ok <- check_welcome_message_config(), -         :ok <- check_gun_pool_options(), -         :ok <- check_activity_expiration_config(), -         :ok <- check_remote_ip_plug_name(), -         :ok <- check_uploders_s3_public_endpoint() do -      :ok -    else -      _ -> -        :error -    end +    [ +      check_hellthread_threshold(), +      check_old_mrf_config(), +      check_media_proxy_whitelist_config(), +      check_welcome_message_config(), +      check_gun_pool_options(), +      check_activity_expiration_config(), +      check_remote_ip_plug_name(), +      check_uploders_s3_public_endpoint(), +      check_old_chat_shoutbox(), +      check_quarantined_instances_tuples(), +      check_transparency_exclusions_tuples(), +      check_simple_policy_tuples() +    ] +    |> Enum.reduce(:ok, fn +      :ok, :ok -> :ok +      _, _ -> :error +    end)    end    def check_welcome_message_config do @@ -215,4 +354,27 @@ defmodule Pleroma.Config.DeprecationWarnings do        :ok      end    end + +  @spec check_old_chat_shoutbox() :: :ok | nil +  def check_old_chat_shoutbox do +    instance_config = Pleroma.Config.get([:instance]) +    chat_config = Pleroma.Config.get([:chat]) || [] + +    use_old_config = +      Keyword.has_key?(instance_config, :chat_limit) or +        Keyword.has_key?(chat_config, :enabled) + +    if use_old_config do +      Logger.error(""" +      !!!DEPRECATION WARNING!!! +      Your config is using the old namespace for the Shoutbox configuration. You need to convert to the new namespace. e.g., +      \n* `config :pleroma, :chat, enabled` and `config :pleroma, :instance, chat_limit` are now equal to: +      \n* `config :pleroma, :shout, enabled` and `config :pleroma, :shout, limit` +      """) + +      :error +    else +      :ok +    end +  end  end diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex index b64d06707..2a945999e 100644 --- a/lib/pleroma/config/loader.ex +++ b/lib/pleroma/config/loader.ex @@ -3,9 +3,11 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Config.Loader do +  # These modules are only being used as keys here (for equality check), +  # so it's okay to use `Module.concat/1` to have the compiler ignore them.    @reject_keys [ -    Pleroma.Repo, -    Pleroma.Web.Endpoint, +    Module.concat(["Pleroma.Repo"]), +    Module.concat(["Pleroma.Web.Endpoint"]),      :env,      :configurable_from_database,      :database, diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index 3e63bca40..53ea7d7be 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -21,9 +21,7 @@ defmodule Pleroma.Config.Oban do            """            !!!OBAN CONFIG WARNING!!!            You are using old workers in Oban crontab settings, which were removed. -          Please, remove setting from crontab in your config file (prod.secret.exs): #{ -            inspect(setting) -          } +          Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}            """            |> Logger.warn() diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index aad45aab8..5371aae7a 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -13,23 +13,25 @@ defmodule Pleroma.Config.TransferTask do    @type env() :: :test | :benchmark | :dev | :prod -  @reboot_time_keys [ -    {:pleroma, :hackney_pools}, -    {:pleroma, :chat}, -    {:pleroma, Oban}, -    {:pleroma, :rate_limit}, -    {:pleroma, :markup}, -    {:pleroma, :streamer}, -    {:pleroma, :pools}, -    {:pleroma, :connections_pool} -  ] - -  @reboot_time_subkeys [ -    {:pleroma, Pleroma.Captcha, [:seconds_valid]}, -    {:pleroma, Pleroma.Upload, [:proxy_remote]}, -    {:pleroma, :instance, [:upload_limit]}, -    {:pleroma, :gopher, [:enabled]} -  ] +  defp reboot_time_keys, +    do: [ +      {:pleroma, :hackney_pools}, +      {:pleroma, :shout}, +      {:pleroma, Oban}, +      {:pleroma, :rate_limit}, +      {:pleroma, :markup}, +      {:pleroma, :streamer}, +      {:pleroma, :pools}, +      {:pleroma, :connections_pool} +    ] + +  defp reboot_time_subkeys, +    do: [ +      {:pleroma, Pleroma.Captcha, [:seconds_valid]}, +      {:pleroma, Pleroma.Upload, [:proxy_remote]}, +      {:pleroma, :instance, [:upload_limit]}, +      {:pleroma, :gopher, [:enabled]} +    ]    def start_link(restart_pleroma? \\ true) do      load_and_update_env([], restart_pleroma?) @@ -146,9 +148,7 @@ defmodule Pleroma.Config.TransferTask do      rescue        error ->          error_msg = -          "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{ -            inspect(value) -          } error: #{inspect(error)}" +          "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"          Logger.warn(error_msg) @@ -165,12 +165,12 @@ defmodule Pleroma.Config.TransferTask do    end    defp group_and_key_need_reboot?(group, key) do -    Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end) +    Enum.any?(reboot_time_keys(), fn {g, k} -> g == group and k == key end)    end    defp group_and_subkey_need_reboot?(group, key, value) do      Keyword.keyword?(value) and -      Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} -> +      Enum.any?(reboot_time_subkeys(), fn {g, k, subkeys} ->          g == group and k == key and            Enum.any?(Keyword.keys(value), &(&1 in subkeys))        end) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index b24338cc6..bf92f65cb 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -27,6 +27,4 @@ defmodule Pleroma.Constants do      do:        ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)    ) - -  def as_local_public, do: Pleroma.Web.base_url() <> "/#Public"  end diff --git a/lib/pleroma/earmark_renderer.ex b/lib/pleroma/earmark_renderer.ex deleted file mode 100644 index 31cae3c72..000000000 --- a/lib/pleroma/earmark_renderer.ex +++ /dev/null @@ -1,256 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> -# 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, "<p>#{lines.value}</p>", 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, "<hr />", 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 = "<h#{level}>#{converted.value}</h#{level}>" -    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 = "<blockquote>#{body}</blockquote>" -    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, "<table>", attrs, [], lnb) -    context2 = set_value(context1, html) - -    context3 = -      if header do -        append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>") -      else -        # Maybe an error, needed append(context, html) -        context2 -      end - -    context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>") - -    {context4, [context4.value, "</table>"]} -  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[<pre><code#{class}>] -    lines = options.render_code.(block) -    html = ~s[#{tag}#{lines}</code></pre>] -    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}</#{type}>" -    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{</?p>}, content, "") -    html = "<li>#{content}</li>" -    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 = "<li>#{content}</li>" -    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[<div class="footnotes">], "<hr />", html, "</div>"])} -  end - -  ####################################### -  # Isolated IALs are rendered as paras # -  ####################################### - -  defp render_block(%Block.Ial{verbatim: verbatim}, context) do -    {context, "<p>{:#{verbatim}}</p>"} -  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, "<tr>"), row, tag, aligns, lnb), "</tr>") -    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}</#{tag}>") -    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/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex index af4b0e527..06fed8fb3 100644 --- a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex @@ -13,21 +13,33 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients do      cast([object])    end +  def cast(object) when is_map(object) do +    case ObjectID.cast(object) do +      {:ok, data} -> {:ok, [data]} +      _ -> :error +    end +  end +    def cast(data) when is_list(data) do -    data -    |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} -> -      case ObjectID.cast(element) do -        {:ok, id} -> -          {:cont, {:ok, [id | list]}} - -        _ -> -          {:halt, :error} -      end -    end) +    data = +      data +      |> Enum.reduce_while([], fn element, list -> +        case ObjectID.cast(element) do +          {:ok, id} -> +            {:cont, [id | list]} + +          _ -> +            {:cont, list} +        end +      end) +      |> Enum.sort() +      |> Enum.uniq() + +    {:ok, data}    end -  def cast(_) do -    :error +  def cast(data) do +    {:error, data}    end    def dump(data) do diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index 5fe74e2f7..88bc78aec 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -73,7 +73,7 @@ defmodule Pleroma.Emails.AdminEmail do      #{comment_html}      #{statuses_html}      <p> -    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a> +    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>      """      new() @@ -87,7 +87,7 @@ defmodule Pleroma.Emails.AdminEmail do      html_body = """      <p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>      <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote> -    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a> +    <a href="#{Pleroma.Web.Endpoint.url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>      """      new() diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 52f3d419d..e38c681ba 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -5,15 +5,22 @@  defmodule Pleroma.Emails.UserEmail do    @moduledoc "User emails" -  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email} -    alias Pleroma.Config    alias Pleroma.User    alias Pleroma.Web.Endpoint    alias Pleroma.Web.Router +  import Swoosh.Email +  import Phoenix.Swoosh, except: [render_body: 3]    import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0] +  def render_body(email, template, assigns \\ %{}) do +    email +    |> put_new_layout({Pleroma.Web.LayoutView, :email}) +    |> put_new_view(Pleroma.Web.EmailView) +    |> Phoenix.Swoosh.render_body(template, assigns) +  end +    defp recipient(email, nil), do: email    defp recipient(email, name), do: {name, email}    defp recipient(%User{} = user), do: recipient(user.email, user.name) diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index 50150e951..191451952 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Emoji.Formatter do    alias Pleroma.Emoji    alias Pleroma.HTML -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.MediaProxy    def emojify(text) do @@ -44,7 +44,7 @@ defmodule Pleroma.Emoji.Formatter do      Emoji.get_all()      |> Enum.filter(fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end)      |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc -> -      Map.put(acc, name, to_string(URI.merge(Web.base_url(), file))) +      Map.put(acc, name, to_string(URI.merge(Endpoint.url(), file)))      end)    end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 67acd7069..95937a892 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -60,9 +60,7 @@ defmodule Pleroma.Emoji.Loader do            if not Enum.empty?(files) do              Logger.warn( -              "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ -                Enum.join(files, ", ") -              }" +              "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"              )            end @@ -122,9 +120,7 @@ defmodule Pleroma.Emoji.Loader do          extensions = Config.get([:emoji, :pack_extensions])          Logger.info( -          "No emoji.txt found for pack \"#{pack_name}\", assuming all #{ -            Enum.join(extensions, ", ") -          } files are emoji" +          "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"          )          make_shortcode_to_file_map(pack_dir, extensions) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 7a08e48a9..ae37946ab 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -62,7 +62,7 @@ defmodule Pleroma.Formatter do    def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do      tag = String.downcase(tag) -    url = "#{Pleroma.Web.base_url()}/tag/#{tag}" +    url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"      link =        Phoenix.HTML.Tag.content_tag(:a, tag_text, @@ -121,6 +121,10 @@ defmodule Pleroma.Formatter do      end    end +  def markdown_to_html(text) do +    Earmark.as_html!(text, %Earmark.Options{compact_output: true}) +  end +    def html_escape({text, mentions, hashtags}, type) do      {html_escape(text, type), mentions, hashtags}    end diff --git a/lib/pleroma/gun.ex b/lib/pleroma/gun.ex index f9c828fac..bef1c9872 100644 --- a/lib/pleroma/gun.ex +++ b/lib/pleroma/gun.ex @@ -11,9 +11,7 @@ defmodule Pleroma.Gun do    @callback await(pid(), reference()) :: {:response, :fin, 200, []}    @callback set_owner(pid(), pid()) :: :ok -  @api Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API) - -  defp api, do: @api +  defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)    def open(host, port, opts), do: api().open(host, port, opts) diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index a56625699..a1210eabf 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -57,9 +57,7 @@ defmodule Pleroma.Gun.Conn do      else        error ->          Logger.warn( -          "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{ -            inspect(error) -          }" +          "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"          )          error @@ -93,9 +91,7 @@ defmodule Pleroma.Gun.Conn do      else        error ->          Logger.warn( -          "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{ -            inspect(error) -          }" +          "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"          )          error diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex index c37b62bf2..4c643d7cb 100644 --- a/lib/pleroma/gun/connection_pool/reclaimer.ex +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -5,11 +5,11 @@  defmodule Pleroma.Gun.ConnectionPool.Reclaimer do    use GenServer, restart: :temporary -  @registry Pleroma.Gun.ConnectionPool +  defp registry, do: Pleroma.Gun.ConnectionPool    def start_monitor do      pid = -      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {@registry, "reclaimer"}}) do +      case :gen_server.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do          {:ok, pid} ->            pid @@ -46,7 +46,7 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do      #   {worker_pid, crf, last_reference} end)      unused_conns =        Registry.select( -        @registry, +        registry(),          [            {{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}          ] diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index 02bfff274..a3fa75386 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -6,10 +6,10 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do    alias Pleroma.Gun    use GenServer, restart: :temporary -  @registry Pleroma.Gun.ConnectionPool +  defp registry, do: Pleroma.Gun.ConnectionPool    def start_link([key | _] = opts) do -    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {@registry, key}}) +    GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {registry(), key}})    end    @impl true @@ -24,7 +24,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do        time = :erlang.monotonic_time(:millisecond)        {_, _} = -        Registry.update_value(@registry, key, fn _ -> +        Registry.update_value(registry(), key, fn _ ->            {conn_pid, [client_pid], 1, time}          end) @@ -65,7 +65,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do      time = :erlang.monotonic_time(:millisecond)      {{conn_pid, used_by, _, _}, _} = -      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> +      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->          {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}        end) @@ -92,7 +92,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do    @impl true    def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do      {{_conn_pid, used_by, _crf, _last_reference}, _} = -      Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> +      Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->          {conn_pid, List.delete(used_by, client_pid), crf, last_reference}        end) diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 2dfdca693..bee66169d 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -49,31 +49,6 @@ defmodule Pleroma.HTML do    def filter_tags(html), do: filter_tags(html, nil)    def strip_tags(html), do: filter_tags(html, FastSanitize.Sanitizer.StripTags) -  def get_cached_scrubbed_html_for_activity( -        content, -        scrubbers, -        activity, -        key \\ "", -        callback \\ fn x -> x end -      ) do -    key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}" - -    @cachex.fetch!(:scrubber_cache, key, fn _key -> -      object = Pleroma.Object.normalize(activity, fetch: false) -      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback) -    end) -  end - -  def get_cached_stripped_html_for_activity(content, activity, key) do -    get_cached_scrubbed_html_for_activity( -      content, -      FastSanitize.Sanitizer.StripTags, -      activity, -      key, -      &HtmlEntities.decode/1 -    ) -  end -    def ensure_scrubbed_html(          content,          scrubbers, @@ -92,16 +67,6 @@ defmodule Pleroma.HTML do      end    end -  defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do -    generate_scrubber_signature([scrubber]) -  end - -  defp generate_scrubber_signature(scrubbers) do -    Enum.reduce(scrubbers, "", fn scrubber, signature -> -      "#{signature}#{to_string(scrubber)}" -    end) -  end -    def extract_first_external_url_from_object(%{data: %{"content" => content}} = object)        when is_binary(content) do      unless object.data["fake"] do diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 82c7fd654..251539f34 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -54,8 +54,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do      Config.get([:pools, pool, :recv_timeout], default)    end -  @prefix Pleroma.Gun.ConnectionPool    def limiter_setup do +    prefix = Pleroma.Gun.ConnectionPool      wait = Config.get([:connections_pool, :connection_acquisition_wait])      retries = Config.get([:connections_pool, :connection_acquisition_retries]) @@ -66,7 +66,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do        max_waiting = Keyword.get(opts, :max_waiting, 10)        result = -        ConcurrentLimiter.new(:"#{@prefix}.#{name}", max_running, max_waiting, +        ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,            wait: wait,            max_retries: retries          ) diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex index 51f72fbf8..16bbe6e8c 100644 --- a/lib/pleroma/http/web_push.ex +++ b/lib/pleroma/http/web_push.ex @@ -5,8 +5,8 @@  defmodule Pleroma.HTTP.WebPush do    @moduledoc false -  def post(url, payload, headers) do +  def post(url, payload, headers, options \\ []) do      list_headers = Map.to_list(headers) -    Pleroma.HTTP.post(url, payload, list_headers) +    Pleroma.HTTP.post(url, payload, list_headers, options)    end  end diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex index 80addcc52..6b57e56da 100644 --- a/lib/pleroma/instances.ex +++ b/lib/pleroma/instances.ex @@ -5,13 +5,18 @@  defmodule Pleroma.Instances do    @moduledoc "Instances context." -  @adapter Pleroma.Instances.Instance +  alias Pleroma.Instances.Instance -  defdelegate filter_reachable(urls_or_hosts), to: @adapter -  defdelegate reachable?(url_or_host), to: @adapter -  defdelegate set_reachable(url_or_host), to: @adapter -  defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter -  defdelegate get_consistently_unreachable(), to: @adapter +  def filter_reachable(urls_or_hosts), do: Instance.filter_reachable(urls_or_hosts) + +  def reachable?(url_or_host), do: Instance.reachable?(url_or_host) + +  def set_reachable(url_or_host), do: Instance.set_reachable(url_or_host) + +  def set_unreachable(url_or_host, unreachable_since \\ nil), +    do: Instance.set_unreachable(url_or_host, unreachable_since) + +  def get_consistently_unreachable, do: Instance.get_consistently_unreachable()    def set_consistently_unreachable(url_or_host),      do: set_unreachable(url_or_host, reachability_datetime_threshold()) diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 4d0e8034d..2f338b3e2 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do    alias Pleroma.Instances    alias Pleroma.Instances.Instance    alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Workers.BackgroundWorker    use Ecto.Schema @@ -195,4 +197,24 @@ defmodule Pleroma.Instances.Instance do          nil      end    end + +  @doc """ +  Deletes all users from an instance in a background task, thus also deleting +  all of those users' activities and notifications. +  """ +  def delete_users_and_activities(host) when is_binary(host) do +    BackgroundWorker.enqueue("delete_instance", %{"host" => host}) +  end + +  def perform(:delete_instance, host) when is_binary(host) do +    User.Query.build(%{nickname: "@#{host}"}) +    |> Repo.chunk_stream(100, :batches) +    |> Stream.each(fn users -> +      users +      |> Enum.each(fn user -> +        User.perform(:delete, user) +      end) +    end) +    |> Stream.run() +  end  end diff --git a/lib/pleroma/maintenance.ex b/lib/pleroma/maintenance.ex index f1058b68a..41c799712 100644 --- a/lib/pleroma/maintenance.ex +++ b/lib/pleroma/maintenance.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do    def vacuum(args) do      case args do        "analyze" -> -        Logger.info("Runnning VACUUM ANALYZE.") +        Logger.info("Running VACUUM ANALYZE.")          Repo.query!(            "vacuum analyze;", @@ -18,7 +18,7 @@ defmodule Pleroma.Maintenance do          )        "full" -> -        Logger.info("Runnning VACUUM FULL.") +        Logger.info("Running VACUUM FULL.")          Logger.warn(            "Re-packing your entire database may take a while and will consume extra disk space during the process." diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex index 0d2e94248..b08b83305 100644 --- a/lib/pleroma/maps.ex +++ b/lib/pleroma/maps.ex @@ -12,4 +12,10 @@ defmodule Pleroma.Maps do        _ -> map      end    end + +  def safe_put_in(data, keys, value) when is_map(data) and is_list(keys) do +    Kernel.put_in(data, keys, value) +  rescue +    _ -> data +  end  end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 1849cacc8..993cff09b 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -481,9 +481,7 @@ defmodule Pleroma.ModerationLog do            "visibility" => visibility          }        }) do -    "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{ -      visibility -    }'" +    "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"    end    def get_log_entry_message(%ModerationLog{ @@ -523,9 +521,7 @@ defmodule Pleroma.ModerationLog do            "subject" => subjects          }        }) do -    "@#{actor_nickname} re-sent confirmation email for users: #{ -      users_to_nicknames_string(subjects) -    }" +    "@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"    end    def get_log_entry_message(%ModerationLog{ diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 4cc9a5669..9e0ce0329 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -72,6 +72,7 @@ defmodule Pleroma.Notification do      pleroma:emoji_reaction      pleroma:report      reblog +    poll    }    def changeset(%Notification{} = notification, attrs) do @@ -391,7 +392,7 @@ defmodule Pleroma.Notification do      notifications =        Enum.map(potential_receivers, fn user ->          do_send = do_send && user in enabled_receivers -        create_notification(activity, user, do_send) +        create_notification(activity, user, do_send: do_send)        end)        |> Enum.reject(&is_nil/1) @@ -447,15 +448,18 @@ defmodule Pleroma.Notification do    end    # TODO move to sql, too. -  def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do -    unless skip?(activity, user) do +  def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do +    do_send = Keyword.get(opts, :do_send, true) +    type = Keyword.get(opts, :type, type_from_activity(activity)) + +    unless skip?(activity, user, opts) do        {:ok, %{notification: notification}} =          Multi.new()          |> Multi.insert(:notification, %Notification{            user_id: user.id,            activity: activity,            seen: mark_as_read?(activity, user), -          type: type_from_activity(activity) +          type: type          })          |> Marker.multi_set_last_read_id(user, "notifications")          |> Repo.transaction() @@ -469,6 +473,28 @@ defmodule Pleroma.Notification do      end    end +  def create_poll_notifications(%Activity{} = activity) do +    with %Object{data: %{"type" => "Question", "actor" => actor} = data} <- +           Object.normalize(activity) do +      voters = +        case data do +          %{"voters" => voters} when is_list(voters) -> voters +          _ -> [] +        end + +      notifications = +        Enum.reduce([actor | voters], [], fn ap_id, acc -> +          with %User{local: true} = user <- User.get_by_ap_id(ap_id) do +            [create_notification(activity, user, type: "poll") | acc] +          else +            _ -> acc +          end +        end) + +      {:ok, notifications} +    end +  end +    @doc """    Returns a tuple with 2 elements:      {notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)} @@ -584,8 +610,10 @@ defmodule Pleroma.Notification do      Enum.uniq(ap_ids) -- thread_muter_ap_ids    end -  @spec skip?(Activity.t(), User.t()) :: boolean() -  def skip?(%Activity{} = activity, %User{} = user) do +  def skip?(activity, user, opts \\ []) + +  @spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean() +  def skip?(%Activity{} = activity, %User{} = user, opts) do      [        :self,        :invisible, @@ -593,17 +621,21 @@ defmodule Pleroma.Notification do        :recently_followed,        :filtered      ] -    |> Enum.find(&skip?(&1, activity, user)) +    |> Enum.find(&skip?(&1, activity, user, opts))    end -  def skip?(_, _), do: false +  def skip?(_activity, _user, _opts), do: false -  @spec skip?(atom(), Activity.t(), User.t()) :: boolean() -  def skip?(:self, %Activity{} = activity, %User{} = user) do -    activity.data["actor"] == user.ap_id +  @spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean() +  def skip?(:self, %Activity{} = activity, %User{} = user, opts) do +    cond do +      opts[:type] == "poll" -> false +      activity.data["actor"] == user.ap_id -> true +      true -> false +    end    end -  def skip?(:invisible, %Activity{} = activity, _) do +  def skip?(:invisible, %Activity{} = activity, _user, _opts) do      actor = activity.data["actor"]      user = User.get_cached_by_ap_id(actor)      User.invisible?(user) @@ -612,15 +644,27 @@ defmodule Pleroma.Notification do    def skip?(          :block_from_strangers,          %Activity{} = activity, -        %User{notification_settings: %{block_from_strangers: true}} = user +        %User{notification_settings: %{block_from_strangers: true}} = user, +        opts        ) do      actor = activity.data["actor"]      follower = User.get_cached_by_ap_id(actor) -    !User.following?(follower, user) + +    cond do +      opts[:type] == "poll" -> false +      user.ap_id == actor -> false +      !User.following?(follower, user) -> true +      true -> false +    end    end    # To do: consider defining recency in hours and checking FollowingRelationship with a single SQL -  def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do +  def skip?( +        :recently_followed, +        %Activity{data: %{"type" => "Follow"}} = activity, +        %User{} = user, +        _opts +      ) do      actor = activity.data["actor"]      Notification.for_user(user) @@ -630,9 +674,10 @@ defmodule Pleroma.Notification do      end)    end -  def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false +  def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"], +    do: false -  def skip?(:filtered, activity, user) do +  def skip?(:filtered, activity, user, _opts) do      object = Object.normalize(activity, fetch: false)      cond do @@ -650,7 +695,7 @@ defmodule Pleroma.Notification do      end    end -  def skip?(_, _, _), do: false +  def skip?(_type, _activity, _user, _opts), do: false    def mark_as_read?(activity, target_user) do      user = Activity.user_actor(activity) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 3ba749d1a..c3ea1b98b 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -366,7 +366,7 @@ defmodule Pleroma.Object do    end    def local?(%Object{data: %{"id" => id}}) do -    String.starts_with?(id, Pleroma.Web.base_url() <> "/") +    String.starts_with?(id, Pleroma.Web.Endpoint.url() <> "/")    end    def replies(object, opts \\ []) do diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index bcccf1c4c..4ca67f0fd 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -4,6 +4,7 @@  defmodule Pleroma.Object.Fetcher do    alias Pleroma.HTTP +  alias Pleroma.Maps    alias Pleroma.Object    alias Pleroma.Object.Containment    alias Pleroma.Repo @@ -101,6 +102,9 @@ defmodule Pleroma.Object.Fetcher do        {:transmogrifier, {:error, {:reject, e}}} ->          {:reject, e} +      {:transmogrifier, {:reject, e}} -> +        {:reject, e} +        {:transmogrifier, _} = e ->          {:error, e} @@ -124,12 +128,14 @@ defmodule Pleroma.Object.Fetcher do    defp prepare_activity_params(data) do      %{        "type" => "Create", -      "to" => data["to"] || [], -      "cc" => data["cc"] || [],        # Should we seriously keep this attributedTo thing?        "actor" => data["actor"] || data["attributedTo"],        "object" => data      } +    |> Maps.put_if_present("to", data["to"]) +    |> Maps.put_if_present("cc", data["cc"]) +    |> Maps.put_if_present("bto", data["bto"]) +    |> Maps.put_if_present("bcc", data["bcc"])    end    def fetch_object_from_id!(id, options \\ []) do diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index b8ea06e33..61b64ed3e 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -8,8 +8,6 @@ defmodule Pleroma.Repo do      adapter: Ecto.Adapters.Postgres,      migration_timestamps: [type: :naive_datetime_usec] -  use Ecto.Explain -    import Ecto.Query    require Logger diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index 406f7e2b8..ec69a1779 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -411,7 +411,7 @@ defmodule Pleroma.ReverseProxy do      {:ok, :no_duration_limit, :no_duration_limit}    end -  defp client, do: Pleroma.ReverseProxy.Client +  defp client, do: Pleroma.ReverseProxy.Client.Wrapper    defp track_failed_url(url, error, opts) do      ttl = diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex index 8698fa2e1..75243d2dc 100644 --- a/lib/pleroma/reverse_proxy/client.ex +++ b/lib/pleroma/reverse_proxy/client.ex @@ -17,22 +17,4 @@ defmodule Pleroma.ReverseProxy.Client do    @callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()}    @callback close(reference() | pid() | map()) :: :ok - -  def request(method, url, headers, body \\ "", opts \\ []) do -    client().request(method, url, headers, body, opts) -  end - -  def stream_body(ref), do: client().stream_body(ref) - -  def close(ref), do: client().close(ref) - -  defp client do -    :tesla -    |> Application.get_env(:adapter) -    |> client() -  end - -  defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney -  defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla -  defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)  end diff --git a/lib/pleroma/reverse_proxy/client/wrapper.ex b/lib/pleroma/reverse_proxy/client/wrapper.ex new file mode 100644 index 000000000..06dd29fea --- /dev/null +++ b/lib/pleroma/reverse_proxy/client/wrapper.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ReverseProxy.Client.Wrapper do +  @moduledoc "Meta-client that calls the appropriate client from the config." +  @behaviour Pleroma.ReverseProxy.Client + +  @impl true +  def request(method, url, headers, body \\ "", opts \\ []) do +    client().request(method, url, headers, body, opts) +  end + +  @impl true +  def stream_body(ref), do: client().stream_body(ref) + +  @impl true +  def close(ref), do: client().close(ref) + +  defp client do +    :tesla +    |> Application.get_env(:adapter) +    |> client() +  end + +  defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney +  defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla +  defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client) +end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 44d2f48dc..10165c1b2 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -29,9 +29,7 @@ defmodule Pleroma.Telemetry.Logger do          _        ) do      Logger.debug(fn -> -      "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{ -        reclaim_max -      } connections" +      "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"      end)    end @@ -73,9 +71,7 @@ defmodule Pleroma.Telemetry.Logger do          _        ) do      Logger.warn(fn -> -      "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{ -        inspect(reason) -      }" +      "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"      end)    end diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex index ddf3fea4f..76514948b 100644 --- a/lib/pleroma/tests/auth_test_controller.ex +++ b/lib/pleroma/tests/auth_test_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Tests.AuthTestController do    use Pleroma.Web, :controller    alias Pleroma.User -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug    alias Pleroma.Web.Plugs.OAuthScopesPlug    # Serves only with proper OAuth token (:api and :authenticated_api) @@ -47,10 +46,7 @@ defmodule Pleroma.Tests.AuthTestController do    # Via :authenticated_api, serves if token is present and has requested scopes    #    # Suggested use: as :fallback_oauth_check but open with nil :user for :api on private instances -  plug( -    :skip_plug, -    EnsurePublicOrAuthenticatedPlug when action == :fallback_oauth_skip_publicity_check -  ) +  plug(:skip_public_check when action == :fallback_oauth_skip_publicity_check)    plug(      OAuthScopesPlug, @@ -62,11 +58,7 @@ defmodule Pleroma.Tests.AuthTestController do    # Via :authenticated_api, serves if :user is set (regardless of token presence and its scopes)    #    # Suggested use: making an :api endpoint always accessible (e.g. email confirmation endpoint) -  plug( -    :skip_plug, -    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] -    when action == :skip_oauth_skip_publicity_check -  ) +  plug(:skip_auth when action == :skip_oauth_skip_publicity_check)    # Via :authenticated_api, always fails with 403 (endpoint is insecure)    # Via :api, drops :user if present and serves if public (private instance rejects on no user) diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 654711351..17822dc5e 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -23,6 +23,9 @@ defmodule Pleroma.Upload do      is once created permanent and changing it (especially in uploaders) is probably a bad idea!    * `:tempfile` - path to the temporary file. Prefer in-place changes on the file rather than changing the    path as the temporary file is also tracked by `Plug.Upload{}` and automatically deleted once the request is over. +  * `:width` - width of the media in pixels +  * `:height` - height of the media in pixels +  * `:blurhash` - string hash of the image encoded with the blurhash algorithm (https://blurha.sh/)    Related behaviors: @@ -32,6 +35,7 @@ defmodule Pleroma.Upload do    """    alias Ecto.UUID    alias Pleroma.Config +  alias Pleroma.Maps    require Logger    @type source :: @@ -53,9 +57,12 @@ defmodule Pleroma.Upload do            name: String.t(),            tempfile: String.t(),            content_type: String.t(), +          width: integer(), +          height: integer(), +          blurhash: String.t(),            path: String.t()          } -  defstruct [:id, :name, :tempfile, :content_type, :path] +  defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]    defp get_description(opts, upload) do      case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do @@ -89,9 +96,12 @@ defmodule Pleroma.Upload do               "mediaType" => upload.content_type,               "href" => url_from_spec(upload, opts.base_url, url_spec)             } +           |> Maps.put_if_present("width", upload.width) +           |> Maps.put_if_present("height", upload.height)           ],           "name" => description -       }} +       } +       |> Maps.put_if_present("blurhash", upload.blurhash)}      else        {:description_limit, _} ->          {:error, :description_too_long} @@ -225,7 +235,7 @@ defmodule Pleroma.Upload do      case uploader do        Pleroma.Uploaders.Local -> -        upload_base_url || Pleroma.Web.base_url() <> "/media/" +        upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"        Pleroma.Uploaders.S3 ->          bucket = Config.get([Pleroma.Uploaders.S3, :bucket]) @@ -251,7 +261,7 @@ defmodule Pleroma.Upload do          end        _ -> -        public_endpoint || upload_base_url || Pleroma.Web.base_url() <> "/media/" +        public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/"      end    end  end diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex index c677d4b9f..e5db2fb20 100644 --- a/lib/pleroma/upload/filter.ex +++ b/lib/pleroma/upload/filter.ex @@ -15,13 +15,13 @@ defmodule Pleroma.Upload.Filter do    require Logger -  @callback filter(Pleroma.Upload.t()) :: +  @callback filter(upload :: struct()) ::                {:ok, :filtered}                | {:ok, :noop} -              | {:ok, :filtered, Pleroma.Upload.t()} +              | {:ok, :filtered, upload :: struct()}                | {:error, any()} -  @spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()} +  @spec filter([module()], upload :: struct()) :: {:ok, upload :: struct()} | {:error, any()}    def filter([], upload) do      {:ok, upload} diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex new file mode 100644 index 000000000..c89c30fc1 --- /dev/null +++ b/lib/pleroma/upload/filter/analyze_metadata.ex @@ -0,0 +1,83 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.AnalyzeMetadata do +  @moduledoc """ +  Extracts metadata about the upload, such as width/height +  """ +  require Logger + +  @behaviour Pleroma.Upload.Filter + +  @spec filter(Pleroma.Upload.t()) :: +          {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()} +  def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do +    try do +      image = +        file +        |> Mogrify.open() +        |> Mogrify.verbose() + +      upload = +        upload +        |> Map.put(:width, image.width) +        |> Map.put(:height, image.height) +        |> Map.put(:blurhash, get_blurhash(file)) + +      {:ok, :filtered, upload} +    rescue +      e in ErlangError -> +        Logger.warn("#{__MODULE__}: #{inspect(e)}") +        {:ok, :noop} +    end +  end + +  def filter(%Pleroma.Upload{tempfile: file, content_type: "video" <> _} = upload) do +    try do +      result = media_dimensions(file) + +      upload = +        upload +        |> Map.put(:width, result.width) +        |> Map.put(:height, result.height) + +      {:ok, :filtered, upload} +    rescue +      e in ErlangError -> +        Logger.warn("#{__MODULE__}: #{inspect(e)}") +        {:ok, :noop} +    end +  end + +  def filter(_), do: {:ok, :noop} + +  defp get_blurhash(file) do +    with {:ok, blurhash} <- :eblurhash.magick(file) do +      blurhash +    else +      _ -> nil +    end +  end + +  defp media_dimensions(file) do +    with executable when is_binary(executable) <- System.find_executable("ffprobe"), +         args = [ +           "-v", +           "error", +           "-show_entries", +           "stream=width,height", +           "-of", +           "csv=p=0:s=x", +           file +         ], +         {result, 0} <- System.cmd(executable, args), +         [width, height] <- +           String.split(String.trim(result), "x") |> Enum.map(&String.to_integer(&1)) do +      %{width: width, height: height} +    else +      nil -> {:error, {:ffprobe, :command_not_found}} +      {:error, _} = error -> error +    end +  end +end diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index 0be878ca2..deba548b7 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -35,7 +35,7 @@ defmodule Pleroma.Uploaders.Uploader do    """    @type file_spec :: {:file | :url, String.t()} -  @callback put_file(Pleroma.Upload.t()) :: +  @callback put_file(upload :: struct()) ::                :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback    @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()} @@ -46,7 +46,7 @@ defmodule Pleroma.Uploaders.Uploader do                | {:error, Plug.Conn.t(), String.t()}    @optional_callbacks http_callback: 2 -  @spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()} +  @spec put_file(module(), upload :: struct()) :: {:ok, file_spec()} | {:error, String.t()}    def put_file(uploader, upload) do      case uploader.put_file(upload) do        :ok -> {:ok, {:file, upload.path}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b78777141..3b4e49176 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -27,13 +27,13 @@ defmodule Pleroma.User do    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.UserRelationship -  alias Pleroma.Web    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.OAuth    alias Pleroma.Web.RelMe    alias Pleroma.Workers.BackgroundWorker @@ -124,7 +124,6 @@ defmodule Pleroma.User do      field(:is_moderator, :boolean, default: false)      field(:is_admin, :boolean, default: false)      field(:show_role, :boolean, default: true) -    field(:mastofe_settings, :map, default: nil)      field(:uri, ObjectValidators.Uri, default: nil)      field(:hide_followers_count, :boolean, default: false)      field(:hide_follows_count, :boolean, default: false) @@ -360,7 +359,7 @@ defmodule Pleroma.User do        _ ->          unless options[:no_default] do -          Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png") +          Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")          end      end    end @@ -368,13 +367,13 @@ defmodule Pleroma.User do    def banner_url(user, options \\ []) do      case user.banner do        %{"url" => [%{"href" => href} | _]} -> href -      _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png" +      _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"      end    end    # Should probably be renamed or removed    @spec ap_id(User.t()) :: String.t() -  def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}" +  def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"    @spec ap_followers(User.t()) :: String.t()    def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa @@ -1695,8 +1694,6 @@ defmodule Pleroma.User do        email: nil,        name: nil,        password_hash: nil, -      keys: nil, -      public_key: nil,        avatar: %{},        tags: [],        last_refreshed_at: nil, @@ -1707,9 +1704,7 @@ defmodule Pleroma.User do        follower_count: 0,        following_count: 0,        is_locked: false, -      is_confirmed: true,        password_reset_pending: false, -      is_approved: true,        registration_reason: nil,        confirmation_token: nil,        domain_blocks: [], @@ -1717,7 +1712,6 @@ defmodule Pleroma.User do        ap_enabled: false,        is_moderator: false,        is_admin: false, -      mastofe_settings: nil,        mascot: nil,        emoji: %{},        pleroma_settings_store: %{}, @@ -1725,45 +1719,53 @@ defmodule Pleroma.User do        raw_fields: [],        is_discoverable: false,        also_known_as: [] +      # id: preserved +      # ap_id: preserved +      # nickname: preserved      })    end +  # Purge doesn't delete the user from the database. +  # It just nulls all its fields and deactivates it. +  # See `User.purge_user_changeset/1` above. +  defp purge(%User{} = user) do +    user +    |> purge_user_changeset() +    |> update_and_set_cache() +  end +    def delete(users) when is_list(users) do      for user <- users, do: delete(user)    end    def delete(%User{} = user) do +    # Purge the user immediately +    purge(user)      BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})    end -  defp delete_and_invalidate_cache(%User{} = user) do +  # *Actually* delete the user from the DB +  defp delete_from_db(%User{} = user) do      invalidate_cache(user)      Repo.delete(user)    end -  defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) +  # If the user never finalized their account, it's safe to delete them. +  defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user), +    do: delete_from_db(user) -  defp delete_or_deactivate(%User{local: true} = user) do -    status = account_status(user) +  defp maybe_delete_from_db(%User{local: true, is_approved: false} = user), +    do: delete_from_db(user) -    case status do -      :confirmation_pending -> -        delete_and_invalidate_cache(user) - -      :approval_pending -> -        delete_and_invalidate_cache(user) - -      _ -> -        user -        |> purge_user_changeset() -        |> update_and_set_cache() -    end -  end +  defp maybe_delete_from_db(user), do: {:ok, user}    def perform(:force_password_reset, user), do: force_password_reset(user)    @spec perform(atom(), User.t()) :: {:ok, User.t()}    def perform(:delete, %User{} = user) do +    # Purge the user again, in case perform/2 is called directly +    purge(user) +      # Remove all relationships      user      |> get_followers() @@ -1781,10 +1783,9 @@ defmodule Pleroma.User do      delete_user_activities(user)      delete_notifications_from_user_activities(user) -      delete_outgoing_pending_follow_requests(user) -    delete_or_deactivate(user) +    maybe_delete_from_db(user)    end    def perform(:set_activation_async, user, status), do: set_activation(user, status) @@ -2245,7 +2246,7 @@ defmodule Pleroma.User do    def change_email(user, email) do      user      |> cast(%{email: email}, [:email]) -    |> validate_required([:email]) +    |> maybe_validate_required_email(false)      |> unique_constraint(:email)      |> validate_format(:email, @email_regex)      |> update_and_set_cache() @@ -2328,13 +2329,6 @@ defmodule Pleroma.User do      |> update_and_set_cache()    end -  def mastodon_settings_update(user, settings) do -    user -    |> cast(%{mastofe_settings: settings}, [:mastofe_settings]) -    |> validate_required([:mastofe_settings]) -    |> update_and_set_cache() -  end -    @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()    def confirmation_changeset(user, set_confirmation: confirmed?) do      params = diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index fa46545da..ac807fc79 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -27,7 +27,7 @@ defmodule Pleroma.User.Query do        - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})    """    import Ecto.Query -  import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1] +  import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]    alias Pleroma.FollowingRelationship    alias Pleroma.User diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index 8630f244b..5761e3b38 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -35,9 +35,10 @@ defmodule Pleroma.Web do        import Plug.Conn        import Pleroma.Web.Gettext -      import Pleroma.Web.Router.Helpers        import Pleroma.Web.TranslationHelpers +      alias Pleroma.Web.Router.Helpers, as: Routes +        plug(:set_put_layout)        defp set_put_layout(conn, _) do @@ -61,6 +62,14 @@ defmodule Pleroma.Web do          )        end +      defp skip_auth(conn, _) do +        skip_plug(conn, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]) +      end + +      defp skip_public_check(conn, _) do +        skip_plug(conn, EnsurePublicOrAuthenticatedPlug) +      end +        # Executed just before actual controller action, invokes before-action hooks (callbacks)        defp action(conn, params) do          with %{halted: false} = conn <- @@ -131,7 +140,8 @@ defmodule Pleroma.Web do        import Pleroma.Web.ErrorHelpers        import Pleroma.Web.Gettext -      import Pleroma.Web.Router.Helpers + +      alias Pleroma.Web.Router.Helpers, as: Routes        require Logger @@ -229,20 +239,4 @@ defmodule Pleroma.Web do    defmacro __using__(which) when is_atom(which) do      apply(__MODULE__, which, [])    end - -  def base_url do -    Pleroma.Web.Endpoint.url() -  end - -  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+ -  def get_api_routes do -    Pleroma.Web.Router.__routes__() -    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) -    |> Enum.map(fn r -> -      r.path -      |> String.split("/", trim: true) -      |> List.first() -    end) -    |> Enum.uniq() -  end  end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a35ba1fad..8324ca22c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -25,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    alias Pleroma.Web.Streamer    alias Pleroma.Web.WebFinger    alias Pleroma.Workers.BackgroundWorker +  alias Pleroma.Workers.PollWorker    import Ecto.Query    import Pleroma.Web.ActivityPub.Utils @@ -53,15 +54,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      {recipients, to, cc}    end -  defp check_actor_is_active(nil), do: true +  defp check_actor_can_insert(%{"type" => "Delete"}), do: true +  defp check_actor_can_insert(%{"type" => "Undo"}), do: true -  defp check_actor_is_active(actor) when is_binary(actor) do +  defp check_actor_can_insert(%{"actor" => actor}) when is_binary(actor) do      case User.get_cached_by_ap_id(actor) do        %User{is_active: true} -> true        _ -> false      end    end +  defp check_actor_can_insert(_), do: true +    defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do      limit = Config.get([:instance, :remote_limit])      String.length(content) <= limit @@ -88,7 +92,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp increase_replies_count_if_reply(_create_data), do: :noop -  @object_types ~w[ChatMessage Question Answer Audio Video Event Article] +  @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page]    @impl true    def persist(%{"type" => type} = object, meta) when type in @object_types do      with {:ok, object} <- Object.create(object) do @@ -117,7 +121,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do      with nil <- Activity.normalize(map),           map <- lazy_put_activity_defaults(map, fake), -         {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, +         {_, true} <- {:actor_check, bypass_actor_check || check_actor_can_insert(map)},           {_, true} <- {:remote_limit_pass, check_remote_limit(map)},           {:ok, map} <- MRF.filter(map),           {recipients, _, _} = get_recipients(map), @@ -285,6 +289,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},           {:ok, _actor} <- increase_note_count_if_public(actor, activity),           _ <- notify_and_stream(activity), +         :ok <- maybe_schedule_poll_notifications(activity),           :ok <- maybe_federate(activity) do        {:ok, activity}      else @@ -299,6 +304,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end +  defp maybe_schedule_poll_notifications(activity) do +    PollWorker.schedule_poll_end(activity) +    :ok +  end +    @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}    def listen(%{to: to, actor: actor, context: context, object: object} = params) do      additional = params[:additional] || %{} @@ -1627,9 +1637,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           %User{} = old_user <- User.get_by_nickname(nickname),           {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do        Logger.info( -        "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{ -          data[:ap_id] -        }, renaming." +        "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."        )        old_user diff --git a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex index 5ec8b7bab..f39cd000a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub/persisting.ex +++ b/lib/pleroma/web/activity_pub/activity_pub/persisting.ex @@ -3,5 +3,5 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.ActivityPub.Persisting do -  @callback persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} +  @callback persist(map(), keyword()) :: {:ok, struct()}  end diff --git a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex index 983168bff..33c7bf2bc 100644 --- a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex +++ b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex @@ -3,10 +3,6 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do -  alias Pleroma.Activity -  alias Pleroma.Object -  alias Pleroma.User - -  @callback stream_out(Activity.t()) :: any() -  @callback stream_out_participations(Object.t(), User.t()) :: any() +  @callback stream_out(struct()) :: any() +  @callback stream_out_participations(struct(), struct()) :: any()  end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 5aa3b281a..4a19938f6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    alias Pleroma.Object.Fetcher    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.InternalFetchActor    alias Pleroma.Web.ActivityPub.ObjectView    alias Pleroma.Web.ActivityPub.Pipeline @@ -284,15 +283,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      json(conn, "ok")    end +  def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do +    conn +    |> put_status(:bad_request) +    |> json("Invalid HTTP Signature") +  end +    # POST /relay/inbox -or- POST /internal/fetch/inbox -  def inbox(conn, params) do -    if params["type"] == "Create" && FederatingPlug.federating?() do +  def inbox(conn, %{"type" => "Create"} = params) do +    if FederatingPlug.federating?() do        post_inbox_relayed_create(conn, params)      else -      post_inbox_fallback(conn, params) +      conn +      |> put_status(:bad_request) +      |> json("Not federating")      end    end +  def inbox(conn, _params) do +    conn +    |> put_status(:bad_request) +    |> json("error, missing HTTP Signature") +  end +    defp post_inbox_relayed_create(conn, params) do      Logger.debug(        "Signature missing or not from author, relayed Create message, fetching object from source" @@ -303,23 +316,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      json(conn, "ok")    end -  defp post_inbox_fallback(conn, params) do -    headers = Enum.into(conn.req_headers, %{}) - -    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!" -      ) - -      Logger.debug(inspect(conn.req_headers)) -    end - -    conn -    |> put_status(:bad_request) -    |> json(dgettext("errors", "error")) -  end -    defp represent_service_actor(%User{} = user, conn) do      with {:ok, user} <- User.ensure_keys_present(user) do        conn @@ -403,83 +399,90 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      |> json(err)    end -  defp handle_user_activity( -         %User{} = user, -         %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params -       ) do -    content = if is_binary(object["content"]), do: object["content"], else: "" -    name = if is_binary(object["name"]), do: object["name"], else: "" -    summary = if is_binary(object["summary"]), do: object["summary"], else: "" -    length = String.length(content <> name <> summary) +  defp fix_user_message(%User{ap_id: actor}, %{"type" => "Create", "object" => object} = activity) +       when is_map(object) do +    length = +      [object["content"], object["summary"], object["name"]] +      |> Enum.filter(&is_binary(&1)) +      |> Enum.join("") +      |> String.length() -    if length > Pleroma.Config.get([:instance, :limit]) do -      {:error, dgettext("errors", "Note is over the character limit")} -    else +    limit = Pleroma.Config.get([:instance, :limit]) + +    if length < limit do        object =          object -        |> Map.merge(Map.take(params, ["to", "cc"])) -        |> Map.put("attributedTo", user.ap_id) -        |> Transmogrifier.fix_object() - -      ActivityPub.create(%{ -        to: params["to"], -        actor: user, -        context: object["context"], -        object: object, -        additional: Map.take(params, ["cc"]) -      }) -    end -  end +        |> Transmogrifier.strip_internal_fields() +        |> Map.put("attributedTo", actor) +        |> Map.put("actor", actor) +        |> Map.put("id", Utils.generate_object_id()) -  defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do -    with %Object{} = object <- Object.normalize(params["object"], fetch: false), -         true <- user.is_moderator || user.ap_id == object.data["actor"], -         {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), -         {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do -      {:ok, delete} +      {:ok, Map.put(activity, "object", object)}      else -      _ -> {:error, dgettext("errors", "Can't delete object")} +      {:error, +       dgettext( +         "errors", +         "Character limit (%{limit} characters) exceeded, contains %{length} characters", +         limit: limit, +         length: length +       )}      end    end -  defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do -    with %Object{} = object <- Object.normalize(params["object"], fetch: false), -         {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, -         {_, {:ok, %Activity{} = activity, _meta}} <- -           {:common_pipeline, -            Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do +  defp fix_user_message( +         %User{ap_id: actor} = user, +         %{"type" => "Delete", "object" => object} = activity +       ) do +    with {_, %Object{data: object_data}} <- {:normalize, Object.normalize(object, fetch: false)}, +         {_, true} <- {:permission, user.is_moderator || actor == object_data["actor"]} do        {:ok, activity}      else -      _ -> {:error, dgettext("errors", "Can't like object")} +      {:normalize, _} -> +        {:error, "No such object found"} + +      {:permission, _} -> +        {:forbidden, "You can't delete this object"}      end    end -  defp handle_user_activity(_, _) do -    {:error, dgettext("errors", "Unhandled activity type")} +  defp fix_user_message(%User{}, activity) do +    {:ok, activity}    end    def update_outbox( -        %{assigns: %{user: %User{nickname: nickname} = user}} = conn, +        %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn,          %{"nickname" => nickname} = params        ) do -    actor = user.ap_id -      params =        params -      |> Map.drop(["id"]) +      |> Map.drop(["nickname"]) +      |> Map.put("id", Utils.generate_activity_id())        |> Map.put("actor", actor) -      |> Transmogrifier.fix_addressing() -    with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do +    with {:ok, params} <- fix_user_message(user, params), +         {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true), +         %Activity{data: activity_data} <- Activity.normalize(activity) do        conn        |> put_status(:created) -      |> put_resp_header("location", activity.data["id"]) -      |> json(activity.data) +      |> put_resp_header("location", activity_data["id"]) +      |> json(activity_data)      else +      {:forbidden, message} -> +        conn +        |> put_status(:forbidden) +        |> json(message) +        {:error, message} ->          conn          |> put_status(:bad_request)          |> json(message) + +      e -> +        Logger.warn(fn -> "AP C2S: #{inspect(e)}" end) + +        conn +        |> put_status(:bad_request) +        |> json("Bad Request")      end    end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 91a45836f..647ccf432 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.CommonAPI.ActivityDraft    require Pleroma.Constants @@ -125,6 +126,37 @@ defmodule Pleroma.Web.ActivityPub.Builder do       |> Pleroma.Maps.put_if_present("context", context), []}    end +  @spec note(ActivityDraft.t()) :: {:ok, map(), keyword()} +  def note(%ActivityDraft{} = draft) do +    data = +      %{ +        "type" => "Note", +        "to" => draft.to, +        "cc" => draft.cc, +        "content" => draft.content_html, +        "summary" => draft.summary, +        "sensitive" => draft.sensitive, +        "context" => draft.context, +        "attachment" => draft.attachments, +        "actor" => draft.user.ap_id, +        "tag" => Keyword.values(draft.tags) |> Enum.uniq() +      } +      |> add_in_reply_to(draft.in_reply_to) +      |> Map.merge(draft.extra) + +    {:ok, data, []} +  end + +  defp add_in_reply_to(object, nil), do: object + +  defp add_in_reply_to(object, in_reply_to) do +    with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do +      Map.put(object, "inReplyTo", in_reply_to_object.data["id"]) +    else +      _ -> object +    end +  end +    def chat_message(actor, recipient, content, opts \\ []) do      basic = %{        "id" => Utils.generate_object_id(), @@ -223,7 +255,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do            [actor.follower_address]          public? and Visibility.is_local_public?(object) -> -          [actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()] +          [actor.follower_address, object.data["actor"], Utils.as_local_public()]          public? ->            [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index f2fec3ff6..bd6f6777f 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do            type: [:module, {:list, :module}],            description:              "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.", -          suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF} +          suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}          },          %{            key: :transparency, @@ -33,9 +33,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do          %{            key: :transparency_exclusions,            label: "MRF transparency exclusions", -          type: {:list, :string}, +          type: {:list, :tuple}, +          key_placeholder: "instance", +          value_placeholder: "reason",            description: -            "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.", +            "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",            suggestions: [              "exclusion.com"            ] @@ -51,17 +53,6 @@ defmodule Pleroma.Web.ActivityPub.MRF do    @required_description_keys [:key, :related_policy] -  @callback filter(Map.t()) :: {:ok | :reject, Map.t()} -  @callback describe() :: {:ok | :error, Map.t()} -  @callback config_description() :: %{ -              optional(:children) => [map()], -              key: atom(), -              related_policy: String.t(), -              label: String.t(), -              description: String.t() -            } -  @optional_callbacks config_description: 0 -    def filter(policies, %{} = message) do      policies      |> Enum.reduce({:ok, message}, fn @@ -111,6 +102,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do      Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)    end +  @spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()] +  def instance_list_from_tuples(list) do +    Enum.map(list, fn {instance, _} -> instance end) +  end +    def describe(policies) do      {:ok, policy_configs} =        policies @@ -142,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do    def describe, do: get_policies() |> describe()    def config_descriptions do -    Pleroma.Web.ActivityPub.MRF +    Pleroma.Web.ActivityPub.MRF.Policy      |> Pleroma.Docs.Generator.list_behaviour_implementations()      |> config_descriptions()    end @@ -161,9 +157,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do            [description | acc]          else            Logger.warn( -            "#{policy} config description doesn't have one or all required keys #{ -              inspect(@required_description_keys) -            }" +            "#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"            )            acc diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex index fc347236e..e78254280 100644 --- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do    @moduledoc "Adds expiration to all local Create activities" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(activity) do diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index b8bfdc3ce..851e95d22 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do    @moduledoc "Prevent followbots from following with a bit of heuristic" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    # XXX: this should become User.normalize_by_ap_id() or similar, really.    defp normalize_by_ap_id(%{"id" => id}), do: User.get_cached_by_ap_id(id) diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex index 40b19c3ab..cdf17fd28 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do    alias Pleroma.User -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    require Logger diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index 378175205..b3ff86eed 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do    require Logger    @moduledoc "Drop and log everything received" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(object) do diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index 2d3a10889..fad8d873b 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do    alias Pleroma.Object    @moduledoc "Ensure a re: is prepended on replies to a post with a Subject" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless]) diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex index 7307c9c14..7cf7de068 100644 --- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex @@ -1,5 +1,5 @@  defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    alias Pleroma.Config    alias Pleroma.User    alias Pleroma.Web.CommonAPI diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex index 51dbb1ad4..11871375e 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do    alias Pleroma.User -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @moduledoc "Remove bot posts from federated timeline"    require Pleroma.Constants diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex index def0c437c..b7db4fa3d 100644 --- a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do    Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.    """ -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp check_reject(message, hashtags) do      if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index 768a669f3..504bd4d57 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do    @moduledoc "Block messages with too much mentions (configurable)" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp delist_message(message, threshold) when threshold > 0 do      follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index f91b51bcf..1383fa757 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do    @moduledoc "Reject or Word-Replace messages with a keyword or regex" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp string_matches?(string, _) when not is_binary(string) do      false    end @@ -159,6 +159,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do          %{            key: :replace,            type: {:list, :tuple}, +          key_placeholder: "instance", +          value_placeholder: "reason",            description: """              **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 8dbf44071..25289d3a4 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do    @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    alias Pleroma.HTTP    alias Pleroma.Web.MediaProxy diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex index 877277d4f..05b28e4f5 100644 --- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do    @moduledoc "Block messages which mention a user" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(%{"type" => "Create"} = message) do diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex index 32bb1b645..80bef591e 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex @@ -4,9 +4,9 @@  defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do    @moduledoc "Filter local activities which have no content" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    @impl true    def filter(%{"actor" => actor} = object) do @@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do    def filter(object), do: {:ok, object}    defp is_local?(actor) do -    if actor |> String.starts_with?("#{Web.base_url()}") do +    if actor |> String.starts_with?("#{Endpoint.url()}") do        true      else        false diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex index 2ebc0674d..25031946c 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do    @moduledoc "Does nothing (lets the messages go through unmodified)" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(object) do diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex index b658d7d41..90272766c 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do    @moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter( diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index 2ad3fde0b..0d7146738 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do    @moduledoc "Scrub configured hypertext markup"    alias Pleroma.HTML -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(%{"type" => "Create", "object" => child_object} = object) do diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index aac24c0ec..02c9b18ed 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do    require Pleroma.Constants    @moduledoc "Filter activities depending on their age" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp check_date(%{"object" => %{"published" => published}} = message) do      with %DateTime{} = now <- DateTime.utc_now(), @@ -49,6 +49,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do            message            |> Map.put("to", to)            |> Map.put("cc", cc) +          |> Kernel.put_in(["object", "to"], to) +          |> Kernel.put_in(["object", "cc"], cc)          {:ok, message}        else @@ -70,6 +72,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do            message            |> Map.put("to", to)            |> Map.put("cc", cc) +          |> Kernel.put_in(["object", "to"], to) +          |> Kernel.put_in(["object", "cc"], cc)          {:ok, message}        else @@ -82,7 +86,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do    end    @impl true -  def filter(%{"type" => "Create", "published" => _} = message) do +  def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do      with actions <- Config.get([:mrf_object_age, :actions]),           {:reject, _} <- check_date(message),           {:ok, message} <- check_reject(message, actions), diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex new file mode 100644 index 000000000..a4a960c01 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/policy.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.Policy do +  @callback filter(Map.t()) :: {:ok | :reject, Map.t()} +  @callback describe() :: {:ok | :error, Map.t()} +  @callback config_description() :: %{ +              optional(:children) => [map()], +              key: atom(), +              related_policy: String.t(), +              label: String.t(), +              description: String.t() +            } +  @optional_callbacks config_description: 0 +end diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index 47a43c6a2..dbb7ca0df 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do    alias Pleroma.Config    alias Pleroma.User -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    require Pleroma.Constants @@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do    @impl true    def describe, -    do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}} +    do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Map.new()}}    @impl true    def config_description do diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 62024c58c..c631cc85f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    @moduledoc "Filter activities depending on their origin instance" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    alias Pleroma.Config    alias Pleroma.FollowingRelationship @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_accept(%{host: actor_host} = _actor_info, object) do      accepts = -      Config.get([:mrf_simple, :accept]) +      instance_list(:accept)        |> MRF.subdomains_regex()      cond do @@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_reject(%{host: actor_host} = _actor_info, object) do      rejects = -      Config.get([:mrf_simple, :reject]) +      instance_list(:reject)        |> MRF.subdomains_regex()      if MRF.subdomain_match?(rejects, actor_host) do @@ -44,7 +44,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do         )         when length(child_attachment) > 0 do      media_removal = -      Config.get([:mrf_simple, :media_removal]) +      instance_list(:media_removal)        |> MRF.subdomains_regex()      object = @@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do           } = object         ) do      media_nsfw = -      Config.get([:mrf_simple, :media_nsfw]) +      instance_list(:media_nsfw)        |> MRF.subdomains_regex()      object = @@ -85,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do      timeline_removal = -      Config.get([:mrf_simple, :federated_timeline_removal]) +      instance_list(:federated_timeline_removal)        |> MRF.subdomains_regex()      object = @@ -112,7 +112,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_followers_only(%{host: actor_host} = _actor_info, object) do      followers_only = -      Config.get([:mrf_simple, :followers_only]) +      instance_list(:followers_only)        |> MRF.subdomains_regex()      object = @@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do      report_removal = -      Config.get([:mrf_simple, :report_removal]) +      instance_list(:report_removal)        |> MRF.subdomains_regex()      if MRF.subdomain_match?(report_removal, actor_host) do @@ -151,7 +151,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do      avatar_removal = -      Config.get([:mrf_simple, :avatar_removal]) +      instance_list(:avatar_removal)        |> MRF.subdomains_regex()      if MRF.subdomain_match?(avatar_removal, actor_host) do @@ -165,7 +165,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do      banner_removal = -      Config.get([:mrf_simple, :banner_removal]) +      instance_list(:banner_removal)        |> MRF.subdomains_regex()      if MRF.subdomain_match?(banner_removal, actor_host) do @@ -177,12 +177,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    defp check_banner_removal(_actor_info, object), do: {:ok, object} +  defp check_object(%{"object" => object} = activity) do +    with {:ok, _object} <- filter(object) do +      {:ok, activity} +    end +  end + +  defp check_object(object), do: {:ok, object} + +  defp instance_list(config_key) do +    Config.get([:mrf_simple, config_key]) +    |> MRF.instance_list_from_tuples() +  end +    @impl true    def filter(%{"type" => "Delete", "actor" => actor} = object) do      %{host: actor_host} = URI.parse(actor)      reject_deletes = -      Config.get([:mrf_simple, :reject_deletes]) +      instance_list(:reject_deletes)        |> MRF.subdomains_regex()      if MRF.subdomain_match?(reject_deletes, actor_host) do @@ -202,7 +215,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do           {:ok, object} <- check_media_nsfw(actor_info, object),           {:ok, object} <- check_ftl_removal(actor_info, object),           {:ok, object} <- check_followers_only(actor_info, object), -         {:ok, object} <- check_report_removal(actor_info, object) do +         {:ok, object} <- check_report_removal(actor_info, object), +         {:ok, object} <- check_object(object) do        {:ok, object}      else        {:reject, nil} -> {:reject, "[SimplePolicy]"} @@ -227,18 +241,59 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do      end    end +  def filter(object) when is_binary(object) do +    uri = URI.parse(object) + +    with {:ok, object} <- check_accept(uri, object), +         {:ok, object} <- check_reject(uri, object) do +      {:ok, object} +    else +      {:reject, nil} -> {:reject, "[SimplePolicy]"} +      {:reject, _} = e -> e +      _ -> {:reject, "[SimplePolicy]"} +    end +  end +    def filter(object), do: {:ok, object}    @impl true    def describe do -    exclusions = Config.get([:mrf, :transparency_exclusions]) +    exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples() -    mrf_simple = +    mrf_simple_excluded =        Config.get(:mrf_simple) -      |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end) -      |> Enum.into(%{}) +      |> Enum.map(fn {rule, instances} -> +        {rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)} +      end) -    {:ok, %{mrf_simple: mrf_simple}} +    mrf_simple = +      mrf_simple_excluded +      |> Enum.map(fn {rule, instances} -> +        {rule, Enum.map(instances, fn {host, _} -> host end)} +      end) +      |> Map.new() + +    # This is for backwards compatibility. We originally didn't sent +    # extra info like a reason why an instance was rejected/quarantined/etc. +    # Because we didn't want to break backwards compatibility it was decided +    # to add an extra "info" key. +    mrf_simple_info = +      mrf_simple_excluded +      |> Enum.map(fn {rule, instances} -> +        {rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)} +      end) +      |> Enum.reject(fn {_, instances} -> instances == [] end) +      |> Enum.map(fn {rule, instances} -> +        instances = +          instances +          |> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end) +          |> Map.new() + +        {rule, instances} +      end) +      |> Map.new() + +    {:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}    end    @impl true @@ -248,70 +303,67 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do        related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",        label: "MRF Simple",        description: "Simple ingress policies", -      children: [ -        %{ -          key: :media_removal, -          type: {:list, :string}, -          description: "List of instances to strip media attachments from", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :media_nsfw, -          label: "Media NSFW", -          type: {:list, :string}, -          description: "List of instances to tag all media as NSFW (sensitive) from", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :federated_timeline_removal, -          type: {:list, :string}, -          description: -            "List of instances to remove from the Federated (aka The Whole Known Network) Timeline", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :reject, -          type: {:list, :string}, -          description: "List of instances to reject activities from (except deletes)", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :accept, -          type: {:list, :string}, -          description: "List of instances to only accept activities from (except deletes)", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :followers_only, -          type: {:list, :string}, -          description: "Force posts from the given instances to be visible by followers only", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :report_removal, -          type: {:list, :string}, -          description: "List of instances to reject reports from", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :avatar_removal, -          type: {:list, :string}, -          description: "List of instances to strip avatars from", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :banner_removal, -          type: {:list, :string}, -          description: "List of instances to strip banners from", -          suggestions: ["example.com", "*.example.com"] -        }, -        %{ -          key: :reject_deletes, -          type: {:list, :string}, -          description: "List of instances to reject deletions from", -          suggestions: ["example.com", "*.example.com"] -        } -      ] +      children: +        [ +          %{ +            key: :media_removal, +            description: +              "List of instances to strip media attachments from and the reason for doing so" +          }, +          %{ +            key: :media_nsfw, +            label: "Media NSFW", +            description: +              "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so" +          }, +          %{ +            key: :federated_timeline_removal, +            description: +              "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so" +          }, +          %{ +            key: :reject, +            description: +              "List of instances to reject activities from (except deletes) and the reason for doing so" +          }, +          %{ +            key: :accept, +            description: +              "List of instances to only accept activities from (except deletes) and the reason for doing so" +          }, +          %{ +            key: :followers_only, +            description: +              "Force posts from the given instances to be visible by followers only and the reason for doing so" +          }, +          %{ +            key: :report_removal, +            description: "List of instances to reject reports from and the reason for doing so" +          }, +          %{ +            key: :avatar_removal, +            description: "List of instances to strip avatars from and the reason for doing so" +          }, +          %{ +            key: :banner_removal, +            description: "List of instances to strip banners from and the reason for doing so" +          }, +          %{ +            key: :reject_deletes, +            description: "List of instances to reject deletions from and the reason for doing so" +          } +        ] +        |> Enum.map(fn setting -> +          Map.merge( +            setting, +            %{ +              type: {:list, :tuple}, +              key_placeholder: "instance", +              value_placeholder: "reason", +              suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}] +            } +          ) +        end)      }    end  end diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 4c5e33619..0dd415732 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do    alias Pleroma.Config    @moduledoc "Detect new emojis by their shortcode and steals them" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], []) @@ -38,9 +38,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do          end        else          Logger.debug( -          "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{ -            size_limit -          } B)" +          "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"          )          nil @@ -93,6 +91,51 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do    def filter(message), do: {:ok, message}    @impl true +  @spec config_description :: %{ +          children: [ +            %{ +              description: <<_::272, _::_*256>>, +              key: :hosts | :rejected_shortcodes | :size_limit, +              suggestions: [any(), ...], +              type: {:list, :string} | {:list, :string} | :integer +            }, +            ... +          ], +          description: <<_::448>>, +          key: :mrf_steal_emoji, +          label: <<_::80>>, +          related_policy: <<_::352>> +        } +  def config_description do +    %{ +      key: :mrf_steal_emoji, +      related_policy: "Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy", +      label: "MRF Emojis", +      description: "Steals emojis from selected instances when it sees them.", +      children: [ +        %{ +          key: :hosts, +          type: {:list, :string}, +          description: "List of hosts to steal emojis from", +          suggestions: [""] +        }, +        %{ +          key: :rejected_shortcodes, +          type: {:list, :string}, +          description: "Regex-list of shortcodes to reject", +          suggestions: [""] +        }, +        %{ +          key: :size_limit, +          type: :integer, +          description: "File size limit (in bytes), checked before an emoji is saved to the disk", +          suggestions: ["100000"] +        } +      ] +    } +  end + +  @impl true    def describe do      {:ok, %{}}    end diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex index 86965d47b..11a36aca1 100644 --- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do    require Logger -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp lookup_subchain(actor) do      with matches <- Config.get([:mrf_subchain, :match_actor]), @@ -23,9 +23,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do    def filter(%{"actor" => actor} = message) do      with {:ok, match, subchain} <- lookup_subchain(actor) do        Logger.debug( -        "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{ -          inspect(subchain) -        }" +        "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"        )        MRF.filter(subchain, message) diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index 528093ac0..56ae654f2 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do    alias Pleroma.User -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @moduledoc """       Apply policies based on user tags diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex index 65b371bf3..52fb02a84 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do    alias Pleroma.Config    @moduledoc "Accept-list of users from specified instances" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    defp filter_by_list(object, []), do: {:ok, object} @@ -37,7 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do    def describe do      mrf_user_allowlist =        Config.get([:mrf_user_allowlist], []) -      |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) +      |> Map.new(fn {k, v} -> {k, length(v)} end)      {:ok, %{mrf_user_allowlist: mrf_user_allowlist}}    end diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index ce559a239..602e10b44 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do    @moduledoc "Filter messages which belong to certain activity vocabularies" -  @behaviour Pleroma.Web.ActivityPub.MRF +  @behaviour Pleroma.Web.ActivityPub.MRF.Policy    @impl true    def filter(%{"type" => "Undo", "object" => child_message} = message) do @@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do    @impl true    def describe, -    do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}} +    do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Map.new()}}    @impl true    def config_description do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 1dce33f1a..187cd0cfd 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator -  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator @@ -102,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do          %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,          meta        ) -      when objtype in ~w[Question Answer Audio Video Event Article] do +      when objtype in ~w[Question Answer Audio Video Event Article Note Page] do      with {:ok, object_data} <- cast_and_apply(object),           meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),           {:ok, create_activity} <- @@ -115,14 +115,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    end    def validate(%{"type" => type} = object, meta) -      when type in ~w[Event Question Audio Video Article] do +      when type in ~w[Event Question Audio Video Article Note Page] do      validator =        case type do          "Event" -> EventValidator          "Question" -> QuestionValidator          "Audio" -> AudioVideoValidator          "Video" -> AudioVideoValidator -        "Article" -> ArticleNoteValidator +        "Article" -> ArticleNotePageValidator +        "Note" -> ArticleNotePageValidator +        "Page" -> ArticleNotePageValidator        end      with {:ok, object} <- @@ -174,6 +176,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do      end    end +  def validate(o, m), do: {:error, {:validator_not_set, {o, m}}} +    def cast_and_apply(%{"type" => "ChatMessage"} = object) do      ChatMessageValidator.cast_and_apply(object)    end @@ -194,8 +198,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do      EventValidator.cast_and_apply(object)    end -  def cast_and_apply(%{"type" => "Article"} = object) do -    ArticleNoteValidator.cast_and_apply(object) +  def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do +    ArticleNotePageValidator.cast_and_apply(object)    end    def cast_and_apply(o), do: {:error, {:validator_not_set, o}} @@ -209,6 +213,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    def stringify_keys(object) when is_map(object) do      object +    |> Enum.filter(fn {_, v} -> v != nil end)      |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)    end diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex index b577a1044..7c3c8d0fa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do    use Ecto.Schema    alias Pleroma.Activity -  alias Pleroma.EctoType.ActivityPub.ObjectValidators    import Ecto.Changeset    import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end    end    def cast_data(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex index f885aabe4..fc482c9c0 100644 --- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex @@ -10,19 +10,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do    require Pleroma.Constants -  alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.User    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true)      field(:target) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) -    field(:type) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) + +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end    end    def cast_and_validate(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex index 576341790..a7f2f6673 100644 --- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do    alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.Object    alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility @@ -19,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) -    field(:context, :string, autogenerate: {Utils, :generate_context_id, []}) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end + +    field(:context, :string)      field(:published, ObjectValidators.DateTime)    end @@ -36,6 +39,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do    end    def cast_data(data) do +    data = +      data +      |> fix() +      %__MODULE__{}      |> changeset(data)    end @@ -43,11 +50,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do    def changeset(struct, data) do      struct      |> cast(data, __schema__(:fields)) -    |> fix_after_cast()    end -  def fix_after_cast(cng) do -    cng +  defp fix(data) do +    data = +      data +      |> CommonFixes.fix_actor() +      |> CommonFixes.fix_activity_addressing() + +    with %Object{} = object <- Object.normalize(data["object"]) do +      data +      |> CommonFixes.fix_activity_context(object) +      |> CommonFixes.fix_object_action_recipients(object) +    else +      _ -> data +    end    end    defp validate_data(data_cng) do @@ -60,7 +77,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do      |> validate_announcable()    end -  def validate_announcable(cng) do +  defp validate_announcable(cng) do      with actor when is_binary(actor) <- get_field(cng, :actor),           object when is_binary(object) <- get_field(cng, :object),           %User{} = actor <- User.get_cached_by_ap_id(actor), @@ -68,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do           false <- Visibility.is_public?(object) do        same_actor = object.data["actor"] == actor.ap_id        recipients = get_field(cng, :to) ++ get_field(cng, :cc) -      local_public = Pleroma.Constants.as_local_public() +      local_public = Utils.as_local_public()        is_public =          Enum.member?(recipients, Pleroma.Constants.as_public()) or @@ -91,7 +108,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do      end    end -  def validate_existing_announce(cng) do +  defp validate_existing_announce(cng) do      actor = get_field(cng, :actor)      object = get_field(cng, :object) diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex index c9bd9e42d..4325e44f7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do    use Ecto.Schema    alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations    import Ecto.Changeset @@ -14,15 +15,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do    @derive Jason.Encoder    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    field(:type, :string) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +      end +    end +      field(:name, :string)      field(:inReplyTo, ObjectValidators.ObjectID)      field(:attributedTo, ObjectValidators.ObjectID) +    field(:context, :string)      # TODO: Remove actor on objects      field(:actor, ObjectValidators.ObjectID) @@ -46,6 +49,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do    end    def changeset(struct, data) do +    data = +      data +      |> CommonFixes.fix_actor() +      |> CommonFixes.fix_object_defaults() +      struct      |> cast(data, __schema__(:fields))    end diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex new file mode 100644 index 000000000..0aa249c4c --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -0,0 +1,97 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do +  use Ecto.Schema + +  alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations +  alias Pleroma.Web.ActivityPub.Transmogrifier + +  import Ecto.Changeset + +  @primary_key false +  @derive Jason.Encoder + +  embedded_schema do +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        object_fields() +        status_object_fields() +      end +    end + +    field(:replies, {:array, ObjectValidators.ObjectID}, default: []) +  end + +  def cast_and_apply(data) do +    data +    |> cast_data +    |> apply_action(:insert) +  end + +  def cast_and_validate(data) do +    data +    |> cast_data() +    |> validate_data() +  end + +  def cast_data(data) do +    %__MODULE__{} +    |> changeset(data) +  end + +  defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data +  defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"]) +  defp fix_url(data), do: data + +  defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data +  defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag]) +  defp fix_tag(data), do: Map.drop(data, ["tag"]) + +  defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data) +       when is_list(replies), +       do: Map.put(data, "replies", replies) + +  defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies), +    do: Map.put(data, "replies", replies) + +  defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies), +    do: Map.drop(data, ["replies"]) + +  defp fix_replies(data), do: data + +  defp fix(data) do +    data +    |> CommonFixes.fix_actor() +    |> CommonFixes.fix_object_defaults() +    |> fix_url() +    |> fix_tag() +    |> fix_replies() +    |> Transmogrifier.fix_emoji() +    |> Transmogrifier.fix_content_map() +  end + +  def changeset(struct, data) do +    data = fix(data) + +    struct +    |> cast(data, __schema__(:fields) -- [:attachment, :tag]) +    |> cast_embed(:attachment) +    |> cast_embed(:tag) +  end + +  defp validate_data(data_cng) do +    data_cng +    |> validate_inclusion(:type, ["Article", "Note", "Page"]) +    |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) +    |> CommonValidations.validate_any_presence([:cc, :to]) +    |> CommonValidations.validate_fields_match([:actor, :attributedTo]) +    |> CommonValidations.validate_actor_presence() +    |> CommonValidations.validate_host_match() +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex deleted file mode 100644 index 39ef6dc29..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex +++ /dev/null @@ -1,107 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do -  use Ecto.Schema - -  alias Pleroma.EctoType.ActivityPub.ObjectValidators -  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator -  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes -  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations -  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator -  alias Pleroma.Web.ActivityPub.Transmogrifier - -  import Ecto.Changeset - -  @primary_key false -  @derive Jason.Encoder - -  embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    embeds_many(:tag, TagValidator) -    field(:type, :string) - -    field(:name, :string) -    field(:summary, :string) -    field(:content, :string) - -    field(:context, :string) -    # short identifier for PleromaFE to group statuses by context -    field(:context_id, :integer) - -    # TODO: Remove actor on objects -    field(:actor, ObjectValidators.ObjectID) - -    field(:attributedTo, ObjectValidators.ObjectID) -    field(:published, ObjectValidators.DateTime) -    field(:emoji, ObjectValidators.Emoji, default: %{}) -    field(:sensitive, :boolean, default: false) -    embeds_many(:attachment, AttachmentValidator) -    field(:replies_count, :integer, default: 0) -    field(:like_count, :integer, default: 0) -    field(:announcement_count, :integer, default: 0) -    field(:inReplyTo, ObjectValidators.ObjectID) -    field(:url, ObjectValidators.Uri) - -    field(:likes, {:array, ObjectValidators.ObjectID}, default: []) -    field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) -  end - -  def cast_and_apply(data) do -    data -    |> cast_data -    |> apply_action(:insert) -  end - -  def cast_and_validate(data) do -    data -    |> cast_data() -    |> validate_data() -  end - -  def cast_data(data) do -    data = fix(data) - -    %__MODULE__{} -    |> changeset(data) -  end - -  defp fix_url(%{"url" => url} = data) when is_map(url) do -    Map.put(data, "url", url["href"]) -  end - -  defp fix_url(data), do: data - -  defp fix(data) do -    data -    |> CommonFixes.fix_defaults() -    |> CommonFixes.fix_attribution() -    |> CommonFixes.fix_actor() -    |> fix_url() -    |> Transmogrifier.fix_emoji() -  end - -  def changeset(struct, data) do -    data = fix(data) - -    struct -    |> cast(data, __schema__(:fields) -- [:attachment, :tag]) -    |> cast_embed(:attachment) -    |> cast_embed(:tag) -  end - -  defp validate_data(data_cng) do -    data_cng -    |> validate_inclusion(:type, ["Article", "Note"]) -    |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) -    |> CommonValidations.validate_any_presence([:cc, :to]) -    |> CommonValidations.validate_fields_match([:actor, :attributedTo]) -    |> CommonValidations.validate_actor_presence() -    |> CommonValidations.validate_host_match() -  end -end diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index 4a0d1473d..837787b9f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do        field(:type, :string)        field(:href, ObjectValidators.Uri)        field(:mediaType, :string, default: "application/octet-stream") +      field(:width, :integer) +      field(:height, :integer)      end    end @@ -51,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do      data = fix_media_type(data)      struct -    |> cast(data, [:type, :href, :mediaType]) +    |> cast(data, [:type, :href, :mediaType, :width, :height])      |> validate_inclusion(:type, ["Link"])      |> validate_required([:type, :href, :mediaType])    end @@ -59,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do    def fix_media_type(data) do      data = Map.put_new(data, "mediaType", data["mimeType"]) -    if MIME.valid?(data["mediaType"]) do +    if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do        data      else        Map.put(data, "mediaType", "application/octet-stream") diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex index 8a5a60526..331ec9050 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex @@ -5,12 +5,8 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do    use Ecto.Schema -  alias Pleroma.EarmarkRenderer -  alias Pleroma.EctoType.ActivityPub.ObjectValidators -  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations -  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator    alias Pleroma.Web.ActivityPub.Transmogrifier    import Ecto.Changeset @@ -19,38 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do    @derive Jason.Encoder    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    embeds_many(:tag, TagValidator) -    field(:type, :string) - -    field(:name, :string) -    field(:summary, :string) -    field(:content, :string) - -    field(:context, :string) -    # short identifier for PleromaFE to group statuses by context -    field(:context_id, :integer) - -    # TODO: Remove actor on objects -    field(:actor, ObjectValidators.ObjectID) - -    field(:attributedTo, ObjectValidators.ObjectID) -    field(:published, ObjectValidators.DateTime) -    field(:emoji, ObjectValidators.Emoji, default: %{}) -    field(:sensitive, :boolean, default: false) -    embeds_many(:attachment, AttachmentValidator) -    field(:replies_count, :integer, default: 0) -    field(:like_count, :integer, default: 0) -    field(:announcement_count, :integer, default: 0) -    field(:inReplyTo, ObjectValidators.ObjectID) -    field(:url, ObjectValidators.Uri) - -    field(:likes, {:array, ObjectValidators.ObjectID}, default: []) -    field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        object_fields() +        status_object_fields() +      end +    end    end    def cast_and_apply(data) do @@ -110,7 +82,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do         when is_binary(content) do      content =        content -      |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) +      |> Pleroma.Formatter.markdown_to_html()        |> Pleroma.HTML.filter_tags()      Map.put(data, "content", content) @@ -120,9 +92,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do    defp fix(data) do      data -    |> CommonFixes.fix_defaults() -    |> CommonFixes.fix_attribution()      |> CommonFixes.fix_actor() +    |> CommonFixes.fix_object_defaults()      |> Transmogrifier.fix_emoji()      |> fix_url()      |> fix_content() diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex index 88948135f..400e5e278 100644 --- a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex @@ -5,20 +5,21 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do    use Ecto.Schema -  alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations    import Ecto.Changeset -  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations    @primary_key false +  @derive Jason.Encoder    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:object, ObjectValidators.ObjectID) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end    end    def cast_data(data) do @@ -30,8 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do      cng      |> validate_required([:id, :type, :actor, :to, :cc, :object])      |> validate_inclusion(:type, ["Block"]) -    |> validate_actor_presence() -    |> validate_actor_presence(field_name: :object) +    |> CommonValidations.validate_actor_presence() +    |> CommonValidations.validate_actor_presence(field_name: :object)    end    def cast_and_validate(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex new file mode 100644 index 000000000..872f80ec3 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do +  alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator + +  # Activities and Objects, except (Create)ChatMessage +  defmacro message_fields do +    quote bind_quoted: binding() do +      field(:type, :string) +      field(:id, ObjectValidators.ObjectID, primary_key: true) + +      field(:to, ObjectValidators.Recipients, default: []) +      field(:cc, ObjectValidators.Recipients, default: []) +      field(:bto, ObjectValidators.Recipients, default: []) +      field(:bcc, ObjectValidators.Recipients, default: []) +    end +  end + +  defmacro activity_fields do +    quote bind_quoted: binding() do +      field(:object, ObjectValidators.ObjectID) +      field(:actor, ObjectValidators.ObjectID) +    end +  end + +  # All objects except Answer and CHatMessage +  defmacro object_fields do +    quote bind_quoted: binding() do +      field(:content, :string) + +      field(:published, ObjectValidators.DateTime) +      field(:emoji, ObjectValidators.Emoji, default: %{}) +      embeds_many(:attachment, AttachmentValidator) +    end +  end + +  # Basically objects that aren't ChatMessage and Answer +  defmacro status_object_fields do +    quote bind_quoted: binding() do +      # TODO: Remove actor on objects +      field(:actor, ObjectValidators.ObjectID) +      field(:attributedTo, ObjectValidators.ObjectID) + +      embeds_many(:tag, TagValidator) + +      field(:name, :string) +      field(:summary, :string) + +      field(:context, :string) +      # short identifier for PleromaFE to group statuses by context +      field(:context_id, :integer) + +      field(:sensitive, :boolean, default: false) +      field(:replies_count, :integer, default: 0) +      field(:like_count, :integer, default: 0) +      field(:announcement_count, :integer, default: 0) +      field(:inReplyTo, ObjectValidators.ObjectID) +      field(:url, ObjectValidators.Uri) + +      field(:likes, {:array, ObjectValidators.ObjectID}, default: []) +      field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) +    end +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 5f2c633bc..9631013a7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -3,29 +3,76 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do +  alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.Object    alias Pleroma.Object.Containment +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Utils -  # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults -  def fix_defaults(data) do +  def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do +    {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback) + +    data = +      Enum.reject(data, fn x -> +        String.ends_with?(x, "/followers") and x != follower_collection +      end) + +    Map.put(message, field, data) +  end + +  def fix_object_defaults(data) do      %{data: %{"id" => context}, id: context_id} =        Utils.create_context(data["context"] || data["conversation"]) +    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"]) +      data      |> Map.put("context", context)      |> Map.put("context_id", context_id) +    |> cast_and_filter_recipients("to", follower_collection) +    |> cast_and_filter_recipients("cc", follower_collection) +    |> cast_and_filter_recipients("bto", follower_collection) +    |> cast_and_filter_recipients("bcc", follower_collection) +    |> Transmogrifier.fix_implicit_addressing(follower_collection)    end -  def fix_attribution(data) do -    data -    |> Map.put_new("actor", data["attributedTo"]) +  def fix_activity_addressing(activity) do +    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"]) + +    activity +    |> cast_and_filter_recipients("to", follower_collection) +    |> cast_and_filter_recipients("cc", follower_collection) +    |> cast_and_filter_recipients("bto", follower_collection) +    |> cast_and_filter_recipients("bcc", follower_collection) +    |> Transmogrifier.fix_implicit_addressing(follower_collection)    end    def fix_actor(data) do -    actor = Containment.get_actor(data) +    actor = +      data +      |> Map.put_new("actor", data["attributedTo"]) +      |> Containment.get_actor()      data      |> Map.put("actor", actor)      |> Map.put("attributedTo", actor)    end + +  def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do +    data +    |> Map.put("context", object_context) +  end + +  def fix_object_action_recipients(%{"actor" => actor} = data, %Object{data: %{"actor" => actor}}) do +    to = ((data["to"] || []) -- [actor]) |> Enum.uniq() + +    Map.put(data, "to", to) +  end + +  def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do +    to = ((data["to"] || []) ++ [actor]) |> Enum.uniq() + +    Map.put(data, "to", to) +  end  end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index 940430588..be5074348 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do        fields        |> Enum.map(fn field -> get_field(cng, field) end)        |> Enum.any?(fn +        nil -> false          [] -> false          _ -> true        end) diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex index 7a31a99bf..6551f64ca 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex @@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do    @primary_key false    embedded_schema do +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        activity_fields() +      end +    end +      field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:actor, ObjectValidators.ObjectID)      field(:type, :string)      field(:to, ObjectValidators.Recipients, default: []) -    field(:object, ObjectValidators.ObjectID)    end    def cast_and_apply(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex index e06e442f4..803b5d5a1 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex @@ -10,20 +10,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do    alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.Object +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations +  alias Pleroma.Web.ActivityPub.Transmogrifier    import Ecto.Changeset    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:actor, ObjectValidators.ObjectID) -    field(:type, :string) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:object, ObjectValidators.ObjectID) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end +      field(:expires_at, ObjectValidators.DateTime)      # Should be moved to object, done for CommonAPI.Utils.make_context @@ -54,39 +58,37 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do      |> cast(data, __schema__(:fields))    end -  defp fix_context(data, meta) do -    if object = meta[:object_data] do -      Map.put_new(data, "context", object["context"]) -    else -      data -    end -  end +  # CommonFixes.fix_activity_addressing adapted for Create specific behavior +  defp fix_addressing(data, object) do +    %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"]) -  defp fix_addressing(data, meta) do -    if object = meta[:object_data] do -      data -      |> Map.put_new("to", object["to"] || []) -      |> Map.put_new("cc", object["cc"] || []) -    else -      data -    end +    data +    |> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"]) +    |> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"]) +    |> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"]) +    |> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"]) +    |> Transmogrifier.fix_implicit_addressing(follower_collection)    end -  defp fix(data, meta) do +  def fix(data, meta) do +    object = meta[:object_data] +      data -    |> fix_context(meta) -    |> fix_addressing(meta)      |> CommonFixes.fix_actor() +    |> Map.put_new("context", object["context"]) +    |> fix_addressing(object)    end    defp validate_data(cng, meta) do +    object = meta[:object_data] +      cng -    |> validate_required([:actor, :type, :object]) +    |> validate_required([:actor, :type, :object, :to, :cc])      |> validate_inclusion(:type, ["Create"])      |> CommonValidations.validate_actor_presence() -    |> CommonValidations.validate_any_presence([:to, :cc]) -    |> validate_actors_match(meta) -    |> validate_context_match(meta) +    |> validate_actors_match(object) +    |> validate_context_match(object) +    |> validate_addressing_match(object)      |> validate_object_nonexistence()      |> validate_object_containment()    end @@ -118,8 +120,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do      end)    end -  def validate_actors_match(cng, meta) do -    attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"] +  def validate_actors_match(cng, object) do +    attributed_to = object["attributedTo"] || object["actor"]      cng      |> validate_change(:actor, fn :actor, actor -> @@ -131,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do      end)    end -  def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do +  def validate_context_match(cng, %{"context" => object_context}) do      cng      |> validate_change(:context, fn :context, context ->        if context == object_context do @@ -142,5 +144,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do      end)    end -  def validate_context_match(cng, _), do: cng +  def validate_addressing_match(cng, object) do +    [:to, :cc, :bcc, :bto] +    |> Enum.reduce(cng, fn field, cng -> +      object_data = object[to_string(field)] + +      validate_change(cng, field, fn field, data -> +        if data == object_data do +          [] +        else +          [{field, "field doesn't match with object (#{inspect(object_data)})"}] +        end +      end) +    end) +  end  end diff --git a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex deleted file mode 100644 index a85a0298c..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex +++ /dev/null @@ -1,29 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do -  use Ecto.Schema - -  alias Pleroma.EctoType.ActivityPub.ObjectValidators -  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator - -  import Ecto.Changeset - -  @primary_key false - -  embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:actor, ObjectValidators.ObjectID) -    field(:type, :string) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    embeds_one(:object, NoteValidator) -  end - -  def cast_data(data) do -    cast(%__MODULE__{}, data, __schema__(:fields)) -  end -end diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 7da67bf16..f0c99356e 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do    alias Pleroma.Activity    alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.User    import Ecto.Changeset    import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -14,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end +      field(:deleted_activity_id, ObjectValidators.ObjectID) -    field(:object, ObjectValidators.ObjectID)    end    def cast_data(data) do @@ -57,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do      cng      |> validate_required([:id, :type, :actor, :to, :cc, :object])      |> validate_inclusion(:type, ["Delete"]) -    |> validate_actor_presence() +    |> validate_delete_actor(:actor)      |> validate_modification_rights()      |> validate_object_or_user_presence(allowed_types: @deletable_types)      |> add_deleted_activity_id() @@ -72,4 +75,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do      |> cast_data      |> validate_data    end + +  defp validate_delete_actor(cng, field_name) do +    validate_change(cng, field_name, fn field_name, actor -> +      case User.get_cached_by_ap_id(actor) do +        %User{} -> [] +        _ -> [{field_name, "can't find user"}] +      end +    end) +  end  end diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index ec7566515..9eaaf8319 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -5,8 +5,8 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do    use Ecto.Schema -  alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    import Ecto.Changeset    import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -14,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end +      field(:context, :string)      field(:content, :string) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: [])    end    def cast_and_validate(data) do @@ -31,6 +33,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do    end    def cast_data(data) do +    data = +      data +      |> fix() +      %__MODULE__{}      |> changeset(data)    end @@ -38,28 +44,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do    def changeset(struct, data) do      struct      |> cast(data, __schema__(:fields)) -    |> fix_after_cast()    end -  def fix_after_cast(cng) do -    cng -    |> fix_context() -  end - -  def fix_context(cng) do -    object = get_field(cng, :object) +  defp fix(data) do +    data = +      data +      |> CommonFixes.fix_actor() +      |> CommonFixes.fix_activity_addressing() -    with nil <- get_field(cng, :context), -         %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do -      cng -      |> put_change(:context, context) +    with %Object{} = object <- Object.normalize(data["object"]) do +      data +      |> CommonFixes.fix_activity_context(object) +      |> CommonFixes.fix_object_action_recipients(object)      else -      _ -> -        cng +      _ -> data      end    end -  def validate_emoji(cng) do +  defp validate_emoji(cng) do      content = get_field(cng, :content)      if Pleroma.Emoji.is_unicode_emoji?(content) do diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index d42458ef5..34a3031c3 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -5,11 +5,8 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do    use Ecto.Schema -  alias Pleroma.EctoType.ActivityPub.ObjectValidators -  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations -  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator    alias Pleroma.Web.ActivityPub.Transmogrifier    import Ecto.Changeset @@ -19,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do    # Extends from NoteValidator    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    embeds_many(:tag, TagValidator) -    field(:type, :string) - -    field(:name, :string) -    field(:summary, :string) -    field(:content, :string) - -    field(:context, :string) -    # short identifier for PleromaFE to group statuses by context -    field(:context_id, :integer) - -    # TODO: Remove actor on objects -    field(:actor, ObjectValidators.ObjectID) - -    field(:attributedTo, ObjectValidators.ObjectID) -    field(:published, ObjectValidators.DateTime) -    field(:emoji, ObjectValidators.Emoji, default: %{}) -    field(:sensitive, :boolean, default: false) -    embeds_many(:attachment, AttachmentValidator) -    field(:replies_count, :integer, default: 0) -    field(:like_count, :integer, default: 0) -    field(:announcement_count, :integer, default: 0) -    field(:inReplyTo, ObjectValidators.ObjectID) -    field(:url, ObjectValidators.Uri) - -    field(:likes, {:array, ObjectValidators.ObjectID}, default: []) -    field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        object_fields() +        status_object_fields() +      end +    end    end    def cast_and_apply(data) do @@ -72,8 +45,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do    defp fix(data) do      data -    |> CommonFixes.fix_defaults() -    |> CommonFixes.fix_attribution() +    |> CommonFixes.fix_actor() +    |> CommonFixes.fix_object_defaults()      |> Transmogrifier.fix_emoji()    end diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex index 239cee5e7..c061ebba9 100644 --- a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex @@ -5,20 +5,20 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do    use Ecto.Schema -  alias Pleroma.EctoType.ActivityPub.ObjectValidators -    import Ecto.Changeset    import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:object, ObjectValidators.ObjectID) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end +      field(:state, :string, default: "pending")    end diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 509da507b..35e000d72 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -5,8 +5,8 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do    use Ecto.Schema -  alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.Utils    import Ecto.Changeset @@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end +      field(:context, :string) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: [])    end    def cast_and_validate(data) do @@ -31,6 +33,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do    end    def cast_data(data) do +    data = +      data +      |> fix() +      %__MODULE__{}      |> changeset(data)    end @@ -38,41 +44,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do    def changeset(struct, data) do      struct      |> cast(data, __schema__(:fields)) -    |> fix_after_cast() -  end - -  def fix_after_cast(cng) do -    cng -    |> fix_recipients() -    |> fix_context() -  end - -  def fix_context(cng) do -    object = get_field(cng, :object) - -    with nil <- get_field(cng, :context), -         %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do -      cng -      |> put_change(:context, context) -    else -      _ -> -        cng -    end    end -  def fix_recipients(cng) do -    to = get_field(cng, :to) -    cc = get_field(cng, :cc) -    object = get_field(cng, :object) +  defp fix(data) do +    data = +      data +      |> CommonFixes.fix_actor() +      |> CommonFixes.fix_activity_addressing() -    with {[], []} <- {to, cc}, -         %Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object), -         {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do -      cng -      |> put_change(:to, [actor]) +    with %Object{} = object <- Object.normalize(data["object"]) do +      data +      |> CommonFixes.fix_activity_context(object) +      |> CommonFixes.fix_object_action_recipients(object)      else -      _ -> -        cng +      _ -> data      end    end @@ -85,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do      |> validate_existing_like()    end -  def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do +  defp validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do      if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do        cng        |> add_error(:actor, "already liked this object") @@ -95,5 +80,5 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do      end    end -  def validate_existing_like(cng), do: cng +  defp validate_existing_like(cng), do: cng  end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 7012e2e1d..bdddfdaeb 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do    use Ecto.Schema    alias Pleroma.EctoType.ActivityPub.ObjectValidators -  alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes    alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations    alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator -  alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator    alias Pleroma.Web.ActivityPub.Transmogrifier    import Ecto.Changeset @@ -20,35 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do    # Extends from NoteValidator    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) -    field(:bto, ObjectValidators.Recipients, default: []) -    field(:bcc, ObjectValidators.Recipients, default: []) -    embeds_many(:tag, TagValidator) -    field(:type, :string) -    field(:content, :string) -    field(:context, :string) - -    # TODO: Remove actor on objects -    field(:actor, ObjectValidators.ObjectID) - -    field(:attributedTo, ObjectValidators.ObjectID) -    field(:summary, :string) -    field(:published, ObjectValidators.DateTime) -    field(:emoji, ObjectValidators.Emoji, default: %{}) -    field(:sensitive, :boolean, default: false) -    embeds_many(:attachment, AttachmentValidator) -    field(:replies_count, :integer, default: 0) -    field(:like_count, :integer, default: 0) -    field(:announcement_count, :integer, default: 0) -    field(:inReplyTo, ObjectValidators.ObjectID) -    field(:url, ObjectValidators.Uri) -    # short identifier for PleromaFE to group statuses by context -    field(:context_id, :integer) - -    field(:likes, {:array, ObjectValidators.ObjectID}, default: []) -    field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        object_fields() +        status_object_fields() +      end +    end      field(:closed, ObjectValidators.DateTime)      field(:voters, {:array, ObjectValidators.ObjectID}, default: []) @@ -83,8 +60,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do    defp fix(data) do      data -    |> CommonFixes.fix_defaults() -    |> CommonFixes.fix_attribution() +    |> CommonFixes.fix_actor() +    |> CommonFixes.fix_object_defaults()      |> Transmogrifier.fix_emoji()      |> fix_closed()    end diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex index e8af60ffa..703643e3f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do    use Ecto.Schema    alias Pleroma.Activity -  alias Pleroma.EctoType.ActivityPub.ObjectValidators +  alias Pleroma.User    import Ecto.Changeset    import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -14,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) -    field(:object, ObjectValidators.ObjectID) -    field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: []) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +        activity_fields() +      end +    end    end    def cast_and_validate(data) do @@ -42,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do      data_cng      |> validate_inclusion(:type, ["Undo"])      |> validate_required([:id, :type, :object, :actor, :to, :cc]) -    |> validate_actor_presence() +    |> validate_undo_actor(:actor)      |> validate_object_presence()      |> validate_undo_rights()    end @@ -59,4 +60,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do        _ -> cng      end    end + +  defp validate_undo_actor(cng, field_name) do +    validate_change(cng, field_name, fn field_name, actor -> +      case User.get_cached_by_ap_id(actor) do +        %User{} -> [] +        _ -> [{field_name, "can't find user"}] +      end +    end) +  end  end diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex index 6bb1dc7fa..a1fae47f5 100644 --- a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex @@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do    @primary_key false    embedded_schema do -    field(:id, ObjectValidators.ObjectID, primary_key: true) -    field(:type, :string) +    quote do +      unquote do +        import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields +        message_fields() +      end +    end +      field(:actor, ObjectValidators.ObjectID) -    field(:to, ObjectValidators.Recipients, default: []) -    field(:cc, ObjectValidators.Recipients, default: [])      # In this case, we save the full object in this activity instead of just a      # reference, so we can always see what was actually changed by this.      field(:object, :map) diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index a0f2e0312..0d6e8aad2 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -15,19 +15,19 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.Federator -  @side_effects Config.get([:pipeline, :side_effects], SideEffects) -  @federator Config.get([:pipeline, :federator], Federator) -  @object_validator Config.get([:pipeline, :object_validator], ObjectValidator) -  @mrf Config.get([:pipeline, :mrf], MRF) -  @activity_pub Config.get([:pipeline, :activity_pub], ActivityPub) -  @config Config.get([:pipeline, :config], Config) +  defp side_effects, do: Config.get([:pipeline, :side_effects], SideEffects) +  defp federator, do: Config.get([:pipeline, :federator], Federator) +  defp object_validator, do: Config.get([:pipeline, :object_validator], ObjectValidator) +  defp mrf, do: Config.get([:pipeline, :mrf], MRF) +  defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub) +  defp config, do: Config.get([:pipeline, :config], Config)    @spec common_pipeline(map(), keyword()) ::            {:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}    def common_pipeline(object, meta) do      case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do        {:ok, {:ok, activity, meta}} -> -        @side_effects.handle_after_transaction(meta) +        side_effects().handle_after_transaction(meta)          {:ok, activity, meta}        {:ok, value} -> @@ -44,10 +44,10 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do    def do_common_pipeline(%{__struct__: _}, _meta), do: {:error, :is_struct}    def do_common_pipeline(message, meta) do -    with {_, {:ok, message, meta}} <- {:validate, @object_validator.validate(message, meta)}, -         {_, {:ok, message, meta}} <- {:mrf, @mrf.pipeline_filter(message, meta)}, -         {_, {:ok, message, meta}} <- {:persist, @activity_pub.persist(message, meta)}, -         {_, {:ok, message, meta}} <- {:side_effects, @side_effects.handle(message, meta)}, +    with {_, {:ok, message, meta}} <- {:validate, object_validator().validate(message, meta)}, +         {_, {:ok, message, meta}} <- {:mrf, mrf().pipeline_filter(message, meta)}, +         {_, {:ok, message, meta}} <- {:persist, activity_pub().persist(message, meta)}, +         {_, {:ok, message, meta}} <- {:side_effects, side_effects().handle(message, meta)},           {_, {:ok, _}} <- {:federation, maybe_federate(message, meta)} do        {:ok, message, meta}      else @@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do    defp maybe_federate(%Activity{} = activity, meta) do      with {:ok, local} <- Keyword.fetch(meta, :local) do -      do_not_federate = meta[:do_not_federate] || !@config.get([:instance, :federating]) +      do_not_federate = meta[:do_not_federate] || !config().get([:instance, :federating])        if !do_not_federate and local and not Visibility.is_local_public?(activity) do          activity = @@ -70,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do              activity            end -        @federator.publish(activity) +        federator().publish(activity)          {:ok, :federated}        else          {:ok, :not_federated} diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index b12b2fc24..4f29a4411 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -112,6 +112,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do        quarantined_instances =          Config.get([:instance, :quarantined_instances], []) +        |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()          |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()        !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) @@ -272,7 +273,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do        },        %{          "rel" => "http://ostatus.org/schema/1.0/subscribe", -        "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" +        "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"        }      ]    end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5fe143c2b..701181a14 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    collection, and so on.    """    alias Pleroma.Activity -  alias Pleroma.Activity.Ir.Topics    alias Pleroma.Chat    alias Pleroma.Chat.MessageReference    alias Pleroma.FollowingRelationship @@ -24,15 +23,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.Push    alias Pleroma.Web.Streamer +  alias Pleroma.Workers.PollWorker    require Logger    @cachex Pleroma.Config.get([:cachex, :provider], Cachex) -  @ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub)    @logger Pleroma.Config.get([:side_effects, :logger], Logger)    @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling +  defp ap_streamer, do: Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub) +    @impl true    def handle(object, meta \\ []) @@ -194,7 +195,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    # - Set up notifications    @impl true    def handle(%{data: %{"type" => "Create"}} = activity, meta) do -    with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta), +    with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),           %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do        {:ok, notifications} = Notification.create_notifications(activity, do_send: false)        {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) @@ -203,6 +204,19 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do          Object.increase_replies_count(in_reply_to)        end +      reply_depth = (meta[:depth] || 0) + 1 + +      # FIXME: Force inReplyTo to replies +      if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and +           object.data["replies"] != nil do +        for reply_id <- object.data["replies"] do +          Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ +            "id" => reply_id, +            "depth" => reply_depth +          }) +        end +      end +        ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->          Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)        end) @@ -211,6 +225,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do          meta          |> add_notifications(notifications) +      ap_streamer().stream_out(activity) +        {:ok, activity, meta}      else        e -> Repo.rollback(e) @@ -231,9 +247,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      if !User.is_internal_user?(user) do        Notification.create_notifications(object) -      object -      |> Topics.get_activity_topics() -      |> Streamer.stream(object) +      ap_streamer().stream_out(object)      end      {:ok, object, meta} @@ -289,8 +303,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do              MessageReference.delete_for_object(deleted_object) -            @ap_streamer.stream_out(object) -            @ap_streamer.stream_out_participations(deleted_object, user) +            ap_streamer().stream_out(object) +            ap_streamer().stream_out_participations(deleted_object, user)              :ok            else              {:actor, _} -> @@ -375,7 +389,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      {:ok, object, meta}    end -  def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do +  def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do      with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do        actor = User.get_cached_by_ap_id(object.data["actor"])        recipient = User.get_cached_by_ap_id(hd(object.data["to"])) @@ -410,7 +424,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      end    end -  def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do +  def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do +    with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do +      PollWorker.schedule_poll_end(activity) +      {:ok, object, meta} +    end +  end + +  def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do      with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do        Object.increase_vote_count(          object.data["inReplyTo"], @@ -422,15 +443,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      end    end -  def handle_object_creation(%{"type" => objtype} = object, meta) -      when objtype in ~w[Audio Video Question Event Article] do +  def handle_object_creation(%{"type" => objtype} = object, _activity, meta) +      when objtype in ~w[Audio Video Event Article Note Page] do      with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do        {:ok, object, meta}      end    end    # Nothing to do -  def handle_object_creation(object, meta) do +  def handle_object_creation(object, _activity, meta) do      {:ok, object, meta}    end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index d27d0bed4..142af1a13 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -43,7 +43,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> fix_content_map()      |> fix_addressing()      |> fix_summary() -    |> fix_type(options)    end    def fix_summary(%{"summary" => nil} = object) do @@ -72,17 +71,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      end    end -  def fix_explicit_addressing( -        %{"to" => to, "cc" => cc} = object, -        explicit_mentions, -        follower_collection -      ) do -    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end) +  # if directMessage flag is set to true, leave the addressing alone +  def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection), +    do: object +  def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do +    explicit_mentions = +      Utils.determine_explicit_mentions(object) ++ +        [Pleroma.Constants.as_public(), follower_collection] + +    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)      explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)      final_cc =        (cc ++ explicit_cc) +      |> Enum.filter(& &1)        |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)        |> Enum.uniq() @@ -91,29 +94,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> Map.put("cc", final_cc)    end -  def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object - -  # if directMessage flag is set to true, leave the addressing alone -  def fix_explicit_addressing(%{"directMessage" => true} = object), do: object - -  def fix_explicit_addressing(object) do -    explicit_mentions = Utils.determine_explicit_mentions(object) - -    %User{follower_address: follower_collection} = -      object -      |> Containment.get_actor() -      |> User.get_cached_by_ap_id() - -    explicit_mentions = -      explicit_mentions ++ -        [ -          Pleroma.Constants.as_public(), -          follower_collection -        ] - -    fix_explicit_addressing(object, explicit_mentions, follower_collection) -  end -    # if as:Public is addressed, then make sure the followers collection is also addressed    # so that the activities will be delivered to local users.    def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do @@ -137,19 +117,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      end    end -  def fix_implicit_addressing(object, _), do: object -    def fix_addressing(object) do -    {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"]) -    followers_collection = User.ap_followers(user) +    {:ok, %User{follower_address: follower_collection}} = +      object +      |> Containment.get_actor() +      |> User.get_or_fetch_by_ap_id()      object      |> fix_addressing_list("to")      |> fix_addressing_list("cc")      |> fix_addressing_list("bto")      |> fix_addressing_list("bcc") -    |> fix_explicit_addressing() -    |> fix_implicit_addressing(followers_collection) +    |> fix_explicit_addressing(follower_collection) +    |> fix_implicit_addressing(follower_collection)    end    def fix_actor(%{"attributedTo" => actor} = object) do @@ -223,10 +203,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          media_type =            cond do -            is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"] -            MIME.valid?(data["mediaType"]) -> data["mediaType"] -            MIME.valid?(data["mimeType"]) -> data["mimeType"] -            true -> nil +            is_map(url) && MIME.extensions(url["mediaType"]) != [] -> +              url["mediaType"] + +            is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] -> +              data["mediaType"] + +            is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] -> +              data["mimeType"] + +            true -> +              nil            end          href = @@ -244,6 +231,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do                "type" => Map.get(url || %{}, "type", "Link")              }              |> Maps.put_if_present("mediaType", media_type) +            |> Maps.put_if_present("width", (url || %{})["width"] || data["width"]) +            |> Maps.put_if_present("height", (url || %{})["height"] || data["height"])            %{              "url" => [attachment_url], @@ -340,19 +329,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def fix_content_map(object), do: object -  def fix_type(object, options \\ []) +  defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options) +       when is_binary(reply_id) do +    options = Keyword.put(options, :fetch, true) -  def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) -      when is_binary(reply_id) do -    with true <- Federator.allowed_thread_distance?(options[:depth]), -         {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do +    with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do        Map.put(object, "type", "Answer")      else        _ -> object      end    end -  def fix_type(object, _), do: object +  defp fix_type(object, _options), do: object    # Reduce the object list to find the reported user.    defp get_reported(objects) do @@ -365,29 +353,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      end)    end -  # Compatibility wrapper for Mastodon votes -  defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do -    handle_incoming(data) -  end - -  defp handle_create(%{"object" => object} = data, user) do -    %{ -      to: data["to"], -      object: object, -      actor: user, -      context: object["context"], -      local: false, -      published: data["published"], -      additional: -        Map.take(data, [ -          "cc", -          "directMessage", -          "id" -        ]) -    } -    |> ActivityPub.create() -  end -    def handle_incoming(data, options \\ [])    # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them @@ -419,44 +384,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,      do: :error -  # TODO: validate those with a Ecto scheme -  # - tags -  # - emoji -  def handle_incoming( -        %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, -        options -      ) -      when objtype in ~w{Note Page} do -    actor = Containment.get_actor(data) - -    with nil <- Activity.get_create_by_object_ap_id(object["id"]), -         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do -      data = -        data -        |> Map.put("object", fix_object(object, options)) -        |> Map.put("actor", actor) -        |> fix_addressing() - -      with {:ok, created_activity} <- handle_create(data, user) do -        reply_depth = (options[:depth] || 0) + 1 - -        if Federator.allowed_thread_distance?(reply_depth) do -          for reply_id <- replies(object) do -            Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ -              "id" => reply_id, -              "depth" => reply_depth -            }) -          end -        end - -        {:ok, created_activity} -      end -    else -      %Activity{} = activity -> {:ok, activity} -      _e -> :error -    end -  end -    def handle_incoming(          %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,          options @@ -518,14 +445,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(          %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, -        _options +        options        ) -      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do -    data = Map.put(data, "object", strip_internal_fields(data["object"])) +      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do +    fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) + +    object = +      data["object"] +      |> strip_internal_fields() +      |> fix_type(fetch_options) +      |> fix_in_reply_to(fetch_options) + +    data = Map.put(data, "object", object) +    options = Keyword.put(options, :local, false)      with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),           nil <- Activity.get_create_by_object_ap_id(obj_id), -         {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do +         {:ok, activity, _} <- Pipeline.common_pipeline(data, options) do        {:ok, activity}      else        %Activity{} = activity -> {:ok, activity} @@ -949,7 +885,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        object        |> Map.get("attachment", [])        |> Enum.map(fn data -> -        [%{"mediaType" => media_type, "href" => href} | _] = data["url"] +        [%{"mediaType" => media_type, "href" => href} = url | _] = data["url"]          %{            "url" => href, @@ -957,6 +893,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            "name" => data["name"],            "type" => "Document"          } +        |> Maps.put_if_present("width", url["width"]) +        |> Maps.put_if_present("height", url["height"]) +        |> Maps.put_if_present("blurhash", data["blurhash"])        end)      Map.put(object, "attachment", attachments) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index a4dc469dc..1df53f79a 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.Web    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.AdminAPI.AccountView @@ -38,6 +37,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do    @supported_report_states ~w(open closed resolved)    @valid_visibilities ~w(public unlisted private direct) +  def as_local_public, do: Endpoint.url() <> "/#Public" +    # Some implementations send the actor URI as the actor field, others send the entire actor object,    # so figure out what the actor's URI is based on what we have.    def get_ap_id(%{"id" => id} = _), do: id @@ -96,8 +97,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do          !label_in_collection?(ap_id, params["cc"])      if need_splice? do -      cc_list = extract_list(params["cc"]) -      Map.put(params, "cc", [ap_id | cc_list]) +      cc = [ap_id | extract_list(params["cc"])] + +      params +      |> Map.put("cc", cc) +      |> Maps.safe_put_in(["object", "cc"], cc)      else        params      end @@ -107,7 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do      %{        "@context" => [          "https://www.w3.org/ns/activitystreams", -        "#{Web.base_url()}/schemas/litepub-0.1.jsonld", +        "#{Endpoint.url()}/schemas/litepub-0.1.jsonld",          %{            "@language" => "und"          } @@ -132,7 +136,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do    end    def generate_id(type) do -    "#{Web.base_url()}/#{type}/#{UUID.generate()}" +    "#{Endpoint.url()}/#{type}/#{UUID.generate()}"    end    def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 462f3b4a7..344da19d3 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -261,7 +261,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do      %{        "id" => featured_address,        "type" => "OrderedCollection", -      "orderedItems" => objects +      "orderedItems" => objects, +      "totalItems" => length(objects)      }      |> Map.merge(Utils.make_json_ld_header())    end diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 00234c0b0..986fa3a08 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do    def is_public?(data) do      Utils.label_in_message?(Pleroma.Constants.as_public(), data) or -      Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) +      Utils.label_in_message?(Utils.as_local_public(), data)    end    def is_local_public?(%Object{data: data}), do: is_local_public?(data)    def is_local_public?(%Activity{data: data}), do: is_local_public?(data)    def is_local_public?(data) do -    Utils.label_in_message?(Pleroma.Constants.as_local_public(), data) and +    Utils.label_in_message?(Utils.as_local_public(), data) and        not Utils.label_in_message?(Pleroma.Constants.as_public(), data)    end @@ -57,6 +57,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do    def is_list?(_), do: false    @spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean() +  def visible_for_user?(%Object{data: %{"type" => "Tombstone"}}, _), do: false    def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true    def visible_for_user?(%Object{data: %{"actor" => ap_id}}, %User{ap_id: ap_id}), do: true    def visible_for_user?(nil, _), do: false @@ -127,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do        Pleroma.Constants.as_public() in cc ->          "unlisted" -      Pleroma.Constants.as_local_public() in to -> +      Utils.as_local_public() in to ->          "local"        # this should use the sql for the object's activity diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 839ac1a8d..50aa294f0 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug,      %{scopes: ["admin:read:statuses"]} -    when action in [:list_user_statuses, :list_instance_statuses] +    when action in [:list_user_statuses]    )    plug( @@ -81,24 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    action_fallback(AdminAPI.FallbackController) -  def list_instance_statuses(conn, %{"instance" => instance} = params) do -    with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true -    {page, page_size} = page_params(params) - -    result = -      ActivityPub.fetch_statuses(nil, %{ -        instance: instance, -        limit: page_size, -        offset: (page - 1) * page_size, -        exclude_reblogs: not with_reblogs, -        total: true -      }) - -    conn -    |> put_view(AdminAPI.StatusView) -    |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity}) -  end -    def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do      with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true      godmode = params["godmode"] == "true" || params["godmode"] == true diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex index 722f51bd2..442e6a5a0 100644 --- a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex @@ -35,6 +35,12 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do    end    defp installed do -    File.ls!(Pleroma.Frontend.dir()) +    frontend_directory = Pleroma.Frontend.dir() + +    if File.exists?(frontend_directory) do +      File.ls!(frontend_directory) +    else +      [] +    end    end  end diff --git a/lib/pleroma/web/admin_api/controllers/instance_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_controller.ex new file mode 100644 index 000000000..00857983f --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/instance_controller.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceController do +  use Pleroma.Web, :controller + +  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3] + +  alias Pleroma.Instances.Instance +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.AdminAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug + +  require Logger + +  @default_page_size 50 + +  plug( +    OAuthScopesPlug, +    %{scopes: ["admin:read:statuses"]} +    when action in [:list_statuses] +  ) + +  plug( +    OAuthScopesPlug, +    %{scopes: ["admin:write:accounts", "admin:write:statuses"]} +    when action in [:delete] +  ) + +  action_fallback(AdminAPI.FallbackController) + +  def list_statuses(conn, %{"instance" => instance} = params) do +    with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true +    {page, page_size} = page_params(params) + +    result = +      ActivityPub.fetch_statuses(nil, %{ +        instance: instance, +        limit: page_size, +        offset: (page - 1) * page_size, +        exclude_reblogs: not with_reblogs, +        total: true +      }) + +    conn +    |> put_view(AdminAPI.StatusView) +    |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity}) +  end + +  def delete(conn, %{"instance" => instance}) do +    with {:ok, _job} <- Instance.delete_users_and_activities(instance) do +      json(conn, instance) +    end +  end + +  defp page_params(params) do +    { +      fetch_integer_param(params, "page", 1), +      fetch_integer_param(params, "page_size", @default_page_size) +    } +  end +end diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex index 005fe67e2..51b17d392 100644 --- a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do    require Logger    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:put_view, Pleroma.Web.MastodonAPI.AppView)    plug(      OAuthScopesPlug, diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex index d3e4c18a3..637a0e702 100644 --- a/lib/pleroma/web/admin_api/controllers/user_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex @@ -45,8 +45,6 @@ defmodule Pleroma.Web.AdminAPI.UserController do      when action in [:follow, :unfollow]    ) -  plug(:put_view, Pleroma.Web.AdminAPI.AccountView) -    action_fallback(AdminAPI.FallbackController)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex index 259068f04..345bc1e87 100644 --- a/lib/pleroma/web/admin_api/report.ex +++ b/lib/pleroma/web/admin_api/report.ex @@ -13,7 +13,9 @@ defmodule Pleroma.Web.AdminAPI.Report do      account = User.get_cached_by_ap_id(account_ap_id)      statuses = -      Enum.map(status_ap_ids, fn +      status_ap_ids +      |> Enum.reject(&is_nil(&1)) +      |> Enum.map(fn          act when is_map(act) -> Activity.get_by_ap_id_with_object(act["id"])          act when is_binary(act) -> Activity.get_by_ap_id_with_object(act)        end) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index eeeebdf4e..da38fab56 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -10,12 +10,6 @@ defmodule Pleroma.Web.AdminAPI.Search do    @page_size 50 -  defmacro not_empty_string(string) do -    quote do -      is_binary(unquote(string)) and unquote(string) != "" -    end -  end -    @spec user(map()) :: {:ok, [User.t()], pos_integer()}    def user(params \\ %{}) do      query = @@ -23,7 +17,7 @@ defmodule Pleroma.Web.AdminAPI.Search do        |> Map.drop([:page, :page_size])        |> Map.put(:invisible, false)        |> User.Query.build() -      |> order_by([u], u.nickname) +      |> order_by(desc: :id)      paginated_query =        User.Query.paginate(query, params[:page] || 1, params[:page_size] || @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 e053a9b67..fae0c07f0 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do    alias Pleroma.User    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.AdminAPI.AccountView +  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI    alias Pleroma.Web.MediaProxy @@ -81,7 +82,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do        "is_approved" => user.is_approved,        "url" => user.uri || user.ap_id,        "registration_reason" => user.registration_reason, -      "actor_type" => user.actor_type +      "actor_type" => user.actor_type, +      "created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at)      }    end diff --git a/lib/pleroma/web/admin_api/views/o_auth_app_view.ex b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex new file mode 100644 index 000000000..af046f343 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.OAuthAppView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.AppView.render(view, opts) +end diff --git a/lib/pleroma/web/admin_api/views/user_view.ex b/lib/pleroma/web/admin_api/views/user_view.ex new file mode 100644 index 000000000..e91265ffe --- /dev/null +++ b/lib/pleroma/web/admin_api/views/user_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.UserView do +  use Pleroma.Web, :view +  alias Pleroma.Web.AdminAPI + +  def render(view, opts), do: AdminAPI.AccountView.render(view, opts) +end diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex index 85aa14869..451b6510f 100644 --- a/lib/pleroma/web/api_spec/operations/media_operation.ex +++ b/lib/pleroma/web/api_spec/operations/media_operation.ex @@ -24,6 +24,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do        requestBody: Helpers.request_body("Parameters", create_request()),        responses: %{          200 => Operation.response("Media", "application/json", Attachment), +        400 => Operation.response("Media", "application/json", ApiError),          401 => Operation.response("Media", "application/json", ApiError),          422 => Operation.response("Media", "application/json", ApiError)        } @@ -105,6 +106,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do        responses: %{          200 => Operation.response("Media", "application/json", Attachment),          401 => Operation.response("Media", "application/json", ApiError), +        403 => Operation.response("Media", "application/json", ApiError),          422 => Operation.response("Media", "application/json", ApiError)        }      } @@ -120,6 +122,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do        requestBody: Helpers.request_body("Parameters", create_request()),        responses: %{          202 => Operation.response("Media", "application/json", Attachment), +        400 => Operation.response("Media", "application/json", ApiError),          422 => Operation.response("Media", "application/json", ApiError),          500 => Operation.response("Media", "application/json", ApiError)        } diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index ec88eabe1..e4ce42f1c 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -195,7 +195,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do          "pleroma:chat_mention",          "pleroma:report",          "move", -        "follow_request" +        "follow_request", +        "poll"        ],        description: """        The type of event that resulted in the notification. diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex index cae18c758..24d792916 100644 --- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex +++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -115,7 +115,8 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do        ],        operationId: "TimelineController.hashtag",        responses: %{ -        200 => Operation.response("Array of Status", "application/json", array_of_statuses()) +        200 => Operation.response("Array of Status", "application/json", array_of_statuses()), +        401 => Operation.response("Error", "application/json", ApiError)        }      }    end diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex new file mode 100644 index 000000000..ebcfd3be2 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex @@ -0,0 +1,240 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.ApiError +  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + +  import Pleroma.Web.ApiSpec.Helpers + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def emoji_operation do +    %Operation{ +      tags: ["Emojis"], +      summary: "List all custom emojis", +      operationId: "UtilController.emoji", +      parameters: [], +      responses: %{ +        200 => +          Operation.response("List", "application/json", %Schema{ +            type: :object, +            additionalProperties: %Schema{ +              type: :object, +              properties: %{ +                image_url: %Schema{type: :string}, +                tags: %Schema{type: :array, items: %Schema{type: :string}} +              } +            }, +            example: %{ +              "firefox" => %{ +                "image_url" => "/emoji/firefox.png", +                "tag" => ["Fun"] +              } +            } +          }) +      } +    } +  end + +  def frontend_configurations_operation do +    %Operation{ +      tags: ["Configuration"], +      summary: "Dump frontend configurations", +      operationId: "UtilController.frontend_configurations", +      parameters: [], +      responses: %{ +        200 => +          Operation.response("List", "application/json", %Schema{ +            type: :object, +            additionalProperties: %Schema{type: :object} +          }) +      } +    } +  end + +  def change_password_operation do +    %Operation{ +      tags: ["Account credentials"], +      summary: "Change account password", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.change_password", +      requestBody: request_body("Parameters", change_password_request(), required: true), +      responses: %{ +        200 => +          Operation.response("Success", "application/json", %Schema{ +            type: :object, +            properties: %{status: %Schema{type: :string, example: "success"}} +          }), +        400 => Operation.response("Error", "application/json", ApiError), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  defp change_password_request do +    %Schema{ +      title: "ChangePasswordRequest", +      description: "POST body for changing the account's passowrd", +      type: :object, +      required: [:password, :new_password, :new_password_confirmation], +      properties: %{ +        password: %Schema{type: :string, description: "Current password"}, +        new_password: %Schema{type: :string, description: "New password"}, +        new_password_confirmation: %Schema{ +          type: :string, +          description: "New password, confirmation" +        } +      } +    } +  end + +  def change_email_operation do +    %Operation{ +      tags: ["Account credentials"], +      summary: "Change account email", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.change_email", +      requestBody: request_body("Parameters", change_email_request(), required: true), +      responses: %{ +        200 => +          Operation.response("Success", "application/json", %Schema{ +            type: :object, +            properties: %{status: %Schema{type: :string, example: "success"}} +          }), +        400 => Operation.response("Error", "application/json", ApiError), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  defp change_email_request do +    %Schema{ +      title: "ChangeEmailRequest", +      description: "POST body for changing the account's email", +      type: :object, +      required: [:email, :password], +      properties: %{ +        email: %Schema{ +          type: :string, +          description: "New email. Set to blank to remove the user's email." +        }, +        password: %Schema{type: :string, description: "Current password"} +      } +    } +  end + +  def update_notificaton_settings_operation do +    %Operation{ +      tags: ["Accounts"], +      summary: "Update Notification Settings", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.update_notificaton_settings", +      parameters: [ +        Operation.parameter( +          :block_from_strangers, +          :query, +          BooleanLike, +          "blocks notifications from accounts you do not follow" +        ), +        Operation.parameter( +          :hide_notification_contents, +          :query, +          BooleanLike, +          "removes the contents of a message from the push notification" +        ) +      ], +      requestBody: nil, +      responses: %{ +        200 => +          Operation.response("Success", "application/json", %Schema{ +            type: :object, +            properties: %{status: %Schema{type: :string, example: "success"}} +          }), +        400 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def disable_account_operation do +    %Operation{ +      tags: ["Account credentials"], +      summary: "Disable Account", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.disable_account", +      parameters: [ +        Operation.parameter(:password, :query, :string, "Password") +      ], +      responses: %{ +        200 => +          Operation.response("Success", "application/json", %Schema{ +            type: :object, +            properties: %{status: %Schema{type: :string, example: "success"}} +          }), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def delete_account_operation do +    %Operation{ +      tags: ["Account credentials"], +      summary: "Delete Account", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.delete_account", +      parameters: [ +        Operation.parameter(:password, :query, :string, "Password") +      ], +      responses: %{ +        200 => +          Operation.response("Success", "application/json", %Schema{ +            type: :object, +            properties: %{status: %Schema{type: :string, example: "success"}} +          }), +        403 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def captcha_operation do +    %Operation{ +      summary: "Get a captcha", +      operationId: "UtilController.captcha", +      parameters: [], +      responses: %{ +        200 => Operation.response("Success", "application/json", %Schema{type: :object}) +      } +    } +  end + +  def healthcheck_operation do +    %Operation{ +      tags: ["Accounts"], +      summary: "Quick status check on the instance", +      security: [%{"oAuth" => ["write:accounts"]}], +      operationId: "UtilController.healthcheck", +      parameters: [], +      responses: %{ +        200 => Operation.response("Healthy", "application/json", %Schema{type: :object}), +        503 => +          Operation.response("Disabled or Unhealthy", "application/json", %Schema{type: :object}) +      } +    } +  end + +  def remote_subscribe_operation do +    %Operation{ +      tags: ["Accounts"], +      summary: "Remote Subscribe", +      operationId: "UtilController.remote_subscribe", +      parameters: [], +      responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex index 6292e2004..8df19f1fc 100644 --- a/lib/pleroma/web/api_spec/operations/user_import_operation.ex +++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation do        requestBody: request_body("Parameters", import_request(), required: true),        responses: %{          200 => ok_response(), +        403 => Operation.response("Error", "application/json", ApiError),          500 => Operation.response("Error", "application/json", ApiError)        },        security: [%{"oAuth" => ["write:follow"]}] diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex index 778158f66..94c5020ca 100644 --- a/lib/pleroma/web/api_spec/schemas/boolean_like.ex +++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex @@ -34,7 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do    def cast(%Cast{value: value} = context) do      context -    |> Map.put(:value, Pleroma.Web.ControllerHelper.truthy_param?(value)) +    |> Map.put(:value, Pleroma.Web.Utils.Params.truthy_param?(value))      |> Cast.ok()    end  end diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index 84741ee11..3fe9718c4 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -3,68 +3,11 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Auth.Authenticator do -  alias Pleroma.Registration -  alias Pleroma.User - -  def implementation do -    Pleroma.Config.get( -      Pleroma.Web.Auth.Authenticator, -      Pleroma.Web.Auth.PleromaAuthenticator -    ) -  end - -  @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()} -  def get_user(plug), do: implementation().get_user(plug) - -  @callback create_from_registration(Plug.Conn.t(), Registration.t()) :: +  @callback get_user(Plug.Conn.t()) :: {:ok, user :: struct()} | {:error, any()} +  @callback create_from_registration(Plug.Conn.t(), registration :: struct()) ::                {:ok, User.t()} | {:error, any()} -  def create_from_registration(plug, registration), -    do: implementation().create_from_registration(plug, registration) - -  @callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()} -  def get_registration(plug), do: implementation().get_registration(plug) - +  @callback get_registration(Plug.Conn.t()) :: {:ok, registration :: struct()} | {:error, any()}    @callback handle_error(Plug.Conn.t(), any()) :: any() -  def handle_error(plug, error), -    do: implementation().handle_error(plug, error) -    @callback auth_template() :: String.t() | nil -  def auth_template do -    # Note: `config :pleroma, :auth_template, "..."` support is deprecated -    implementation().auth_template() || -      Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) || -      "show.html" -  end -    @callback oauth_consumer_template() :: String.t() | nil -  def oauth_consumer_template do -    implementation().oauth_consumer_template() || -      Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html") -  end - -  @doc "Gets user by nickname or email for auth." -  @spec fetch_user(String.t()) :: User.t() | nil -  def fetch_user(name) do -    User.get_by_nickname_or_email(name) -  end - -  # Gets name and password from conn -  # -  @spec fetch_credentials(Plug.Conn.t() | map()) :: -          {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials} -  def fetch_credentials(%Plug.Conn{params: params} = _), -    do: fetch_credentials(params) - -  def fetch_credentials(params) do -    case params do -      %{"authorization" => %{"name" => name, "password" => password}} -> -        {:ok, {name, password}} - -      %{"grant_type" => "password", "username" => name, "password" => password} -> -        {:ok, {name, password}} - -      _ -> -        {:error, :invalid_credentials} -    end -  end  end diff --git a/lib/pleroma/web/auth/helpers.ex b/lib/pleroma/web/auth/helpers.ex new file mode 100644 index 000000000..c566de8d4 --- /dev/null +++ b/lib/pleroma/web/auth/helpers.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.Helpers do +  alias Pleroma.User + +  @doc "Gets user by nickname or email for auth." +  @spec fetch_user(String.t()) :: User.t() | nil +  def fetch_user(name) do +    User.get_by_nickname_or_email(name) +  end + +  # Gets name and password from conn +  # +  @spec fetch_credentials(Plug.Conn.t() | map()) :: +          {:ok, {name :: any, password :: any}} | {:error, :invalid_credentials} +  def fetch_credentials(%Plug.Conn{params: params} = _), +    do: fetch_credentials(params) + +  def fetch_credentials(params) do +    case params do +      %{"authorization" => %{"name" => name, "password" => password}} -> +        {:ok, {name, password}} + +      %{"grant_type" => "password", "username" => name, "password" => password} -> +        {:ok, {name, password}} + +      _ -> +        {:error, :invalid_credentials} +    end +  end +end diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 17e08a2a6..f77e8d203 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -7,8 +7,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do    require Logger -  import Pleroma.Web.Auth.Authenticator, -    only: [fetch_credentials: 1, fetch_user: 1] +  import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]    @behaviour Pleroma.Web.Auth.Authenticator    @base Pleroma.Web.Auth.PleromaAuthenticator diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 401f23c9f..68472e75f 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -8,8 +8,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do    alias Pleroma.User    alias Pleroma.Web.Plugs.AuthenticationPlug -  import Pleroma.Web.Auth.Authenticator, -    only: [fetch_credentials: 1, fetch_user: 1] +  import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]    @behaviour Pleroma.Web.Auth.Authenticator diff --git a/lib/pleroma/web/auth/wrapper_authenticator.ex b/lib/pleroma/web/auth/wrapper_authenticator.ex new file mode 100644 index 000000000..c67082f7b --- /dev/null +++ b/lib/pleroma/web/auth/wrapper_authenticator.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.WrapperAuthenticator do +  @behaviour Pleroma.Web.Auth.Authenticator + +  defp implementation do +    Pleroma.Config.get( +      Pleroma.Web.Auth.Authenticator, +      Pleroma.Web.Auth.PleromaAuthenticator +    ) +  end + +  @impl true +  def get_user(plug), do: implementation().get_user(plug) + +  @impl true +  def create_from_registration(plug, registration), +    do: implementation().create_from_registration(plug, registration) + +  @impl true +  def get_registration(plug), do: implementation().get_registration(plug) + +  @impl true +  def handle_error(plug, error), +    do: implementation().handle_error(plug, error) + +  @impl true +  def auth_template do +    # Note: `config :pleroma, :auth_template, "..."` support is deprecated +    implementation().auth_template() || +      Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) || +      "show.html" +  end + +  @impl true +  def oauth_consumer_template do +    implementation().oauth_consumer_template() || +      Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html") +  end +end diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 1c09b6768..043206835 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.UserSocket do    ## Channels    # channel "room:*", Pleroma.Web.RoomChannel -  channel("chat:*", Pleroma.Web.ChatChannel) +  channel("chat:*", Pleroma.Web.ShoutChannel)    # Socket params are passed from the client and can    # be used to verify and authenticate a user. After @@ -22,7 +22,7 @@ defmodule Pleroma.Web.UserSocket do    # See `Phoenix.Token` documentation for examples in    # performing token verification on connect.    def connect(%{"token" => token}, socket) do -    with true <- Pleroma.Config.get([:chat, :enabled]), +    with true <- Pleroma.Config.get([:shout, :enabled]),           {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),           %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do        {:ok, assign(socket, :user_name, user.nickname)} diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 1b5f8491e..6f685cb7b 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -487,9 +487,7 @@ defmodule Pleroma.Web.CommonAPI do      else        {what, result} = error ->          Logger.warn( -          "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{ -            activity_id -          }" +          "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"          )          {:error, error} diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 80a9fa7bb..b4e3e37ae 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do    alias Pleroma.Activity    alias Pleroma.Conversation.Participation    alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils @@ -213,8 +214,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do      emoji = Map.merge(emoji, summary_emoji) +    {:ok, note_data, _meta} = Builder.note(draft) +      object = -      Utils.make_note_data(draft) +      note_data        |> Map.put("emoji", emoji)        |> Map.put("source", draft.status)        |> Map.put("generator", draft.params[:generator]) @@ -223,7 +226,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do    end    defp preview?(draft) do -    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params[:preview]) +    preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])      %__MODULE__{draft | preview?: preview?}    end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 4e6a3feb0..b6feaf32a 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -4,7 +4,6 @@  defmodule Pleroma.Web.CommonAPI.Utils do    import Pleroma.Web.Gettext -  import Pleroma.Web.ControllerHelper, only: [truthy_param?: 1]    alias Calendar.Strftime    alias Pleroma.Activity @@ -19,6 +18,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    alias Pleroma.Web.CommonAPI.ActivityDraft    alias Pleroma.Web.MediaProxy    alias Pleroma.Web.Plugs.AuthenticationPlug +  alias Pleroma.Web.Utils.Params    require Logger    require Pleroma.Constants @@ -69,7 +69,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do      to =        case visibility do          "public" -> [Pleroma.Constants.as_public() | draft.mentions] -        "local" -> [Pleroma.Constants.as_local_public() | draft.mentions] +        "local" -> [Utils.as_local_public() | draft.mentions]        end      cc = [draft.user.follower_address] @@ -160,7 +160,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do          |> DateTime.add(expires_in)          |> DateTime.to_iso8601() -      key = if truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf" +      key = if Params.truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"        poll = %{"type" => "Question", key => option_notes, "closed" => end_time}        {:ok, {poll, emoji}} @@ -203,7 +203,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do      attachment_links =        draft.params        |> Map.get("attachment_links", Config.get([:instance, :attachment_links])) -      |> truthy_param?() +      |> Params.truthy_param?()      content_type = get_content_type(draft.params[:content_type]) @@ -286,38 +286,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def format_input(text, "text/markdown", options) do      text      |> Formatter.mentions_escape(options) -    |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer}) +    |> Formatter.markdown_to_html()      |> Formatter.linkify(options)      |> Formatter.html_escape("text/html")    end -  def make_note_data(%ActivityDraft{} = draft) do -    %{ -      "type" => "Note", -      "to" => draft.to, -      "cc" => draft.cc, -      "content" => draft.content_html, -      "summary" => draft.summary, -      "sensitive" => draft.sensitive, -      "context" => draft.context, -      "attachment" => draft.attachments, -      "actor" => draft.user.ap_id, -      "tag" => Keyword.values(draft.tags) |> Enum.uniq() -    } -    |> add_in_reply_to(draft.in_reply_to) -    |> Map.merge(draft.extra) -  end - -  defp add_in_reply_to(object, nil), do: object - -  defp add_in_reply_to(object, in_reply_to) do -    with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do -      Map.put(object, "inReplyTo", in_reply_to_object.data["id"]) -    else -      _ -> object -    end -  end -    def format_naive_asctime(date) do      date |> DateTime.from_naive!("Etc/UTC") |> format_asctime    end @@ -412,19 +385,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def maybe_notify_mentioned_recipients(recipients, _), do: recipients -  # Do not notify subscribers if author is making a reply -  def maybe_notify_subscribers(recipients, %Activity{ -        object: %Object{data: %{"inReplyTo" => _ap_id}} -      }) do -    recipients -  end -    def maybe_notify_subscribers(          recipients, -        %Activity{data: %{"actor" => actor, "type" => type}} = activity -      ) -      when type == "Create" do -    with %User{} = user <- User.get_cached_by_ap_id(actor) do +        %Activity{data: %{"actor" => actor, "type" => "Create"}} = activity +      ) do +    # Do not notify subscribers if author is making a reply +    with %Object{data: object} <- Object.normalize(activity, fetch: false), +         nil <- object["inReplyTo"], +         %User{} = user <- User.get_cached_by_ap_id(actor) do        subscriber_ids =          user          |> User.subscriber_users() diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 61d65e7a3..7b84b43e4 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -6,17 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do    use Pleroma.Web, :controller    alias Pleroma.Pagination - -  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html -  @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] - -  def explicitly_falsy_param?(value), do: value in @falsy_param_values - -  # Note: `nil` and `""` are considered falsy values in Pleroma -  def falsy_param?(value), -    do: explicitly_falsy_param?(value) or value in [nil, ""] - -  def truthy_param?(value), do: not falsy_param?(value) +  alias Pleroma.Web.Utils.Params    def json_response(conn, status, _) when status in [204, :no_content] do      conn @@ -123,6 +113,6 @@ defmodule Pleroma.Web.ControllerHelper do      # To do once OpenAPI transition mess is over: just `truthy_param?(params[:with_relationships])`      params      |> Map.get(:with_relationships, params["with_relationships"]) -    |> truthy_param?() +    |> Params.truthy_param?()    end  end diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index f5ef76d32..69cfc2d52 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -96,6 +96,11 @@ defmodule Pleroma.Web.Federator do          Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")          {:error, e} +      {:error, {:validate_object, _}} = e -> +        Logger.error("Incoming AP doc validation error: #{inspect(e)}") +        Logger.debug(Jason.encode!(params, pretty: true)) +        e +        e ->          # Just drop those for now          Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 66940f311..c0fb35e01 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -52,10 +52,10 @@ defmodule Pleroma.Web.Feed.FeedView do    def feed_logo do      case Pleroma.Config.get([:feed, :logo]) do        nil -> -        "#{Pleroma.Web.base_url()}/static/logo.svg" +        "#{Pleroma.Web.Endpoint.url()}/static/logo.svg"        logo -> -        "#{Pleroma.Web.base_url()}#{logo}" +        "#{Pleroma.Web.Endpoint.url()}#{logo}"      end      |> MediaProxy.url()    end diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 58d35da1e..739b1f026 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController do    def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do      with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do        Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user}) +    else +      _ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)      end    end @@ -28,7 +30,7 @@ defmodule Pleroma.Web.Feed.UserController do    def feed_redirect(conn, %{"nickname" => nickname}) do      with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do -      redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom") +      redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")      end    end diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex deleted file mode 100644 index e788ab37a..000000000 --- a/lib/pleroma/web/masto_fe_controller.ex +++ /dev/null @@ -1,65 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MastoFEController do -  use Pleroma.Web, :controller - -  alias Pleroma.User -  alias Pleroma.Web.MastodonAPI.AuthController -  alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Web.Plugs.OAuthScopesPlug - -  plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings) - -  # Note: :index action handles attempt of unauthenticated access to private instance with redirect -  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index) - -  plug( -    OAuthScopesPlug, -    %{scopes: ["read"], fallback: :proceed_unauthenticated} -    when action == :index -  ) - -  plug( -    :skip_plug, -    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :manifest -  ) - -  @doc "GET /web/*path" -  def index(conn, _params) do -    with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn, -         {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do -      conn -      |> put_layout(false) -      |> render("index.html", -        token: token.token, -        user: user, -        custom_emojis: Pleroma.Emoji.get_all() -      ) -    else -      _ -> -        conn -        |> put_session(:return_to, conn.request_path) -        |> redirect(to: "/web/login") -    end -  end - -  @doc "GET /web/manifest.json" -  def manifest(conn, _params) do -    render(conn, "manifest.json") -  end - -  @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere" -  def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do -    with {:ok, _} <- User.mastodon_settings_update(user, settings) do -      json(conn, %{}) -    else -      e -> -        conn -        |> put_status(:internal_server_error) -        |> json(%{error: inspect(e)}) -    end -  end -end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7a1e99044..5fcbffc34 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    import Pleroma.Web.ControllerHelper,      only: [        add_link_headers: 2, -      truthy_param?: 1,        assign_account_by_id: 2,        embed_relationships?: 1,        json_response: 3 @@ -25,16 +24,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    alias Pleroma.Web.MastodonAPI.MastodonAPIController    alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.OAuth.OAuthController -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug    alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.Plugs.RateLimiter    alias Pleroma.Web.TwitterAPI.TwitterAPI +  alias Pleroma.Web.Utils.Params    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create) +  plug(:skip_auth when action == :create) -  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses]) +  plug(:skip_public_check when action in [:show, :statuses])    plug(      OAuthScopesPlug, @@ -188,7 +187,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do          :accepts_chat_messages        ]        |> Enum.reduce(%{}, fn key, acc -> -        Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)}) +        Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})        end)        |> Maps.put_if_present(:name, params[:display_name])        |> Maps.put_if_present(:bio, params[:note]) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index dd3b39c77..93e63ba03 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -14,21 +14,13 @@ defmodule Pleroma.Web.MastodonAPI.AppController do    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Scopes    alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Web.Plugs.OAuthScopesPlug    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) -  plug( -    :skip_plug, -    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] -    when action in [:create, :verify_credentials] -  ) +  plug(:skip_auth when action in [:create, :verify_credentials])    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  @local_mastodon_name "Mastodon-Local" -    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation    @doc "POST /api/v1/apps" @@ -41,7 +33,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do        |> Map.put(:scopes, scopes)      with cs <- App.register_changeset(%App{}, app_attrs), -         false <- cs.changes[:client_name] == @local_mastodon_name,           {:ok, app} <- Repo.insert(cs) do        render(conn, "show.json", app: app)      end diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index eb6639fc5..08943f6f1 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do    import Pleroma.Web.ControllerHelper, only: [json_response: 3] -  alias Pleroma.Helpers.AuthHelper -  alias Pleroma.Helpers.UriHelper -  alias Pleroma.User -  alias Pleroma.Web.OAuth.App -  alias Pleroma.Web.OAuth.Authorization -  alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken    alias Pleroma.Web.TwitterAPI.TwitterAPI    action_fallback(Pleroma.Web.MastodonAPI.FallbackController)    plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) -  @local_mastodon_name "Mastodon-Local" - -  @doc "GET /web/login" -  # Local Mastodon FE login callback action -  def login(conn, %{"code" => auth_token} = params) do -    with {:ok, app} <- local_mastofe_app(), -         {:ok, auth} <- Authorization.get_by_token(app, auth_token), -         {:ok, oauth_token} <- Token.exchange_token(app, auth) do -      redirect_to = -        conn -        |> local_mastodon_post_login_path() -        |> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token}) - -      conn -      |> AuthHelper.put_session_token(oauth_token.token) -      |> redirect(to: redirect_to) -    else -      _ -> redirect_to_oauth_form(conn, params) -    end -  end - -  def login(conn, params) do -    with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn, -         {:ok, %{id: ^app_id}} <- local_mastofe_app() do -      redirect(conn, to: local_mastodon_post_login_path(conn)) -    else -      _ -> redirect_to_oauth_form(conn, params) -    end -  end - -  defp redirect_to_oauth_form(conn, _params) do -    with {:ok, app} <- local_mastofe_app() do -      path = -        o_auth_path(conn, :authorize, -          response_type: "code", -          client_id: app.client_id, -          redirect_uri: ".", -          scope: Enum.join(app.scopes, " ") -        ) - -      redirect(conn, to: path) -    end -  end - -  @doc "DELETE /auth/sign_out" -  def logout(conn, _) do -    conn = -      with %{assigns: %{token: %Token{} = oauth_token}} <- conn, -           session_token = AuthHelper.get_session_token(conn), -           {:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do -        AuthHelper.delete_session_token(conn) -      else -        _ -> conn -      end - -    redirect(conn, to: "/") -  end -    @doc "POST /auth/password"    def password_reset(conn, params) do      nickname_or_email = params["email"] || params["nickname"] @@ -86,23 +21,4 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do      json_response(conn, :no_content, "")    end - -  defp local_mastodon_post_login_path(conn) do -    case get_session(conn, :return_to) do -      nil -> -        masto_fe_path(conn, :index, ["getting-started"]) - -      return_to -> -        delete_session(conn, :return_to) -        return_to -    end -  end - -  @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} -  def local_mastofe_app do -    App.get_or_make( -      %{client_name: @local_mastodon_name, redirect_uris: "."}, -      ["read", "write", "follow", "push", "admin"] -    ) -  end  end diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index d7e18dc92..31b647755 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -7,11 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug( -    :skip_plug, -    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] -    when action == :index -  ) +  plug(:skip_auth when action == :index)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 63d0e2c35..d915298f1 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Plugs.OAuthScopesPlug -  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:assign_follower when action != :index) diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index c7a5267d4..5376e4594 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,11 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug( -    :skip_plug, -    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] -    when action in [:show, :peers] -  ) +  plug(:skip_auth when action in [:show, :peers])    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index a1bcc91d9..a0f79f377 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -15,11 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    require Logger -  plug( -    :skip_plug, -    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] -    when action in [:empty_array, :empty_object] -  ) +  plug(:skip_auth when action in [:empty_array, :empty_object])    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index d6949ed80..5918b288d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do    action_fallback(Pleroma.Web.MastodonAPI.FallbackController)    plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)    plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show)    plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 647ba661e..002d6b2ce 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -50,6 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do      favourite      move      pleroma:emoji_reaction +    poll    }    def index(%{assigns: %{user: user}} = conn, params) do      params = diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index af93e453d..64b177eb3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    alias Pleroma.Activity    alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.Web    alias Pleroma.Web.ControllerHelper +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -108,7 +108,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    end    defp resource_search(:v2, "hashtags", query, options) do -    tags_path = Web.base_url() <> "/tag/" +    tags_path = Endpoint.url() <> "/tag/"      query      |> prepare_tags(options) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 724dc5c5d..2eff4d9d0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -27,10 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug( -    :skip_plug, -    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show] -  ) +  plug(:skip_public_check when action in [:index, :show])    @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index c611958be..10c279893 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -12,12 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    alias Pleroma.Pagination    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug    alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.Plugs.RateLimiter    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) +  plug(:skip_public_check when action in [:public, :hashtag])    # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:    # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e @@ -37,8 +36,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      when action in [:public, :hashtag]    ) -  plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) -    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation    # GET /api/v1/timelines/home @@ -196,7 +193,9 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do          |> ActivityPub.fetch_activities_bounded(following, params)          |> Enum.reverse() -      render(conn, "index.json", +      conn +      |> add_link_headers(activities) +      |> render("index.json",          activities: activities,          for: user,          as: :activity, diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index ac25aefdd..9e9de33f6 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -292,6 +292,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      |> maybe_put_allow_following_move(user, opts[:for])      |> maybe_put_unread_conversation_count(user, opts[:for])      |> maybe_put_unread_notification_count(user, opts[:for]) +    |> maybe_put_email_address(user, opts[:for])    end    defp username_from_nickname(string) when is_binary(string) do @@ -403,6 +404,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do    defp maybe_put_unread_notification_count(data, _, _), do: data +  defp maybe_put_email_address(data, %User{id: user_id}, %User{id: user_id} = user) do +    Kernel.put_in( +      data, +      [:pleroma, :email], +      user.email +    ) +  end + +  defp maybe_put_email_address(data, _, _), do: data +    defp image_url(%{"url" => [%{"href" => href} | _]}), do: href    defp image_url(_), do: nil  end diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex index 40e314164..7d2d605e9 100644 --- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex +++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex @@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do    use Pleroma.Web, :view    alias Pleroma.Emoji -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    def render("index.json", %{custom_emojis: custom_emojis}) do      render_many(custom_emojis, __MODULE__, "show.json")    end    def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do -    url = Web.base_url() |> URI.merge(relative_url) |> to_string() +    url = Endpoint.url() |> URI.merge(relative_url) |> to_string()      %{        "shortcode" => shortcode, diff --git a/lib/pleroma/web/mastodon_api/views/follow_request_view.ex b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex new file mode 100644 index 000000000..4c7d9fc65 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/follow_request_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts) +end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index dac68d8e6..ef208062b 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do      instance = Config.get(:instance)      %{ -      uri: Pleroma.Web.base_url(), +      uri: Pleroma.Web.Endpoint.url(),        title: Keyword.get(instance, :name),        description: Keyword.get(instance, :description),        version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", @@ -24,7 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do        },        stats: Pleroma.Stats.get_stats(),        thumbnail: -        URI.merge(Pleroma.Web.base_url(), Keyword.get(instance, :instance_thumbnail)) |> to_string, +        URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) +        |> to_string,        languages: ["en"],        registrations: Keyword.get(instance, :registrations_open),        approval_required: Keyword.get(instance, :account_approval_required), @@ -35,8 +36,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do        avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),        background_upload_limit: Keyword.get(instance, :background_upload_limit),        banner_upload_limit: Keyword.get(instance, :banner_upload_limit), -      background_image: Pleroma.Web.base_url() <> Keyword.get(instance, :background_image), -      chat_limit: Keyword.get(instance, :chat_limit), +      background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), +      shout_limit: Config.get([:shout, :limit]),        description_limit: Keyword.get(instance, :description_limit),        pleroma: %{          metadata: %{ @@ -68,9 +69,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do        if Config.get([:gopher, :enabled]) do          "gopher"        end, -      if Config.get([:chat, :enabled]) do +      # backwards compat +      if Config.get([:shout, :enabled]) do          "chat"        end, +      if Config.get([:shout, :enabled]) do +        "shout" +      end,        if Config.get([:instance, :allow_relay]) do          "relay"        end, @@ -90,7 +95,20 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do        {:ok, data} = MRF.describe()        data -      |> Map.merge(%{quarantined_instances: quarantined}) +      |> Map.put( +        :quarantined_instances, +        Enum.map(quarantined, fn {instance, _reason} -> instance end) +      ) +      # This is for backwards compatibility. We originally didn't sent +      # extra info like a reason why an instance was rejected/quarantined/etc. +      # Because we didn't want to break backwards compatibility it was decided +      # to add an extra "info" key. +      |> Map.put(:quarantined_instances_info, %{ +        "quarantined_instances" => +          quarantined +          |> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end) +          |> Map.new() +      })      else        %{}      end diff --git a/lib/pleroma/web/mastodon_api/views/media_view.ex b/lib/pleroma/web/mastodon_api/views/media_view.ex new file mode 100644 index 000000000..cf521887e --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/media_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MediaView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts) +end diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index df9bedfed..35c636d4e 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -112,6 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do        "move" ->          put_target(response, activity, reading_user, %{}) +      "poll" -> +        put_status(response, activity, reading_user, status_render_opts) +        "pleroma:emoji_reaction" ->          response          |> put_status(parent_activity_fn.(), reading_user, status_render_opts) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 814b3d142..463f34198 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    alias Pleroma.Activity    alias Pleroma.HTML +  alias Pleroma.Maps    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User @@ -64,11 +65,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    defp get_context_id(_), do: nil -  defp reblogged?(activity, user) do -    object = Object.normalize(activity, fetch: false) || %{} -    present?(user && user.ap_id in (object.data["announcements"] || [])) +  # Check if the user reblogged this status +  defp reblogged?(activity, %User{ap_id: ap_id}) do +    with %Object{data: %{"announcements" => announcements}} when is_list(announcements) <- +           Object.normalize(activity, fetch: false) do +      ap_id in announcements +    else +      _ -> false +    end    end +  # False if the user is logged out +  defp reblogged?(_activity, _user), do: false +    def render("index.json", opts) do      reading_user = opts[:for] @@ -259,7 +268,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      content_html =        content -      |> HTML.get_cached_scrubbed_html_for_activity( +      |> Activity.HTML.get_cached_scrubbed_html_for_activity(          User.html_filter_policy(opts[:for]),          activity,          "mastoapi:content" @@ -267,7 +276,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      content_plaintext =        content -      |> HTML.get_cached_stripped_html_for_activity( +      |> Activity.HTML.get_cached_stripped_html_for_activity(          activity,          "mastoapi:content"        ) @@ -417,6 +426,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"      href = attachment_url["href"] |> MediaProxy.url()      href_preview = attachment_url["href"] |> MediaProxy.preview_url() +    meta = render("attachment_meta.json", %{attachment: attachment})      type =        cond do @@ -439,8 +449,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        pleroma: %{mime_type: media_type},        blurhash: attachment["blurhash"]      } +    |> Maps.put_if_present(:meta, meta)    end +  def render("attachment_meta.json", %{ +        attachment: %{"url" => [%{"width" => width, "height" => height} | _]} +      }) +      when is_integer(width) and is_integer(height) do +    %{ +      original: %{ +        width: width, +        height: height, +        aspect: width / height +      } +    } +  end + +  def render("attachment_meta.json", _), do: nil +    def render("context.json", %{activity: activity, activities: activities, user: user}) do      %{ancestors: ancestors, descendants: descendants} =        activities @@ -496,7 +522,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    def build_tags(object_tags) when is_list(object_tags) do      object_tags      |> Enum.filter(&is_binary/1) -    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.base_url()}/tag/#{URI.encode(&1)}"}) +    |> Enum.map(&%{name: &1, url: "#{Pleroma.Web.Endpoint.url()}/tag/#{URI.encode(&1)}"})    end    def build_tags(_), do: [] diff --git a/lib/pleroma/web/mastodon_api/views/timeline_view.ex b/lib/pleroma/web/mastodon_api/views/timeline_view.ex new file mode 100644 index 000000000..91226d78e --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/timeline_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.TimelineView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.StatusView.render(view, opts) +end diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 0d1faffbd..b978167b6 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -49,9 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do    def websocket_init(state) do      Logger.debug( -      "#{__MODULE__} accepted websocket connection for user #{ -        (state.user || %{id: "anonymous"}).id -      }, topic #{state.topic}" +      "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"      )      Streamer.add_socket(state.topic, state.user) @@ -106,9 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do    def terminate(reason, _req, state) do      Logger.debug( -      "#{__MODULE__} terminating websocket connection for user #{ -        (state.user || %{id: "anonymous"}).id -      }, topic #{state.topic || "?"}: #{inspect(reason)}" +      "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"      )      Streamer.remove_socket(state.topic) diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex index d0d4bb4b3..0b232f14b 100644 --- a/lib/pleroma/web/media_proxy.ex +++ b/lib/pleroma/web/media_proxy.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MediaProxy do    alias Pleroma.Config    alias Pleroma.Helpers.UriHelper    alias Pleroma.Upload -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.MediaProxy.Invalidation    @base64_opts [padding: false] @@ -69,7 +69,7 @@ defmodule Pleroma.Web.MediaProxy do    #   non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.    def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled]) -  def local?(url), do: String.starts_with?(url, Web.base_url()) +  def local?(url), do: String.starts_with?(url, Endpoint.url())    def whitelisted?(url) do      %{host: domain} = URI.parse(url) @@ -127,7 +127,7 @@ defmodule Pleroma.Web.MediaProxy do    end    defp signed_url(url) do -    :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url) +    :crypto.mac(:hmac, :sha, Config.get([Endpoint, :secret_key_base]), url)    end    def filename(url_or_path) do @@ -135,7 +135,7 @@ defmodule Pleroma.Web.MediaProxy do    end    def base_url do -    Config.get([:media_proxy, :base_url], Web.base_url()) +    Config.get([:media_proxy, :base_url], Endpoint.url())    end    defp proxy_url(path, sig_base64, url_base64, filename) do diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex index 1687b2634..df0cca74a 100644 --- a/lib/pleroma/web/metadata/providers/open_graph.ex +++ b/lib/pleroma/web/metadata/providers/open_graph.ex @@ -4,6 +4,7 @@  defmodule Pleroma.Web.Metadata.Providers.OpenGraph do    alias Pleroma.User +  alias Pleroma.Web.MediaProxy    alias Pleroma.Web.Metadata    alias Pleroma.Web.Metadata.Providers.Provider    alias Pleroma.Web.Metadata.Utils @@ -19,37 +20,24 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do        }) do      attachments = build_attachments(object)      scrubbed_content = Utils.scrub_html_and_truncate(object) -    # Zero width space -    content = -      if scrubbed_content != "" and scrubbed_content != "\u200B" do -        ": “" <> scrubbed_content <> "”" -      else -        "" -      end -    # Most previews only show og:title which is inconvenient. Instagram -    # hacks this by putting the description in the title and making the -    # description longer prefixed by how many likes and shares the post -    # has. Here we use the descriptive nickname in the title, and expand -    # the full account & nickname in the description. We also use the cute^Wevil -    # smart quotes around the status text like Instagram, too.      [        {:meta,         [           property: "og:title", -         content: "#{user.name}" <> content +         content: Utils.user_name_string(user)         ], []},        {:meta, [property: "og:url", content: url], []},        {:meta,         [           property: "og:description", -         content: "#{Utils.user_name_string(user)}" <> content +         content: scrubbed_content         ], []}, -      {:meta, [property: "og:type", content: "website"], []} +      {:meta, [property: "og:type", content: "article"], []}      ] ++        if attachments == [] or Metadata.activity_nsfw?(object) do          [ -          {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], +          {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],             []},            {:meta, [property: "og:image:width", content: 150], []},            {:meta, [property: "og:image:height", content: 150], []} @@ -70,8 +58,9 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do           ], []},          {:meta, [property: "og:url", content: user.uri || user.ap_id], []},          {:meta, [property: "og:description", content: truncated_bio], []}, -        {:meta, [property: "og:type", content: "website"], []}, -        {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []}, +        {:meta, [property: "og:type", content: "article"], []}, +        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], +         []},          {:meta, [property: "og:image:width", content: 150], []},          {:meta, [property: "og:image:height", content: 150], []}        ] @@ -82,29 +71,35 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do      Enum.reduce(attachments, [], fn attachment, acc ->        rendered_tags =          Enum.reduce(attachment["url"], [], fn url, acc -> -          # TODO: Add additional properties to objects when we have the data available. -          # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image +          # TODO: Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image            # object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.            case Utils.fetch_media_type(@media_types, url["mediaType"]) do              "audio" ->                [ -                {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []} +                {:meta, [property: "og:audio", content: MediaProxy.url(url["href"])], []}                  | acc                ] +            # Not using preview_url for this. It saves bandwidth, but the image dimensions will +            # be wrong. We generate it on the fly and have no way to capture or analyze the +            # image to get the dimensions. This can be an issue for apps/FEs rendering images +            # in timelines too, but you can get clever with the aspect ratio metadata as a +            # workaround.              "image" ->                [ -                {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []}, -                {:meta, [property: "og:image:width", content: 150], []}, -                {:meta, [property: "og:image:height", content: 150], []} +                {:meta, [property: "og:image", content: MediaProxy.url(url["href"])], []}, +                {:meta, [property: "og:image:alt", content: attachment["name"]], []}                  | acc                ] +              |> maybe_add_dimensions(url)              "video" ->                [ -                {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []} +                {:meta, [property: "og:video", content: MediaProxy.url(url["href"])], []}                  | acc                ] +              |> maybe_add_dimensions(url) +              |> maybe_add_video_thumbnail(url)              _ ->                acc @@ -116,4 +111,38 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do    end    defp build_attachments(_), do: [] + +  # We can use url["mediaType"] to dynamically fill the metadata +  defp maybe_add_dimensions(metadata, url) do +    type = url["mediaType"] |> String.split("/") |> List.first() + +    cond do +      !is_nil(url["height"]) && !is_nil(url["width"]) -> +        metadata ++ +          [ +            {:meta, [property: "og:#{type}:width", content: "#{url["width"]}"], []}, +            {:meta, [property: "og:#{type}:height", content: "#{url["height"]}"], []} +          ] + +      true -> +        metadata +    end +  end + +  # Media Preview Proxy makes thumbnails of videos without resizing, so we can trust the +  # width and height of the source video. +  defp maybe_add_video_thumbnail(metadata, url) do +    cond do +      Pleroma.Config.get([:media_preview_proxy, :enabled], false) -> +        metadata ++ +          [ +            {:meta, [property: "og:image:width", content: "#{url["width"]}"], []}, +            {:meta, [property: "og:image:height", content: "#{url["height"]}"], []}, +            {:meta, [property: "og:image", content: MediaProxy.preview_url(url["href"])], []} +          ] + +      true -> +        metadata +    end +  end  end diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index 58fc05cf9..79183df86 100644 --- a/lib/pleroma/web/metadata/providers/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.Metadata.Providers.TwitterCard do    alias Pleroma.User +  alias Pleroma.Web.MediaProxy    alias Pleroma.Web.Metadata    alias Pleroma.Web.Metadata.Providers.Provider    alias Pleroma.Web.Metadata.Utils @@ -16,17 +17,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do    def build_tags(%{activity_id: id, object: object, user: user}) do      attachments = build_attachments(id, object)      scrubbed_content = Utils.scrub_html_and_truncate(object) -    # Zero width space -    content = -      if scrubbed_content != "" and scrubbed_content != "\u200B" do -        "“" <> scrubbed_content <> "”" -      else -        "" -      end      [        title_tag(user), -      {:meta, [property: "twitter:description", content: content], []} +      {:meta, [property: "twitter:description", content: scrubbed_content], []}      ] ++        if attachments == [] or Metadata.activity_nsfw?(object) do          [ @@ -55,14 +49,14 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do    end    def image_tag(user) do -    {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []} +    {:meta, [property: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], +     []}    end    defp build_attachments(id, %{data: %{"attachment" => attachments}}) do      Enum.reduce(attachments, [], fn attachment, acc ->        rendered_tags =          Enum.reduce(attachment["url"], [], fn url, acc -> -          # TODO: Add additional properties to objects when we have the data available.            case Utils.fetch_media_type(@media_types, url["mediaType"]) do              "audio" ->                [ @@ -73,25 +67,37 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do                  | acc                ] +            # Not using preview_url for this. It saves bandwidth, but the image dimensions will +            # be wrong. We generate it on the fly and have no way to capture or analyze the +            # image to get the dimensions. This can be an issue for apps/FEs rendering images +            # in timelines too, but you can get clever with the aspect ratio metadata as a +            # workaround.              "image" ->                [                  {:meta, [property: "twitter:card", content: "summary_large_image"], []},                  {:meta,                   [                     property: "twitter:player", -                   content: Utils.attachment_url(url["href"]) +                   content: MediaProxy.url(url["href"])                   ], []}                  | acc                ] +              |> maybe_add_dimensions(url) -            # TODO: Need the true width and height values here or Twitter renders an iFrame with -            # a bad aspect ratio              "video" -> +              # fallback to old placeholder values +              height = url["height"] || 480 +              width = url["width"] || 480 +                [                  {:meta, [property: "twitter:card", content: "player"], []},                  {:meta, [property: "twitter:player", content: player_url(id)], []}, -                {:meta, [property: "twitter:player:width", content: "480"], []}, -                {:meta, [property: "twitter:player:height", content: "480"], []} +                {:meta, [property: "twitter:player:width", content: "#{width}"], []}, +                {:meta, [property: "twitter:player:height", content: "#{height}"], []}, +                {:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])], +                 []}, +                {:meta, +                 [property: "twitter:player:stream:content_type", content: url["mediaType"]], []}                  | acc                ] @@ -109,4 +115,20 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do    defp player_url(id) do      Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)    end + +  # Videos have problems without dimensions, but we used to not provide WxH for images. +  # A default (read: incorrect) fallback for images is likely to cause rendering bugs. +  defp maybe_add_dimensions(metadata, url) do +    cond do +      !is_nil(url["height"]) && !is_nil(url["width"]) -> +        metadata ++ +          [ +            {:meta, [property: "twitter:player:width", content: "#{url["width"]}"], []}, +            {:meta, [property: "twitter:player:height", content: "#{url["height"]}"], []} +          ] + +      true -> +        metadata +    end +  end  end diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index de7195435..caca42934 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -3,17 +3,17 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Metadata.Utils do +  alias Pleroma.Activity    alias Pleroma.Emoji    alias Pleroma.Formatter    alias Pleroma.HTML -  alias Pleroma.Web.MediaProxy    def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do      content      # html content comes from DB already encoded, decode first and scrub after      |> HtmlEntities.decode()      |> String.replace(~r/<br\s?\/?>/, " ") -    |> HTML.get_cached_stripped_html_for_activity(object, "metadata") +    |> Activity.HTML.get_cached_stripped_html_for_activity(object, "metadata")      |> Emoji.Formatter.demojify()      |> HtmlEntities.decode()      |> Formatter.truncate() @@ -37,10 +37,6 @@ defmodule Pleroma.Web.Metadata.Utils do    def scrub_html(content), do: content -  def attachment_url(url) do -    MediaProxy.preview_url(url) -  end -    def user_name_string(user) do      "#{user.name} " <>        if user.local do diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index bca94d236..69ec27ba0 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.Nodeinfo.NodeinfoController do    use Pleroma.Web, :controller -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.Nodeinfo.Nodeinfo    def schemas(conn, _params) do @@ -13,11 +13,11 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do        links: [          %{            rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", -          href: Web.base_url() <> "/nodeinfo/2.0.json" +          href: Endpoint.url() <> "/nodeinfo/2.0.json"          },          %{            rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", -          href: Web.base_url() <> "/nodeinfo/2.1.json" +          href: Endpoint.url() <> "/nodeinfo/2.1.json"          }        ]      } diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 215d97b3a..0d7d17b8a 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -12,8 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Registration    alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.Web.Auth.Authenticator -  alias Pleroma.Web.ControllerHelper +  alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.MFAController @@ -24,6 +23,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken    alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken    alias Pleroma.Web.Plugs.RateLimiter +  alias Pleroma.Web.Utils.Params    require Logger @@ -32,10 +32,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    plug(:fetch_session)    plug(:fetch_flash) -  plug(:skip_plug, [ -    Pleroma.Web.Plugs.OAuthScopesPlug, -    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug -  ]) +  plug(:skip_auth)    plug(RateLimiter, [name: :authentication] when action == :create_authorization) @@ -50,7 +47,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    end    def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do -    if ControllerHelper.truthy_param?(params["force_login"]) do +    if Params.truthy_param?(params["force_login"]) do        do_authorize(conn, params)      else        handle_existing_authorization(conn, params) @@ -427,7 +424,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do        |> Map.put("state", state)      # Handing the request to Ueberauth -    redirect(conn, to: o_auth_path(conn, :request, provider, params)) +    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))    end    def request(%Plug.Conn{} = conn, params) do @@ -600,9 +597,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do      end    end -  # Special case: Local MastodonFE -  defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) -    defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri    defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id) diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex index 281bbcc3c..1419c96a2 100644 --- a/lib/pleroma/web/o_auth/o_auth_view.ex +++ b/lib/pleroma/web/o_auth/o_auth_view.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.OAuth.OAuthView do    def render("token.json", %{token: token} = opts) do      response = %{ +      id: token.id,        token_type: "Bearer",        access_token: token.token,        refresh_token: token.refresh_token, diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 165afd3b4..8e4d3e7f7 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.MastodonAPI.StatusView -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug    alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.Plugs.RateLimiter @@ -29,10 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug( -    :skip_plug, -    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend -  ) +  plug(:skip_auth when action == :confirmation_resend)    plug(      OAuthScopesPlug, @@ -47,7 +43,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do    plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)    plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) -  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex index d285e4907..be2f4617d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do    alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)    plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses])    plug( diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index d0f677d3c..1ea44f347 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -22,11 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do           ]    ) -  @skip_plugs [ -    Pleroma.Web.Plugs.OAuthScopesPlug, -    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug -  ] -  plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show]) +  plug(:skip_auth when action in [:index, :archive, :show])    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex index 257bcd550..bcb3a9ae1 100644 --- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -14,8 +14,6 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do      %{scopes: ["write:notifications"]} when action == :mark_as_read    ) -  plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView) -    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation    def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do diff --git a/lib/pleroma/web/pleroma_api/views/account_view.ex b/lib/pleroma/web/pleroma_api/views/account_view.ex new file mode 100644 index 000000000..28941f471 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/account_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.AccountView.render(view, opts) +end diff --git a/lib/pleroma/web/pleroma_api/views/conversation_view.ex b/lib/pleroma/web/pleroma_api/views/conversation_view.ex new file mode 100644 index 000000000..173006360 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/conversation_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ConversationView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.ConversationView.render(view, opts) +end diff --git a/lib/pleroma/web/pleroma_api/views/notification_view.ex b/lib/pleroma/web/pleroma_api/views/notification_view.ex new file mode 100644 index 000000000..36b2fdfe8 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/notification_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.NotificationView do +  use Pleroma.Web, :view +  alias Pleroma.Web.MastodonAPI + +  def render(view, opts), do: MastodonAPI.NotificationView.render(view, opts) +end diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index eb385e94d..ebe7eaf86 100644 --- a/lib/pleroma/web/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -10,8 +10,6 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do    """    @behaviour Plug -  @api_routes Pleroma.Web.get_api_routes() -    def file_path(path, frontend_type \\ :primary) do      if configuration = Pleroma.Config.get([:frontends, frontend_type]) do        instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static") @@ -55,10 +53,13 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do    defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)    defp invalid_path?([], _match), do: false -  defp api_route?([h | _]) when h in @api_routes, do: true -  defp api_route?([_ | t]), do: api_route?(t)    defp api_route?([]), do: false +  defp api_route?([h | t]) do +    api_routes = Pleroma.Web.Router.get_api_routes() +    if h in api_routes, do: true, else: api_route?(t) +  end +    defp call_static(conn, opts, from) do      opts = Map.put(opts, :from, from)      Plug.Static.call(conn, opts) diff --git a/lib/pleroma/web/plugs/user_is_staff_plug.ex b/lib/pleroma/web/plugs/user_is_staff_plug.ex new file mode 100644 index 000000000..49c2d9cca --- /dev/null +++ b/lib/pleroma/web/plugs/user_is_staff_plug.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.UserIsStaffPlug do +  import Pleroma.Web.TranslationHelpers +  import Plug.Conn + +  alias Pleroma.User + +  def init(options) do +    options +  end + +  def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn +  def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _), do: conn + +  def call(conn, _) do +    conn +    |> render_error(:forbidden, "User is not a staff member.") +    |> halt() +  end +end diff --git a/lib/pleroma/web/preload.ex b/lib/pleroma/web/preload.ex index e8588bcc9..34a181e17 100644 --- a/lib/pleroma/web/preload.ex +++ b/lib/pleroma/web/preload.ex @@ -4,7 +4,6 @@  defmodule Pleroma.Web.Preload do    alias Phoenix.HTML -  require Logger    def build_tags(_conn, params) do      preload_data = diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 83cbdc870..28e13ef9c 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -124,8 +124,8 @@ defmodule Pleroma.Web.Push.Impl do    def format_body(activity, actor, object, mastodon_type \\ nil) -  def format_body(_activity, actor, %{data: %{"type" => "ChatMessage", "content" => content}}, _) do -    case content do +  def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do +    case data["content"] do        nil -> "@#{actor.nickname}: (Attachment)"        content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"      end diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index 4f6c9bc9f..35bf2e223 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do    end    # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength -  @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a +  @supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a    defp alerts(%{data: %{alerts: alerts}}) do      alerts = Map.take(alerts, @supported_alert_types) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ccf2ef796..abb332ec2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -96,14 +96,12 @@ defmodule Pleroma.Web.Router do      plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)      plug(:after_auth)      plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) -    plug(Pleroma.Web.Plugs.UserIsAdminPlug) +    plug(Pleroma.Web.Plugs.UserIsStaffPlug)      plug(Pleroma.Web.Plugs.IdempotencyPlug)    end -  pipeline :mastodon_html do -    plug(:browser) -    plug(:authenticate) -    plug(:after_auth) +  pipeline :require_admin do +    plug(Pleroma.Web.Plugs.UserIsAdminPlug)    end    pipeline :pleroma_html do @@ -140,6 +138,10 @@ defmodule Pleroma.Web.Router do      plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)    end +  pipeline :static_fe do +    plug(Pleroma.Web.Plugs.StaticFEPlug) +  end +    scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do      pipe_through(:pleroma_api) @@ -156,7 +158,7 @@ defmodule Pleroma.Web.Router do    end    scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do -    pipe_through(:admin_api) +    pipe_through([:admin_api, :require_admin])      put("/users/disable_mfa", AdminAPIController, :disable_mfa)      put("/users/tag", AdminAPIController, :tag_users) @@ -209,7 +211,8 @@ defmodule Pleroma.Web.Router do      get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)      get("/users/:nickname/chats", AdminAPIController, :list_user_chats) -    get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses) +    get("/instances/:instance/statuses", InstanceController, :list_statuses) +    delete("/instances/:instance", InstanceController, :delete)      get("/instance_document/:name", InstanceDocumentController, :show)      patch("/instance_document/:name", InstanceDocumentController, :update) @@ -261,7 +264,7 @@ defmodule Pleroma.Web.Router do    scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do      scope "/pack" do -      pipe_through(:admin_api) +      pipe_through([:admin_api, :require_admin])        post("/", EmojiPackController, :create)        patch("/", EmojiPackController, :update) @@ -276,7 +279,7 @@ defmodule Pleroma.Web.Router do      # Modifying packs      scope "/packs" do -      pipe_through(:admin_api) +      pipe_through([:admin_api, :require_admin])        get("/import", EmojiPackController, :import_from_filesystem)        get("/remote", EmojiPackController, :remote) @@ -538,13 +541,6 @@ defmodule Pleroma.Web.Router do      get("/timelines/list/:list_id", TimelineController, :list)    end -  scope "/api/web", Pleroma.Web do -    pipe_through(:authenticated_api) - -    # Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere -    put("/settings", MastoFEController, :put_settings) -  end -    scope "/api/v1", Pleroma.Web.MastodonAPI do      pipe_through(:app_api) @@ -620,18 +616,12 @@ defmodule Pleroma.Web.Router do      get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)      delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) - -    post( -      "/qvitter/statuses/notifications/read", -      TwitterAPI.Controller, -      :mark_notifications_as_read -    )    end    scope "/", Pleroma.Web do      # Note: html format is supported only if static FE is enabled      # Note: http signature is only considered for json requests (no auth for non-json requests) -    pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug]) +    pipe_through([:accepts_html_json, :http_signature, :static_fe])      get("/objects/:uuid", OStatus.OStatusController, :object)      get("/activities/:uuid", OStatus.OStatusController, :activity) @@ -645,7 +635,7 @@ defmodule Pleroma.Web.Router do    scope "/", Pleroma.Web do      # Note: html format is supported only if static FE is enabled      # Note: http signature is only considered for json requests (no auth for non-json requests) -    pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug]) +    pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])      # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones      get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) @@ -653,7 +643,7 @@ defmodule Pleroma.Web.Router do    scope "/", Pleroma.Web do      # Note: html format is supported only if static FE is enabled -    pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug]) +    pipe_through([:accepts_html_xml, :static_fe])      get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)    end @@ -746,30 +736,11 @@ defmodule Pleroma.Web.Router do      get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)    end -  scope "/", Pleroma.Web do -    pipe_through(:api) - -    get("/web/manifest.json", MastoFEController, :manifest) -  end - -  scope "/", Pleroma.Web do -    pipe_through(:mastodon_html) - -    get("/web/login", MastodonAPI.AuthController, :login) -    delete("/auth/sign_out", MastodonAPI.AuthController, :logout) - -    post("/auth/password", MastodonAPI.AuthController, :password_reset) - -    get("/web/*path", MastoFEController, :index) - -    get("/embed/:id", EmbedController, :show) -  end - -  scope "/proxy/", Pleroma.Web.MediaProxy do -    get("/preview/:sig/:url", MediaProxyController, :preview) -    get("/preview/:sig/:url/:filename", MediaProxyController, :preview) -    get("/:sig/:url", MediaProxyController, :remote) -    get("/:sig/:url/:filename", MediaProxyController, :remote) +  scope "/proxy/", Pleroma.Web do +    get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview) +    get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview) +    get("/:sig/:url", MediaProxy.MediaProxyController, :remote) +    get("/:sig/:url/:filename", MediaProxy.MediaProxyController, :remote)    end    if Pleroma.Config.get(:env) == :dev do @@ -822,4 +793,16 @@ defmodule Pleroma.Web.Router do      options("/*path", RedirectController, :empty)    end + +  # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+ +  def get_api_routes do +    __MODULE__.__routes__() +    |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) +    |> Enum.map(fn r -> +      r.path +      |> String.split("/", trim: true) +      |> List.first() +    end) +    |> Enum.uniq() +  end  end diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/shout_channel.ex index 4008129e9..17caecb1a 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/shout_channel.ex @@ -2,12 +2,12 @@  # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ChatChannel do +defmodule Pleroma.Web.ShoutChannel do    use Phoenix.Channel    alias Pleroma.User -  alias Pleroma.Web.ChatChannel.ChatChannelState    alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.ShoutChannel.ShoutChannelState    def join("chat:public", _message, socket) do      send(self(), :after_join) @@ -15,18 +15,18 @@ defmodule Pleroma.Web.ChatChannel do    end    def handle_info(:after_join, socket) do -    push(socket, "messages", %{messages: ChatChannelState.messages()}) +    push(socket, "messages", %{messages: ShoutChannelState.messages()})      {:noreply, socket}    end    def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do      text = String.trim(text) -    if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do +    if String.length(text) in 1..Pleroma.Config.get([:shout, :limit]) do        author = User.get_cached_by_nickname(user_name)        author_json = AccountView.render("show.json", user: author, skip_visibility_check: true) -      message = ChatChannelState.add_message(%{text: text, author: author_json}) +      message = ShoutChannelState.add_message(%{text: text, author: author_json})        broadcast!(socket, "new_msg", message)      end @@ -35,7 +35,7 @@ defmodule Pleroma.Web.ChatChannel do    end  end -defmodule Pleroma.Web.ChatChannel.ChatChannelState do +defmodule Pleroma.Web.ShoutChannel.ShoutChannelState do    use Agent    @max_messages 20 diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index fe485d10d..50f0927a3 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do    alias Pleroma.Web.Router.Helpers    plug(:put_layout, :static_fe) -  plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)    plug(:assign_id)    @page_keys ["max_id", "min_id", "limit", "since_id", "order"] diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex index 6688830ba..57bd92468 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex @@ -38,7 +38,7 @@      <%= if id == Pleroma.Constants.as_public() do %>        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>      <% else %> -      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> +      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>          <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>        <% end %>      <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 592b9dcdc..279f2171d 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -38,7 +38,7 @@      <%= if id == Pleroma.Constants.as_public() do %>        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection">http://activityschema.org/collection/public</link>      <% else %> -      <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> +      <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>          <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person"><%= id %></link>        <% end %>      <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex index c2de28fe4..aa3035bca 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -33,7 +33,7 @@            ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"            href="http://activityschema.org/collection/public"/>        <% else %> -        <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> +        <%= unless Regex.match?(~r/^#{Pleroma.Web.Endpoint.url()}.+followers$/, id) do %>            <link rel="mentioned"              ostatus:object-type="http://activitystrea.ms/schema/1.0/person"              href="<%= id %>" /> diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex index a288539ed..de0731085 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -9,13 +9,13 @@        xmlns:ostatus="http://ostatus.org/schema/1.0"        xmlns:statusnet="http://status.net/schema/api/1/"> -    <id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id> +    <id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>      <title>#<%= @tag %></title>      <subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>      <logo><%= feed_logo() %></logo>      <updated><%= most_recent_update(@activities) %></updated> -    <link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/> +    <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>      <%= for activity <- @activities do %>      <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>      <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex index eeda01a04..9c3613feb 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex @@ -5,7 +5,7 @@      <title>#<%= @tag %></title>      <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description> -    <link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link> +    <link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>      <webfeeds:logo><%= feed_logo() %></webfeeds:logo>      <webfeeds:accentColor>2b90d9</webfeeds:accentColor>      <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex index c6acd848f..5c1f0ecbc 100644 --- a/lib/pleroma/web/templates/feed/feed/user.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex @@ -6,16 +6,16 @@    xmlns:poco="http://portablecontacts.net/spec/1.0"    xmlns:ostatus="http://ostatus.org/schema/1.0"> -  <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id> +  <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>    <title><%= @user.nickname <> "'s timeline" %></title>    <updated><%= most_recent_update(@activities, @user) %></updated>    <logo><%= logo(@user) %></logo> -  <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/> +  <link rel="self" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>    <%= render @view_module, "_author.atom", assigns %>    <%= if last_activity(@activities) do %> -    <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/> +    <link rel="next" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>    <% end %>    <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex index d69120480..6b842a085 100644 --- a/lib/pleroma/web/templates/feed/feed/user.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex @@ -1,16 +1,16 @@  <?xml version="1.0" encoding="UTF-8" ?>  <rss version="2.0">    <channel> -    <guid><%= user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid> +    <guid><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>      <title><%= @user.nickname <> "'s timeline" %></title>      <updated><%= most_recent_update(@activities, @user) %></updated>      <image><%= logo(@user) %></image> -    <link><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link> +    <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>      <%= render @view_module, "_author.rss", assigns %>      <%= if last_activity(@activities) do %> -      <link rel="next"><%= '#{user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link> +      <link rel="next"><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>      <% end %>      <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/masto_fe/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex deleted file mode 100644 index c330960fa..000000000 --- a/lib/pleroma/web/templates/masto_fe/index.html.eex +++ /dev/null @@ -1,35 +0,0 @@ -<!DOCTYPE html> -<html lang='en'> -<head> -<meta charset='utf-8'> -<meta content='width=device-width, initial-scale=1' name='viewport'> -<title> -<%= Config.get([:instance, :name]) %> -</title> -<link rel="icon" type="image/png" href="/favicon.png"/> -<link rel="manifest" type="applicaton/manifest+json" href="<%= masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" /> - -<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" /> - -<script crossorigin='anonymous' src="/packs/locales.js"></script> -<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script> - -<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'> -<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'> -<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js'> -<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/notifications.js'> -<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script> - -<script src="/packs/core/common.js"></script> -<link rel="stylesheet" media="all" href="/packs/core/common.css" /> - -<script src="/packs/flavours/glitch/common.js"></script> -<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" /> - -<script src="/packs/flavours/glitch/home.js"></script> -</head> -<body class='app-body no-reduce-motion system-font'> -  <div class='app-holder' data-props='{"locale":"en"}' id='mastodon'> -  </div> -</body> -</html> diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index 5ab59b57b..b9daa8d8b 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -7,7 +7,7 @@  <h2>Two-factor recovery</h2> -<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> +<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>  <div class="input">    <%= label f, :code, "Recovery code" %>    <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %> @@ -19,6 +19,6 @@  <%= submit "Verify" %>  <% end %> -<a href="<%= mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>"> +<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">    Enter a two-factor code  </a> diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index af85777eb..29ea7c5fb 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -7,7 +7,7 @@  <h2>Two-factor authentication</h2> -<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> +<%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>  <div class="input">    <%= label f, :code, "Authentication code" %>    <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %> @@ -19,6 +19,6 @@  <%= submit "Verify" %>  <% end %> -<a href="<%= mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>"> +<a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">    Enter a two-factor recovery code  </a> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 4a0718851..dc4521a62 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -1,6 +1,6 @@  <h2>Sign in with external provider</h2> -<%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %> +<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>    <div style="display: none">      <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>    </div> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index facedc8db..99f900fb7 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -8,7 +8,7 @@  <h2>Registration Details</h2>  <p>If you'd like to register a new account, please provide the details below.</p> -<%= form_for @conn, o_auth_path(@conn, :register), [as: "authorization"], fn f -> %> +<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>  <div class="input">    <%= label f, :nickname, "Nickname" %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 1a85818ec..181a9519a 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -5,7 +5,7 @@  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>  <% end %> -<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> +<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>  <%= if @user do %>    <div class="account-header"> @@ -61,5 +61,5 @@  <% end %>  <%= if Pleroma.Config.oauth_consumer_enabled?() do %> -  <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %> +  <%= render @view_module, Pleroma.Web.Auth.WrapperAuthenticator.oauth_consumer_template(), assigns %>  <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex index 7d3ef6b0d..fbcacdc14 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex @@ -1,5 +1,5 @@  <h2>Password Reset for <%= @user.nickname %></h2> -<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %> +<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>    <div class="form-row">      <%= label f, :password, "Password" %>      <%= password_input f, :password %> diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex index df037c01e..4ed4ac8bc 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex @@ -1,2 +1,2 @@  <h2>Password reset failed</h2> -<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3> +<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3> diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex index f30ba3274..086d4e08b 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex @@ -1,2 +1,2 @@  <h2>Password changed!</h2> -<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3> +<h3><a href="<%= Pleroma.Web.Endpoint.url() %>">Homepage</a></h3> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex index 5ba192cd7..a7be53091 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex @@ -4,7 +4,7 @@      <h2>Remote follow</h2>      <img height="128" width="128" src="<%= avatar_url(@followee) %>">      <p><%= @followee.nickname %></p> -    <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> +    <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>      <%= hidden_input f, :id, value: @followee.id %>      <%= submit "Authorize" %>      <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex index df44988ee..a8026fa9d 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex @@ -4,7 +4,7 @@  <h2>Log in to follow</h2>  <p><%= @followee.nickname %></p>  <img height="128" width="128" src="<%= avatar_url(@followee) %>"> -<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> +<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>  <%= text_input f, :name, placeholder: "Username", required: true %>  <br>  <%= password_input f, :password, placeholder: "Password", required: true %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex index adc3a3e3d..a54ed83b5 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex @@ -4,7 +4,7 @@  <h2>Two-factor authentication</h2>  <p><%= @followee.nickname %></p>  <img height="128" width="128" src="<%= avatar_url(@followee) %>"> -<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> +<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>  <%= text_input f, :code, placeholder: "Authentication code", required: true %>  <br>  <%= hidden_input f, :id, value: @followee.id %> diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex index f60accebf..a6b313d8a 100644 --- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex @@ -2,7 +2,7 @@    <h2>Error: <%= @error %></h2>  <% else %>    <h2>Remotely follow <%= @nickname %></h2> -  <%= form_for @conn, util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> +  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>    <%= hidden_input f, :nickname, value: @nickname %>    <%= text_input f, :profile, placeholder: "Your account ID, e.g. lain@quitter.se" %>    <%= submit "Follow" %> diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex index 077bfa70d..1e78ff2c1 100644 --- a/lib/pleroma/web/twitter_api/controller.ex +++ b/lib/pleroma/web/twitter_api/controller.ex @@ -5,25 +5,14 @@  defmodule Pleroma.Web.TwitterAPI.Controller do    use Pleroma.Web, :controller -  alias Pleroma.Notification    alias Pleroma.User    alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug    alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.TwitterAPI.TokenView    require Logger -  plug( -    OAuthScopesPlug, -    %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read -  ) - -  plug( -    :skip_plug, -    [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email -  ) - +  plug(:skip_auth when action == :confirm_email)    plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])    action_fallback(:errors) @@ -67,31 +56,4 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      |> put_resp_content_type("application/json")      |> send_resp(status, json)    end - -  def mark_notifications_as_read( -        %{assigns: %{user: user}} = conn, -        %{"latest_id" => latest_id} = params -      ) do -    Notification.set_read_up_to(user, latest_id) - -    notifications = Notification.for_user(user, params) - -    conn -    # XXX: This is a hack because pleroma-fe still uses that API. -    |> put_view(Pleroma.Web.MastodonAPI.NotificationView) -    |> render("index.json", %{notifications: notifications, for: user}) -  end - -  def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do -    bad_request_reply(conn, "You need to specify latest_id") -  end - -  defp bad_request_reply(conn, error_message) do -    json = error_json(conn, error_message) -    json_reply(conn, 400, json) -  end - -  defp error_json(conn, error_message) do -    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!() -  end  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 6ca02fbd7..42d7601ed 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    alias Pleroma.MFA    alias Pleroma.Object.Fetcher    alias Pleroma.User -  alias Pleroma.Web.Auth.Authenticator    alias Pleroma.Web.Auth.TOTPAuthenticator +  alias Pleroma.Web.Auth.WrapperAuthenticator    alias Pleroma.Web.CommonAPI    @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] @@ -38,7 +38,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    defp follow_status(conn, _user, acct) do      with {:ok, object} <- Fetcher.fetch_object_from_id(acct),           %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do -      redirect(conn, to: o_status_path(conn, :notice, activity_id)) +      redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id))      else        error ->          handle_follow_error(conn, error) @@ -88,7 +88,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    #    def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do      with {_, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, -         {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee}, +         {_, {:ok, user}, _} <- {:auth, WrapperAuthenticator.get_user(conn), followee},           {_, _, _, false} <- {:mfa_required, followee, user, MFA.require?(user)},           {:ok, _, _, _} <- CommonAPI.follow(user, followee) do        redirect(conn, to: "/users/#{followee.id}") diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 940a645bb..ef43f7682 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -10,12 +10,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    alias Pleroma.Config    alias Pleroma.Emoji    alias Pleroma.Healthcheck -  alias Pleroma.Notification    alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.WebFinger +  plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)    plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)    plug( @@ -30,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do           ]    ) -  plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read) +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation    def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do      with %User{} = user <- User.get_cached_by_nickname(nick), @@ -62,17 +62,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do      end    end -  def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do -    with {:ok, _} <- Notification.read_one(user, notification_id) do -      json(conn, %{status: "success"}) -    else -      {:error, message} -> -        conn -        |> put_resp_content_type("application/json") -        |> send_resp(403, Jason.encode!(%{"error" => message})) -    end -  end -    def frontend_configurations(conn, _params) do      render(conn, "frontend_configurations.json")    end @@ -92,13 +81,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do      end    end -  def change_password(%{assigns: %{user: user}} = conn, params) do -    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do +  def change_password(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do +    case CommonAPI.Utils.confirm_current_password(user, body_params.password) do        {:ok, user} ->          with {:ok, _user} <-                 User.reset_password(user, %{ -                 password: params["new_password"], -                 password_confirmation: params["new_password_confirmation"] +                 password: body_params.new_password, +                 password_confirmation: body_params.new_password_confirmation                 }) do            json(conn, %{status: "success"})          else @@ -115,10 +104,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do      end    end -  def change_email(%{assigns: %{user: user}} = conn, params) do -    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do +  def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}) do +    case CommonAPI.Utils.confirm_current_password(user, body_params.password) do        {:ok, user} -> -        with {:ok, _user} <- User.change_email(user, params["email"]) do +        with {:ok, _user} <- User.change_email(user, body_params.email) do            json(conn, %{status: "success"})          else            {:error, changeset} -> @@ -135,7 +124,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def delete_account(%{assigns: %{user: user}} = conn, params) do -    password = params["password"] || "" +    password = params[:password] || ""      case CommonAPI.Utils.confirm_current_password(user, password) do        {:ok, user} -> @@ -148,7 +137,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def disable_account(%{assigns: %{user: user}} = conn, params) do -    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do +    case CommonAPI.Utils.confirm_current_password(user, params[:password]) do        {:ok, user} ->          User.set_activation_async(user, false)          json(conn, %{status: "success"}) diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index 9b13c09b3..87cb79dd7 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -6,14 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do    use Pleroma.Web, :view    import Phoenix.HTML.Form    alias Pleroma.Config -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    def status_net_config(instance) do      """      <config>      <site>      <name>#{Keyword.get(instance, :name)}</name> -    <site>#{Web.base_url()}</site> +    <site>#{Endpoint.url()}</site>      <textlimit>#{Keyword.get(instance, :limit)}</textlimit>      <closed>#{!Keyword.get(instance, :registrations_open)}</closed>      </site> diff --git a/lib/pleroma/web/utils/guards.ex b/lib/pleroma/web/utils/guards.ex new file mode 100644 index 000000000..aea7b6314 --- /dev/null +++ b/lib/pleroma/web/utils/guards.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Utils.Guards do +  @moduledoc """ +  Project-wide custom guards. +  See: https://hexdocs.pm/elixir/master/patterns-and-guards.html#custom-patterns-and-guards-expressions +  """ + +  @doc "Checks for non-empty string" +  defguard not_empty_string(string) when is_binary(string) and string != "" +end diff --git a/lib/pleroma/web/utils/params.ex b/lib/pleroma/web/utils/params.ex new file mode 100644 index 000000000..6e0900341 --- /dev/null +++ b/lib/pleroma/web/utils/params.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Utils.Params do +  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html +  @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] + +  defp explicitly_falsy_param?(value), do: value in @falsy_param_values + +  # Note: `nil` and `""` are considered falsy values in Pleroma +  defp falsy_param?(value), +    do: explicitly_falsy_param?(value) or value in [nil, ""] + +  def truthy_param?(value), do: not falsy_param?(value) +end diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex deleted file mode 100644 index b9055cb7f..000000000 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ /dev/null @@ -1,91 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MastoFEView do -  use Pleroma.Web, :view -  alias Pleroma.Config -  alias Pleroma.User -  alias Pleroma.Web.MastodonAPI.AccountView -  alias Pleroma.Web.MastodonAPI.CustomEmojiView - -  def initial_state(token, user, custom_emojis) do -    limit = Config.get([:instance, :limit]) - -    %{ -      meta: %{ -        streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(), -        access_token: token, -        locale: "en", -        domain: Pleroma.Web.Endpoint.host(), -        admin: "1", -        me: "#{user.id}", -        unfollow_modal: false, -        boost_modal: false, -        delete_modal: true, -        auto_play_gif: false, -        display_sensitive_media: false, -        reduce_motion: false, -        max_toot_chars: limit, -        mascot: User.get_mascot(user)["url"] -      }, -      poll_limits: Config.get([:instance, :poll_limits]), -      rights: %{ -        delete_others_notice: present?(user.is_moderator), -        admin: present?(user.is_admin) -      }, -      compose: %{ -        me: "#{user.id}", -        default_privacy: user.default_scope, -        default_sensitive: false, -        allow_content_types: Config.get([:instance, :allowed_post_formats]) -      }, -      media_attachments: %{ -        accept_content_types: [ -          ".jpg", -          ".jpeg", -          ".png", -          ".gif", -          ".webm", -          ".mp4", -          ".m4v", -          "image\/jpeg", -          "image\/png", -          "image\/gif", -          "video\/webm", -          "video\/mp4" -        ] -      }, -      settings: user.mastofe_settings || %{}, -      push_subscription: nil, -      accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)}, -      custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis), -      char_limit: limit -    } -    |> Jason.encode!() -    |> Phoenix.HTML.raw() -  end - -  defp present?(nil), do: false -  defp present?(false), do: false -  defp present?(_), do: true - -  def render("manifest.json", _params) do -    %{ -      name: Config.get([:instance, :name]), -      description: Config.get([:instance, :description]), -      icons: Config.get([:manifest, :icons]), -      theme_color: Config.get([:manifest, :theme_color]), -      background_color: Config.get([:manifest, :background_color]), -      display: "standalone", -      scope: Pleroma.Web.base_url(), -      start_url: masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]), -      categories: [ -        "social" -      ], -      serviceworker: %{ -        src: "/sw.js" -      } -    } -  end -end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index 21b10e654..938fc09e3 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.WebFinger do    alias Pleroma.HTTP    alias Pleroma.User -  alias Pleroma.Web +  alias Pleroma.Web.Endpoint    alias Pleroma.Web.Federator.Publisher    alias Pleroma.Web.XML    alias Pleroma.XmlBuilder @@ -13,7 +13,7 @@ defmodule Pleroma.Web.WebFinger do    require Logger    def host_meta do -    base_url = Web.base_url() +    base_url = Endpoint.url()      {        :XRD, diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 1e28384cb..4db077232 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -3,6 +3,7 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.BackgroundWorker do +  alias Pleroma.Instances.Instance    alias Pleroma.User    use Pleroma.Workers.WorkerHelper, queue: "background" @@ -38,4 +39,8 @@ defmodule Pleroma.Workers.BackgroundWorker do      Pleroma.FollowingRelationship.move_following(origin, target)    end + +  def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do +    Instance.perform(:delete_instance, host) +  end  end diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex new file mode 100644 index 000000000..3423cc889 --- /dev/null +++ b/lib/pleroma/workers/poll_worker.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PollWorker do +  @moduledoc """ +  Generates notifications when a poll ends. +  """ +  use Pleroma.Workers.WorkerHelper, queue: "poll_notifications" + +  alias Pleroma.Activity +  alias Pleroma.Notification +  alias Pleroma.Object + +  @impl Oban.Worker +  def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do +    with %Activity{} = activity <- find_poll_activity(activity_id) do +      Notification.create_poll_notifications(activity) +    end +  end + +  defp find_poll_activity(activity_id) do +    with nil <- Activity.get_by_id(activity_id) do +      {:error, :poll_activity_not_found} +    end +  end + +  def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do +    with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <- +           Object.normalize(activity), +         {:ok, end_time} <- NaiveDateTime.from_iso8601(closed), +         :gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do +      %{ +        op: "poll_end", +        activity_id: activity_id +      } +      |> new(scheduled_at: end_time) +      |> Oban.insert() +    else +      _ -> {:error, activity} +    end +  end + +  def schedule_poll_end(activity), do: {:error, activity} +end | 
