diff options
Diffstat (limited to 'lib')
48 files changed, 616 insertions, 187 deletions
| diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 9f0bf6ecb..074492a46 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -24,8 +24,10 @@ defmodule Mix.Pleroma do        Application.put_env(:logger, :console, level: :debug)      end +    adapter = Application.get_env(:tesla, :adapter) +      apps = -      if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do +      if adapter == Tesla.Adapter.Gun do          [:gun | @apps]        else          [:hackney | @apps] @@ -33,11 +35,14 @@ defmodule Mix.Pleroma do      Enum.each(apps, &Application.ensure_all_started/1) -    children = [ -      Pleroma.Repo, -      {Pleroma.Config.TransferTask, false}, -      Pleroma.Web.Endpoint -    ] +    children = +      [ +        Pleroma.Repo, +        {Pleroma.Config.TransferTask, false}, +        Pleroma.Web.Endpoint, +        {Oban, Pleroma.Config.get(Oban)} +      ] ++ +        http_children(adapter)      cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, [])) @@ -115,4 +120,11 @@ defmodule Mix.Pleroma do    def escape_sh_path(path) do      ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')    end + +  defp http_children(Tesla.Adapter.Gun) do +    Pleroma.Gun.ConnectionPool.children() ++ +      [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}] +  end + +  defp http_children(_), do: []  end diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 88575a498..16f62b6f5 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -16,7 +16,9 @@ defmodule Pleroma.ApplicationRequirements do    @spec verify!() :: :ok | VerifyError.t()    def verify! do      :ok +    |> check_confirmation_accounts!      |> check_migrations_applied!() +    |> check_welcome_message_config!()      |> check_rum!()      |> handle_result()    end @@ -24,6 +26,40 @@ defmodule Pleroma.ApplicationRequirements do    defp handle_result(:ok), do: :ok    defp handle_result({:error, message}), do: raise(VerifyError, message: message) +  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 +      """) + +      {:error, "The mail disabled."} +    else +      :ok +    end +  end + +  defp check_welcome_message_config!(result), do: result + +  # Checks account confirmation email +  # +  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.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer." +      ) + +      {:error, +       "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."} +    else +      :ok +    end +  end + +  def check_confirmation_accounts!(result), do: result +    # Checks for pending migrations.    #    def check_migrations_applied!(:ok) do diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 1a89d8895..e5b7811aa 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -156,7 +156,6 @@ defmodule Pleroma.ConfigDB do        {:quack, :meta},        {:mime, :types},        {:cors_plug, [:max_age, :methods, :expose, :headers]}, -      {:auto_linker, :opts},        {:swarm, :node_blacklist},        {:logger, :backends}      ] diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 026871c4f..0f52eb210 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -55,6 +55,24 @@ defmodule Pleroma.Config.DeprecationWarnings do      mrf_user_allowlist()      check_old_mrf_config()      check_media_proxy_whitelist_config() +    check_welcome_message_config() +  end + +  def check_welcome_message_config do +    instance_config = Pleroma.Config.get([:instance]) + +    use_old_config = +      Keyword.has_key?(instance_config, :welcome_user_nickname) or +        Keyword.has_key?(instance_config, :welcome_message) + +    if use_old_config do +      Logger.error(""" +      !!!DEPRECATION WARNING!!! +      Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace: +      \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` +      \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` +      """) +    end    end    def check_old_mrf_config do diff --git a/lib/pleroma/config/helpers.ex b/lib/pleroma/config/helpers.ex new file mode 100644 index 000000000..3dce40ea0 --- /dev/null +++ b/lib/pleroma/config/helpers.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Config.Helpers do +  alias Pleroma.Config + +  def instance_name, do: Config.get([:instance, :name]) + +  defp instance_notify_email do +    Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) +  end + +  def sender do +    {instance_name(), instance_notify_email()} +  end +end diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index aa0b2a66b..c27ad1065 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do    import Swoosh.Email    alias Pleroma.Config +  alias Pleroma.HTML    alias Pleroma.Web.Router.Helpers    defp instance_config, do: Config.get(:instance) @@ -82,4 +83,18 @@ defmodule Pleroma.Emails.AdminEmail do      |> subject("#{instance_name()} Report")      |> html_body(html_body)    end + +  def new_unapproved_registration(to, account) do +    html_body = """ +    <p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p> +    <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote> +    <a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a> +    """ + +    new() +    |> to({to.name, to.email}) +    |> from({instance_name(), instance_notify_email()}) +    |> subject("New account up for review on #{instance_name()} (@#{account.nickname})") +    |> html_body(html_body) +  end  end diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index dfadc10b3..313533859 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -12,17 +12,22 @@ defmodule Pleroma.Emails.UserEmail do    alias Pleroma.Web.Endpoint    alias Pleroma.Web.Router -  defp instance_name, do: Config.get([:instance, :name]) - -  defp sender do -    email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) -    {instance_name(), email} -  end +  import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]    defp recipient(email, nil), do: email    defp recipient(email, name), do: {name, email}    defp recipient(%User{} = user), do: recipient(user.email, user.name) +  @spec welcome(User.t(), map()) :: Swoosh.Email.t() +  def welcome(user, opts \\ %{}) do +    new() +    |> to(recipient(user)) +    |> from(Map.get(opts, :sender, sender())) +    |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!")) +    |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!")) +    |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!")) +  end +    def password_reset_email(user, token) when is_binary(token) do      password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 02a93a8dc..0c450eae4 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -10,11 +10,15 @@ defmodule Pleroma.Formatter do    @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui    @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ -  @auto_linker_config hashtag: true, -                      hashtag_handler: &Pleroma.Formatter.hashtag_handler/4, -                      mention: true, -                      mention_handler: &Pleroma.Formatter.mention_handler/4, -                      scheme: true +  defp linkify_opts do +    Pleroma.Config.get(Pleroma.Formatter) ++ +      [ +        hashtag: true, +        hashtag_handler: &Pleroma.Formatter.hashtag_handler/4, +        mention: true, +        mention_handler: &Pleroma.Formatter.mention_handler/4 +      ] +  end    def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do      case User.get_cached_by_nickname(nickname) do @@ -80,19 +84,19 @@ defmodule Pleroma.Formatter do    @spec linkify(String.t(), keyword()) ::            {String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}    def linkify(text, options \\ []) do -    options = options ++ @auto_linker_config +    options = linkify_opts() ++ options      if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do        %{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)        acc = %{mentions: MapSet.new(), tags: MapSet.new()} -      {text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options) -      {text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options) +      {text_mentions, %{mentions: mentions}} = Linkify.link_map(mentions, acc, options) +      {text_rest, %{tags: tags}} = Linkify.link_map(rest, acc, options)        {text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}      else        acc = %{mentions: MapSet.new(), tags: MapSet.new()} -      {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options) +      {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options)        {text, MapSet.to_list(mentions), MapSet.to_list(tags)}      end @@ -111,9 +115,9 @@ defmodule Pleroma.Formatter do      if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do        %{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text) -      AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options) +      Linkify.link(mentions, options) <> Linkify.link(rest, options)      else -      AutoLinker.link(text, options) +      Linkify.link(text, options)      end    end diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex index 8b41a668c..49e9885bb 100644 --- a/lib/pleroma/gun/connection_pool.ex +++ b/lib/pleroma/gun/connection_pool.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Gun.ConnectionPool do          get_gun_pid_from_worker(worker_pid, true)        [{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] -> -        GenServer.cast(worker_pid, {:add_client, self(), false}) +        GenServer.call(worker_pid, :add_client)          {:ok, gun_pid}        [] -> @@ -45,7 +45,7 @@ defmodule Pleroma.Gun.ConnectionPool do      # so instead we use cast + monitor      ref = Process.monitor(worker_pid) -    if register, do: GenServer.cast(worker_pid, {:add_client, self(), true}) +    if register, do: GenServer.cast(worker_pid, {:add_client, self()})      receive do        {:conn_pid, pid} -> @@ -70,7 +70,7 @@ defmodule Pleroma.Gun.ConnectionPool do      case query_result do        [worker_pid] -> -        GenServer.cast(worker_pid, {:remove_client, self()}) +        GenServer.call(worker_pid, :remove_client)        [] ->          :ok diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index f33447cb6..fec9d0efa 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -36,7 +36,24 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do    end    @impl true -  def handle_cast({:add_client, client_pid, send_pid_back}, %{key: key} = state) do +  def handle_cast({:add_client, client_pid}, state) do +    case handle_call(:add_client, {client_pid, nil}, state) do +      {:reply, conn_pid, state, :hibernate} -> +        send(client_pid, {:conn_pid, conn_pid}) +        {:noreply, state, :hibernate} +    end +  end + +  @impl true +  def handle_cast({:remove_client, client_pid}, state) do +    case handle_call(:remove_client, {client_pid, nil}, state) do +      {:reply, _, state, :hibernate} -> +        {:noreply, state, :hibernate} +    end +  end + +  @impl true +  def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do      time = :erlang.monotonic_time(:millisecond)      {{conn_pid, _, _, _}, _} = @@ -44,8 +61,6 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do          {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}        end) -    if send_pid_back, do: send(client_pid, {:conn_pid, conn_pid}) -      state =        if state.timer != nil do          Process.cancel_timer(state[:timer]) @@ -57,11 +72,11 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do      ref = Process.monitor(client_pid)      state = put_in(state.client_monitors[client_pid], ref) -    {:noreply, state, :hibernate} +    {:reply, conn_pid, state, :hibernate}    end    @impl true -  def handle_cast({:remove_client, client_pid}, %{key: key} = state) do +  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} ->          {conn_pid, List.delete(used_by, client_pid), crf, last_reference} @@ -78,7 +93,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do          nil        end -    {:noreply, %{state | timer: timer}, :hibernate} +    {:reply, :ok, %{state | timer: timer}, :hibernate}    end    @impl true @@ -102,22 +117,13 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do    @impl true    def handle_info({:DOWN, _ref, :process, pid, reason}, state) do -    # Sometimes the client is dead before we demonitor it in :remove_client, so the message -    # arrives anyway +    :telemetry.execute( +      [:pleroma, :connection_pool, :client_death], +      %{client_pid: pid, reason: reason}, +      %{key: state.key} +    ) -    case state.client_monitors[pid] do -      nil -> -        {:noreply, state, :hibernate} - -      _ref -> -        :telemetry.execute( -          [:pleroma, :connection_pool, :client_death], -          %{client_pid: pid, reason: reason}, -          %{key: state.key} -        ) - -        handle_cast({:remove_client, pid}, state) -    end +    handle_cast({:remove_client, pid}, state)    end    # LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478 diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 7aacd9d80..31c9afe2a 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -413,6 +413,17 @@ defmodule Pleroma.ModerationLog do    def get_log_entry_message(%ModerationLog{          data: %{            "actor" => %{"nickname" => actor_nickname}, +          "action" => "approve", +          "subject" => users +        } +      }) do +    "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" +  end + +  @spec get_log_entry_message(ModerationLog) :: String.t() +  def get_log_entry_message(%ModerationLog{ +        data: %{ +          "actor" => %{"nickname" => actor_nickname},            "nicknames" => nicknames,            "tags" => tags,            "action" => "tag" diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 3e2949ee2..e74c87269 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -124,6 +124,10 @@ defmodule Pleroma.Object.Fetcher do        {:error, "Object has been deleted"} ->          nil +      {:reject, reason} -> +        Logger.info("Rejected #{id} while fetching: #{inspect(reason)}") +        nil +        e ->          Logger.error("Error while fetching #{id}: #{inspect(e)}")          nil diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/plugs/frontend_static.ex new file mode 100644 index 000000000..f549ca75f --- /dev/null +++ b/lib/pleroma/plugs/frontend_static.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.FrontendStatic do +  require Pleroma.Constants + +  @moduledoc """ +  This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends. +  """ +  @behaviour Plug + +  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") + +      Path.join([ +        instance_static_path, +        "frontends", +        configuration["name"], +        configuration["ref"], +        path +      ]) +    else +      nil +    end +  end + +  def init(opts) do +    opts +    |> Keyword.put(:from, "__unconfigured_frontend_static_plug") +    |> Plug.Static.init() +  end + +  def call(conn, opts) do +    frontend_type = Map.get(opts, :frontend_type, :primary) +    path = file_path("", frontend_type) + +    if path do +      conn +      |> call_static(opts, path) +    else +      conn +    end +  end + +  defp call_static(conn, opts, from) do +    opts = +      opts +      |> Map.put(:from, from) + +    Plug.Static.call(conn, opts) +  end +end diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex index 7516f75c3..0fb57e422 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/plugs/instance_static.ex @@ -16,28 +16,24 @@ defmodule Pleroma.Plugs.InstanceStatic do      instance_path =        Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) -    if File.exists?(instance_path) do -      instance_path -    else +    frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary) + +    (File.exists?(instance_path) && instance_path) || +      (frontend_path && File.exists?(frontend_path) && frontend_path) ||        Path.join(Application.app_dir(:pleroma, "priv/static/"), path) -    end    end    def init(opts) do      opts      |> Keyword.put(:from, "__unconfigured_instance_static_plug") -    |> Keyword.put(:at, "/__unconfigured_instance_static_plug")      |> Plug.Static.init()    end    for only <- Pleroma.Constants.static_only_files() do -    at = Plug.Router.Utils.split("/") -      def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do        call_static(          conn,          opts, -        unquote(at),          Pleroma.Config.get([:instance, :static_dir], "instance/static")        )      end @@ -47,11 +43,10 @@ defmodule Pleroma.Plugs.InstanceStatic do      conn    end -  defp call_static(conn, opts, at, from) do +  defp call_static(conn, opts, from) do      opts =        opts        |> Map.put(:from, from) -      |> Map.put(:at, at)      Plug.Static.call(conn, opts)    end diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex index 65785445d..d5a339681 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -5,6 +5,8 @@  defmodule Pleroma.ReverseProxy.Client.Tesla do    @behaviour Pleroma.ReverseProxy.Client +  alias Pleroma.Gun.ConnectionPool +    @type headers() :: [{String.t(), String.t()}]    @type status() :: pos_integer() @@ -31,6 +33,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do        if is_map(response.body) and method != :head do          {:ok, response.status, response.headers, response.body}        else +        conn_pid = response.opts[:adapter][:conn] +        ConnectionPool.release_conn(conn_pid)          {:ok, response.status, response.headers}        end      else @@ -41,15 +45,8 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do    @impl true    @spec stream_body(map()) ::            {:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return() -  def stream_body(%{pid: pid, opts: opts, fin: true}) do -    # if connection was reused, but in tesla were redirects, -    # tesla returns new opened connection, which must be closed manually -    if opts[:old_conn], do: Tesla.Adapter.Gun.close(pid) -    # if there were redirects we need to checkout old conn -    conn = opts[:old_conn] || opts[:conn] - -    if conn, do: :ok = Pleroma.Gun.ConnectionPool.release_conn(conn) - +  def stream_body(%{pid: pid, fin: true}) do +    ConnectionPool.release_conn(pid)      :done    end @@ -74,8 +71,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do    @impl true    @spec close(map) :: :ok | no_return()    def close(%{pid: pid}) do -    adapter = check_adapter() -    adapter.close(pid) +    ConnectionPool.release_conn(pid)    end    defp check_adapter do diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 28ad4c846..0de4e2309 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -165,6 +165,9 @@ defmodule Pleroma.ReverseProxy do        {:ok, code, _, _} ->          {:error, {:invalid_http_response, code}} +      {:ok, code, _} -> +        {:error, {:invalid_http_response, code}} +        {:error, error} ->          {:error, error}      end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9240e912d..dcf6ebee2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -42,7 +42,12 @@ defmodule Pleroma.User do    require Logger    @type t :: %__MODULE__{} -  @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending +  @type account_status :: +          :active +          | :deactivated +          | :password_reset_pending +          | :confirmation_pending +          | :approval_pending    @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}    # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @@ -106,6 +111,8 @@ defmodule Pleroma.User do      field(:locked, :boolean, default: false)      field(:confirmation_pending, :boolean, default: false)      field(:password_reset_pending, :boolean, default: false) +    field(:approval_pending, :boolean, default: false) +    field(:registration_reason, :string, default: nil)      field(:confirmation_token, :string, default: nil)      field(:default_scope, :string, default: "public")      field(:domain_blocks, {:array, :string}, default: []) @@ -262,6 +269,7 @@ defmodule Pleroma.User do    @spec account_status(User.t()) :: account_status()    def account_status(%User{deactivated: true}), do: :deactivated    def account_status(%User{password_reset_pending: true}), do: :password_reset_pending +  def account_status(%User{approval_pending: true}), do: :approval_pending    def account_status(%User{confirmation_pending: true}) do      if Config.get([:instance, :account_activation_required]) do @@ -633,6 +641,7 @@ defmodule Pleroma.User do    def register_changeset(struct, params \\ %{}, opts \\ []) do      bio_limit = Config.get([:instance, :user_bio_length], 5000)      name_limit = Config.get([:instance, :user_name_length], 100) +    reason_limit = Config.get([:instance, :registration_reason_length], 500)      params = Map.put_new(params, :accepts_chat_messages, true)      need_confirmation? = @@ -642,8 +651,16 @@ defmodule Pleroma.User do          opts[:need_confirmation]        end +    need_approval? = +      if is_nil(opts[:need_approval]) do +        Config.get([:instance, :account_approval_required]) +      else +        opts[:need_approval] +      end +      struct      |> confirmation_changeset(need_confirmation: need_confirmation?) +    |> approval_changeset(need_approval: need_approval?)      |> cast(params, [        :bio,        :raw_bio, @@ -653,7 +670,8 @@ defmodule Pleroma.User do        :password,        :password_confirmation,        :emoji, -      :accepts_chat_messages +      :accepts_chat_messages, +      :registration_reason      ])      |> validate_required([:name, :nickname, :password, :password_confirmation])      |> validate_confirmation(:password) @@ -664,6 +682,7 @@ defmodule Pleroma.User do      |> validate_format(:email, @email_regex)      |> validate_length(:bio, max: bio_limit)      |> validate_length(:name, min: 1, max: name_limit) +    |> validate_length(:registration_reason, max: reason_limit)      |> maybe_validate_required_email(opts[:external])      |> put_password_hash      |> put_ap_id() @@ -713,27 +732,52 @@ defmodule Pleroma.User do    def post_register_action(%User{} = user) do      with {:ok, user} <- autofollow_users(user),           {:ok, user} <- set_cache(user), -         {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user), +         {:ok, _} <- send_welcome_email(user), +         {:ok, _} <- send_welcome_message(user),           {:ok, _} <- try_send_confirmation_email(user) do        {:ok, user}      end    end -  def try_send_confirmation_email(%User{} = user) do -    if user.confirmation_pending && -         Config.get([:instance, :account_activation_required]) do -      user -      |> Pleroma.Emails.UserEmail.account_confirmation_email() -      |> Pleroma.Emails.Mailer.deliver_async() +  def send_welcome_message(user) do +    if User.WelcomeMessage.enabled?() do +      User.WelcomeMessage.post_message(user) +      {:ok, :enqueued} +    else +      {:ok, :noop} +    end +  end + +  def send_welcome_email(%User{email: email} = user) when is_binary(email) do +    if User.WelcomeEmail.enabled?() do +      User.WelcomeEmail.send_email(user) +      {:ok, :enqueued} +    else +      {:ok, :noop} +    end +  end + +  def send_welcome_email(_), do: {:ok, :noop} +  @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} +  def try_send_confirmation_email(%User{confirmation_pending: true} = user) do +    if Config.get([:instance, :account_activation_required]) do +      send_confirmation_email(user)        {:ok, :enqueued}      else        {:ok, :noop}      end    end -  def try_send_confirmation_email(users) do -    Enum.each(users, &try_send_confirmation_email/1) +  def try_send_confirmation_email(_), do: {:ok, :noop} + +  @spec send_confirmation_email(Uset.t()) :: User.t() +  def send_confirmation_email(%User{} = user) do +    user +    |> Pleroma.Emails.UserEmail.account_confirmation_email() +    |> Pleroma.Emails.Mailer.deliver_async() + +    user    end    def needs_update?(%User{local: true}), do: false @@ -1469,6 +1513,19 @@ defmodule Pleroma.User do      end    end +  def approve(users) when is_list(users) do +    Repo.transaction(fn -> +      Enum.map(users, fn user -> +        with {:ok, user} <- approve(user), do: user +      end) +    end) +  end + +  def approve(%User{} = user) do +    change(user, approval_pending: false) +    |> update_and_set_cache() +  end +    def update_notification_settings(%User{} = user, settings) do      user      |> cast(%{notification_settings: settings}, []) @@ -1495,12 +1552,17 @@ defmodule Pleroma.User do    defp delete_or_deactivate(%User{local: true} = user) do      status = account_status(user) -    if status == :confirmation_pending do -      delete_and_invalidate_cache(user) -    else -      user -      |> change(%{deactivated: true, email: nil}) -      |> update_and_set_cache() +    case status do +      :confirmation_pending -> +        delete_and_invalidate_cache(user) + +      :approval_pending -> +        delete_and_invalidate_cache(user) + +      _ -> +        user +        |> change(%{deactivated: true, email: nil}) +        |> update_and_set_cache()      end    end @@ -2153,6 +2215,12 @@ defmodule Pleroma.User do      cast(user, params, [:confirmation_pending, :confirmation_token])    end +  @spec approval_changeset(User.t(), keyword()) :: Changeset.t() +  def approval_changeset(user, need_approval: need_approval?) do +    params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false} +    cast(user, params, [:approval_pending]) +  end +    def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do      if id not in user.pinned_activities do        max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 66ffe9090..45553cb6c 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do              external: boolean(),              active: boolean(),              deactivated: boolean(), +            need_approval: boolean(),              is_admin: boolean(),              is_moderator: boolean(),              super_users: boolean(), @@ -146,6 +147,10 @@ defmodule Pleroma.User.Query do      |> where([u], not is_nil(u.nickname))    end +  defp compose_query({:need_approval, _}, query) do +    where(query, [u], u.approval_pending) +  end +    defp compose_query({:followers, %User{id: id}}, query) do      query      |> where([u], u.id != ^id) diff --git a/lib/pleroma/user/welcome_email.ex b/lib/pleroma/user/welcome_email.ex new file mode 100644 index 000000000..5322000d4 --- /dev/null +++ b/lib/pleroma/user/welcome_email.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeEmail do +  @moduledoc """ +  The module represents the functions to send welcome email. +  """ + +  alias Pleroma.Config +  alias Pleroma.Emails +  alias Pleroma.User + +  import Pleroma.Config.Helpers, only: [instance_name: 0] + +  @spec enabled?() :: boolean() +  def enabled?, do: Config.get([:welcome, :email, :enabled], false) + +  @spec send_email(User.t()) :: {:ok, Oban.Job.t()} +  def send_email(%User{} = user) do +    user +    |> Emails.UserEmail.welcome(email_options(user)) +    |> Emails.Mailer.deliver_async() +  end + +  defp email_options(user) do +    bindings = [user: user, instance_name: instance_name()] + +    %{} +    |> add_sender(Config.get([:welcome, :email, :sender], nil)) +    |> add_option(:subject, bindings) +    |> add_option(:html, bindings) +    |> add_option(:text, bindings) +  end + +  defp add_option(opts, option, bindings) do +    [:welcome, :email, option] +    |> Config.get(nil) +    |> eval_string(bindings) +    |> merge_options(opts, option) +  end + +  defp add_sender(opts, {_name, _email} = sender) do +    merge_options(sender, opts, :sender) +  end + +  defp add_sender(opts, sender) when is_binary(sender) do +    add_sender(opts, {instance_name(), sender}) +  end + +  defp add_sender(opts, _), do: opts + +  defp merge_options(nil, options, _option), do: options + +  defp merge_options(value, options, option) do +    Map.merge(options, %{option => value}) +  end + +  defp eval_string(nil, _), do: nil +  defp eval_string("", _), do: nil +  defp eval_string(str, bindings), do: EEx.eval_string(str, bindings) +end diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex index f8f520285..86e1c0678 100644 --- a/lib/pleroma/user/welcome_message.ex +++ b/lib/pleroma/user/welcome_message.ex @@ -3,32 +3,45 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User.WelcomeMessage do +  alias Pleroma.Config    alias Pleroma.User    alias Pleroma.Web.CommonAPI -  def post_welcome_message_to_user(user) do -    with %User{} = sender_user <- welcome_user(), -         message when is_binary(message) <- welcome_message() do -      CommonAPI.post(sender_user, %{ +  @spec enabled?() :: boolean() +  def enabled?, do: Config.get([:welcome, :direct_message, :enabled], false) + +  @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil} +  def post_message(user) do +    [:welcome, :direct_message, :sender_nickname] +    |> Config.get(nil) +    |> fetch_sender() +    |> do_post(user, welcome_message()) +  end + +  defp do_post(%User{} = sender, %User{nickname: nickname}, message) +       when is_binary(message) do +    CommonAPI.post( +      sender, +      %{          visibility: "direct", -        status: "@#{user.nickname}\n#{message}" -      }) -    else -      _ -> {:ok, nil} -    end +        status: "@#{nickname}\n#{message}" +      } +    )    end -  defp welcome_user do -    with nickname when is_binary(nickname) <- -           Pleroma.Config.get([:instance, :welcome_user_nickname]), -         %User{local: true} = user <- User.get_cached_by_nickname(nickname) do +  defp do_post(_sender, _recipient, _message), do: {:ok, nil} + +  defp fetch_sender(nickname) when is_binary(nickname) do +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do        user      else        _ -> nil      end    end +  defp fetch_sender(_), do: nil +    defp welcome_message do -    Pleroma.Config.get([:instance, :welcome_message]) +    Config.get([:welcome, :direct_message, :message], nil)    end  end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index bc7b5d95a..a4db1d87c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1370,6 +1370,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")          {:error, e} +      {:error, {:reject, reason} = e} -> +        Logger.info("Rejected user #{ap_id}: #{inspect(reason)}") +        {:error, e} +        {:error, e} ->          Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")          {:error, e} 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 2627a0007..3bf70b894 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -27,7 +27,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do    def filter_by_summary(_in_reply_to, child), do: child -  def filter(%{"type" => "Create", "object" => child_object} = object) do +  def filter(%{"type" => "Create", "object" => child_object} = object) +      when is_map(child_object) do      child =        child_object["inReplyTo"]        |> Object.normalize(child_object["inReplyTo"]) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index df926829c..0dcc7be4d 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    the system.    """ +  alias Pleroma.Activity    alias Pleroma.EctoType.ActivityPub.ObjectValidators    alias Pleroma.Object    alias Pleroma.User @@ -71,6 +72,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do             |> UndoValidator.cast_and_validate()             |> Ecto.Changeset.apply_action(:insert) do        object = stringify_keys(object) +      undone_object = Activity.get_by_ap_id(object["object"]) + +      meta = +        meta +        |> Keyword.put(:object_data, undone_object.data) +        {:ok, object, meta}      end    end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 6875c47f6..36e325c37 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -52,6 +52,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do        do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])        if !do_not_federate && local do +        activity = +          if object = Keyword.get(meta, :object_data) do +            %{activity | data: Map.put(activity.data, "object", object)} +          else +            activity +          end +          Federator.publish(activity)          {:ok, :federated}        else diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f37bcab3e..35aa05eb5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -178,7 +178,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          |> Map.drop(["conversation"])        else          e -> -          Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") +          Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")            object        end      else diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index dfae602df..713b0ca1f 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -719,15 +719,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do      case Activity.get_by_ap_id_with_object(id) do        %Activity{} = activity -> +        activity_actor = User.get_by_ap_id(activity.object.data["actor"]) +          %{            "type" => "Note",            "id" => activity.data["id"],            "content" => activity.object.data["content"],            "published" => activity.object.data["published"],            "actor" => -            AccountView.render("show.json", %{ -              user: User.get_by_ap_id(activity.object.data["actor"]) -            }) +            AccountView.render( +              "show.json", +              %{user: activity_actor, skip_visibility_check: true} +            )          }        _ -> 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 e5f14269a..aa2af1ab5 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do             :user_toggle_activation,             :user_activate,             :user_deactivate, +           :user_approve,             :tag_users,             :untag_users,             :right_add, @@ -303,6 +304,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      |> render("index.json", %{users: Keyword.values(updated_users)})    end +  def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) +    {:ok, updated_users} = User.approve(users) + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: users, +      action: "approve" +    }) + +    conn +    |> put_view(AccountView) +    |> render("index.json", %{users: updated_users}) +  end +    def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do      with {:ok, _} <- User.tag(nicknames, tags) do        ModerationLog.insert_log(%{ @@ -345,12 +361,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do        json(          conn, -        AccountView.render("index.json", users: users, count: count, page_size: page_size) +        AccountView.render("index.json", +          users: users, +          count: count, +          page_size: page_size +        )        )      end    end -  @filters ~w(local external active deactivated is_admin is_moderator) +  @filters ~w(local external active deactivated need_approval is_admin is_moderator)    @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}    defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} @@ -616,29 +636,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1)      User.toggle_confirmation(users) -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "confirm_email" -    }) +    ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})      json(conn, "")    end    def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - -    User.try_send_confirmation_email(users) +    users = +      Enum.map(nicknames, fn nickname -> +        nickname +        |> User.get_cached_by_nickname() +        |> User.send_confirmation_email() +      end) -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "resend_confirmation_email" -    }) +    ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})      json(conn, "")    end diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index e1e929632..333e72e42 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -77,7 +77,9 @@ defmodule Pleroma.Web.AdminAPI.AccountView do        "roles" => User.roles(user),        "tags" => user.tags || [],        "confirmation_pending" => user.confirmation_pending, -      "url" => user.uri || user.ap_id +      "approval_pending" => user.approval_pending, +      "url" => user.uri || user.ap_id, +      "registration_reason" => user.registration_reason      }    end @@ -105,7 +107,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do    end    def merge_account_views(%User{} = user) do -    MastodonAPI.AccountView.render("show.json", %{user: user}) +    MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true})      |> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user}))    end diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index cf299bfc2..b1a0d26ab 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -300,11 +300,11 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do            "content" => "Check this out :firefox:",            "id" => "13",            "chat_id" => "1", -          "actor_id" => "someflakeid", +          "account_id" => "someflakeid",            "unread" => false          },          %{ -          "actor_id" => "someflakeid", +          "account_id" => "someflakeid",            "content" => "Whats' up?",            "id" => "12",            "chat_id" => "1", diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 049bcf931..1e0da8209 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -31,6 +31,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do      }    end +  # Supporting domain query parameter is deprecated in Mastodon API    def create_operation do      %Operation{        tags: ["domain_blocks"], @@ -45,11 +46,13 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do        """,        operationId: "DomainBlockController.create",        requestBody: domain_block_request(), +      parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],        security: [%{"oAuth" => ["follow", "write:blocks"]}],        responses: %{200 => empty_object_response()}      }    end +  # Supporting domain query parameter is deprecated in Mastodon API    def delete_operation do      %Operation{        tags: ["domain_blocks"], @@ -57,6 +60,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do        description: "Remove a domain block, if it exists in the user's array of blocked domains.",        operationId: "DomainBlockController.delete",        requestBody: domain_block_request(), +      parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],        security: [%{"oAuth" => ["follow", "write:blocks"]}],        responses: %{          200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -71,10 +75,9 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do          type: :object,          properties: %{            domain: %Schema{type: :string} -        }, -        required: [:domain] +        }        }, -      required: true, +      required: false,        example: %{          "domain" => "facebook.com"        } diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index bce27897f..3b1469c19 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -4,8 +4,10 @@  defmodule Pleroma.Web.ChatChannel do    use Phoenix.Channel +    alias Pleroma.User    alias Pleroma.Web.ChatChannel.ChatChannelState +  alias Pleroma.Web.MastodonAPI.AccountView    def join("chat:public", _message, socket) do      send(self(), :after_join) @@ -22,9 +24,9 @@ defmodule Pleroma.Web.ChatChannel do      if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do        author = User.get_cached_by_nickname(user_name) -      author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) +      author_json = AccountView.render("show.json", user: author, skip_visibility_check: true) -      message = ChatChannelState.add_message(%{text: text, author: author}) +      message = ChatChannelState.add_message(%{text: text, author: author_json})        broadcast!(socket, "new_msg", message)      end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 226d42c2c..527fb288d 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -28,6 +28,17 @@ defmodule Pleroma.Web.Endpoint do      }    ) +  # Careful! No `only` restriction here, as we don't know what frontends contain. +  plug(Pleroma.Plugs.FrontendStatic, +    at: "/", +    frontend_type: :primary, +    gzip: true, +    cache_control_for_etags: @static_cache_control, +    headers: %{ +      "cache-control" => @static_cache_control +    } +  ) +    # Serve at "/" the static files from "priv/static" directory.    #    # You should set gzip to true if you are running phoenix.digest diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index d56f43818..9cd334a33 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -47,7 +47,7 @@ defmodule Pleroma.Web.Feed.UserController do          "atom"        end -    with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do +    with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do        activities =          %{            type: ["Create"], @@ -71,6 +71,7 @@ defmodule Pleroma.Web.Feed.UserController do      render_error(conn, :not_found, "Not found")    end +  def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})    def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})    def errors(conn, _) do diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 825b231ab..9c2d093cd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -32,9 +32,19 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do      json(conn, %{})    end +  def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do +    User.block_domain(blocker, domain) +    json(conn, %{}) +  end +    @doc "DELETE /api/v1/domain_blocks"    def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do      User.unblock_domain(blocker, domain)      json(conn, %{})    end + +  def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do +    User.unblock_domain(blocker, domain) +    json(conn, %{}) +  end  end diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 29affa7d5..5a983db39 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -93,7 +93,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do      AccountView.render("index.json",        users: accounts,        for: options[:for_user], -      as: :user,        embed_relationships: options[:embed_relationships]      )    end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bc9745044..864c0417f 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -27,21 +27,40 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do            UserRelationship.view_relationships_option(reading_user, users)        end -    opts = Map.put(opts, :relationships, relationships_opt) +    opts = +      opts +      |> Map.merge(%{relationships: relationships_opt, as: :user}) +      |> Map.delete(:users)      users      |> render_many(AccountView, "show.json", opts)      |> Enum.filter(&Enum.any?/1)    end -  def render("show.json", %{user: user} = opts) do -    if User.visible_for(user, opts[:for]) == :visible do +  @doc """ +  Renders specified user account. +    :skip_visibility_check option skips visibility check and renders any user (local or remote) +      regardless of [:pleroma, :restrict_unauthenticated] setting. +    :for option specifies the requester and can be a User record or nil. +      Only use `user: user, for: user` when `user` is the actual requester of own profile. +  """ +  def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do +    do_render("show.json", opts) +  end + +  def render("show.json", %{user: user, for: for_user_or_nil} = opts) do +    if User.visible_for(user, for_user_or_nil) == :visible do        do_render("show.json", opts)      else        %{}      end    end +  def render("show.json", _) do +    raise "In order to prevent account accessibility issues, " <> +            ":skip_visibility_check or :for option is required." +  end +    def render("mention.json", %{user: user}) do      %{        id: to_string(user.id), diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 06f0c1728..a91994915 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -38,7 +38,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do      %{        id: participation.id |> to_string(), -      accounts: render(AccountView, "index.json", users: users, as: :user), +      accounts: render(AccountView, "index.json", users: users, for: user),        unread: !participation.read,        last_status:          render(StatusView, "show.json", diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index cd3bc7f00..ea2d3aa9c 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -26,6 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do        thumbnail: Keyword.get(instance, :instance_thumbnail),        languages: ["en"],        registrations: Keyword.get(instance, :registrations_open), +      approval_required: Keyword.get(instance, :account_approval_required),        # Extra (not present in Mastodon):        max_toot_chars: Keyword.get(instance, :limit),        poll_limits: Keyword.get(instance, :poll_limits), diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index fa9d695f3..91b41ef59 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -297,13 +297,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      emoji_reactions =        with %{data: %{"reactions" => emoji_reactions}} <- object do -        Enum.map(emoji_reactions, fn [emoji, users] -> -          %{ -            name: emoji, -            count: length(users), -            me: !!(opts[:for] && opts[:for].ap_id in users) -          } +        Enum.map(emoji_reactions, fn +          [emoji, users] when is_list(users) -> +            build_emoji_map(emoji, users, opts[:for]) + +          {emoji, users} when is_list(users) -> +            build_emoji_map(emoji, users, opts[:for]) + +          _ -> +            nil          end) +        |> Enum.reject(&is_nil/1)        else          _ -> []        end @@ -545,4 +549,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),      do: id in pinned_activities + +  defp build_emoji_map(emoji, users, current_user) do +    %{ +      name: emoji, +      count: length(users), +      me: !!(current_user && current_user.ap_id in users) +    } +  end  end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 7683589cf..61fe81d33 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -337,6 +337,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do      )    end +  defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do +    render_error( +      conn, +      :forbidden, +      "Your account is awaiting approval.", +      %{}, +      "awaiting_approval" +    ) +  end +    defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do      render_invalid_credentials_error(conn)    end diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index c8ef3d915..e8a1746d4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -89,11 +89,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do           cm_ref <- MessageReference.for_chat_and_object(chat, message) do        conn        |> put_view(MessageReferenceView) -      |> render("show.json", for: user, chat_message_reference: cm_ref) +      |> render("show.json", chat_message_reference: cm_ref)      end    end -  def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ +  def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{          id: chat_id,          message_id: message_id        }) do @@ -104,12 +104,15 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do           {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do        conn        |> put_view(MessageReferenceView) -      |> render("show.json", for: user, chat_message_reference: cm_ref) +      |> render("show.json", chat_message_reference: cm_ref)      end    end    def mark_as_read( -        %{body_params: %{last_read_id: last_read_id}, assigns: %{user: %{id: user_id}}} = conn, +        %{ +          body_params: %{last_read_id: last_read_id}, +          assigns: %{user: %{id: user_id}} +        } = conn,          %{id: id}        ) do      with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), @@ -121,7 +124,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = params) do +  def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do      with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do        cm_refs =          chat @@ -130,7 +133,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do        conn        |> put_view(MessageReferenceView) -      |> render("index.json", for: user, chat_message_references: cm_refs) +      |> render("index.json", chat_message_references: cm_refs)      else        _ ->          conn 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 33ecd1f70..657f46324 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -21,8 +21,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do           ]    ) -  @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug] -  plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list]) +  @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] +  plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive])    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation diff --git a/lib/pleroma/web/pleroma_api/views/chat_view.ex b/lib/pleroma/web/pleroma_api/views/chat_view.ex index 1c996da11..04dc20d51 100644 --- a/lib/pleroma/web/pleroma_api/views/chat_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat_view.ex @@ -15,10 +15,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do    def render("show.json", %{chat: %Chat{} = chat} = opts) do      recipient = User.get_cached_by_ap_id(chat.recipient)      last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat) +    account_view_opts = account_view_opts(opts, recipient)      %{        id: chat.id |> to_string(), -      account: AccountView.render("show.json", Map.put(opts, :user, recipient)), +      account: AccountView.render("show.json", account_view_opts),        unread: MessageReference.unread_count_for_chat(chat),        last_message:          last_message && @@ -27,7 +28,17 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do      }    end -  def render("index.json", %{chats: chats}) do -    render_many(chats, __MODULE__, "show.json") +  def render("index.json", %{chats: chats} = opts) do +    render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats)) +  end + +  defp account_view_opts(opts, recipient) do +    account_view_opts = Map.put(opts, :user, recipient) + +    if Map.has_key?(account_view_opts, :for) do +      account_view_opts +    else +      Map.put(account_view_opts, :skip_visibility_check, true) +    end    end  end diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index 84d2d303d..e0f98b50a 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do      %{        name: emoji,        count: length(users), -      accounts: render(AccountView, "index.json", users: users, for: user, as: :user), +      accounts: render(AccountView, "index.json", users: users, for: user),        me: !!(user && user.ap_id in user_ap_ids)      }    end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 1729141e9..747f2dc6b 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -11,10 +11,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do    @spec validate_page_url(URI.t() | binary()) :: :ok | :error    defp validate_page_url(page_url) when is_binary(page_url) do -    validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld] +    validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])      page_url -    |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld) +    |> Linkify.Parser.url?(validate_tld: validate_tld)      |> parse_uri(page_url)    end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 386308362..c6433cc53 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -138,6 +138,7 @@ defmodule Pleroma.Web.Router do      patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)      patch("/users/activate", AdminAPIController, :user_activate)      patch("/users/deactivate", AdminAPIController, :user_deactivate) +    patch("/users/approve", AdminAPIController, :user_approve)      put("/users/tag", AdminAPIController, :tag_users)      delete("/users/tag", AdminAPIController, :untag_users) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 5cfb385ac..2294d9d0d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do        |> Map.put(:nickname, params[:username])        |> Map.put(:name, Map.get(params, :fullname, params[:username]))        |> Map.put(:password_confirmation, params[:password]) +      |> Map.put(:registration_reason, params[:reason])      if Pleroma.Config.get([:instance, :registrations_open]) do        create_user(params, opts) @@ -44,6 +45,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      case User.register(changeset) do        {:ok, user} -> +        maybe_notify_admins(user)          {:ok, user}        {:error, changeset} -> @@ -56,6 +58,18 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      end    end +  defp maybe_notify_admins(%User{} = account) do +    if Pleroma.Config.get([:instance, :account_approval_required]) do +      User.all_superusers() +      |> Enum.filter(fn user -> not is_nil(user.email) end) +      |> Enum.each(fn superuser -> +        superuser +        |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account) +        |> Pleroma.Emails.Mailer.deliver_async() +      end) +    end +  end +    def password_reset(nickname_or_email) do      with true <- is_binary(nickname_or_email),           %User{local: true, email: email} = user when is_binary(email) <- diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex index f739dacb6..b1669d198 100644 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ b/lib/pleroma/web/views/masto_fe_view.ex @@ -9,36 +9,6 @@ defmodule Pleroma.Web.MastoFEView do    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.CustomEmojiView -  @default_settings %{ -    onboarded: true, -    home: %{ -      shows: %{ -        reblog: true, -        reply: true -      } -    }, -    notifications: %{ -      alerts: %{ -        follow: true, -        favourite: true, -        reblog: true, -        mention: true -      }, -      shows: %{ -        follow: true, -        favourite: true, -        reblog: true, -        mention: true -      }, -      sounds: %{ -        follow: true, -        favourite: true, -        reblog: true, -        mention: true -      } -    } -  } -    def initial_state(token, user, custom_emojis) do      limit = Config.get([:instance, :limit]) @@ -86,7 +56,7 @@ defmodule Pleroma.Web.MastoFEView do            "video\/mp4"          ]        }, -      settings: user.mastofe_settings || @default_settings, +      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), | 
