diff options
| author | Egor Kislitsyn <egor@kislitsyn.com> | 2020-03-03 00:32:34 +0400 | 
|---|---|---|
| committer | Egor Kislitsyn <egor@kislitsyn.com> | 2020-03-03 00:32:34 +0400 | 
| commit | 0f386110c6e15148ff1ff5ea3451885485fcb7ff (patch) | |
| tree | 8a4dcbede6c674730980e1fafa5b956518170a14 /lib | |
| parent | 011ede45361096f55dda938078e24574cdf33b2b (diff) | |
| parent | 4c02e049358441529c54a72cd11f1c81ee897d49 (diff) | |
| download | pleroma-0f386110c6e15148ff1ff5ea3451885485fcb7ff.tar.gz pleroma-0f386110c6e15148ff1ff5ea3451885485fcb7ff.zip | |
Merge remote-tracking branch 'origin/develop' into global-status-expiration
Diffstat (limited to 'lib')
115 files changed, 1320 insertions, 373 deletions
| diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 3e76d2c97..5c9ef6904 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Mix.Tasks.Pleroma.Config do diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 24d999707..2b03a3009 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Mix.Tasks.Pleroma.Emoji do @@ -186,11 +186,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do      tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}") -    {:ok, _} = -      :zip.unzip( -        binary_archive, -        cwd: tmp_pack_dir -      ) +    {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))      emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts) diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 9af6cda30..bc842a59f 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -1,11 +1,13 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Mix.Tasks.Pleroma.Instance do    use Mix.Task    import Mix.Pleroma +  alias Pleroma.Config +    @shortdoc "Manages Pleroma instance"    @moduledoc File.read!("docs/administration/CLI_tasks/instance.md") @@ -63,7 +65,8 @@ defmodule Mix.Tasks.Pleroma.Instance do          get_option(            options,            :instance_name, -          "What is the name of your instance? (e.g. Pleroma/Soykaf)" +          "What is the name of your instance? (e.g. The Corndog Emporium)", +          domain          )        email = get_option(options, :admin_email, "What is your admin email address?") @@ -153,6 +156,8 @@ defmodule Mix.Tasks.Pleroma.Instance do            Pleroma.Config.get([:instance, :static_dir])          ) +      Config.put([:instance, :static_dir], static_dir) +        secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)        jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)        signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) @@ -202,8 +207,14 @@ defmodule Mix.Tasks.Pleroma.Instance do        write_robots_txt(indexable, template_dir)        shell_info( -        "\n All files successfully written! Refer to the installation instructions for your platform for next steps" +        "\n All files successfully written! Refer to the installation instructions for your platform for next steps."        ) + +      if db_configurable? do +        shell_info( +          " Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information." +        ) +      end      else        shell_error(          "The task would have overwritten the following files:\n" <> diff --git a/lib/mix/tasks/pleroma/refresh_counter_cache.ex b/lib/mix/tasks/pleroma/refresh_counter_cache.ex new file mode 100644 index 000000000..15b4dbfa6 --- /dev/null +++ b/lib/mix/tasks/pleroma/refresh_counter_cache.ex @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.RefreshCounterCache do +  @shortdoc "Refreshes counter cache" + +  use Mix.Task + +  alias Pleroma.Activity +  alias Pleroma.CounterCache +  alias Pleroma.Repo + +  require Logger +  import Ecto.Query + +  def run([]) do +    Mix.Pleroma.start_pleroma() + +    ["public", "unlisted", "private", "direct"] +    |> Enum.each(fn visibility -> +      count = status_visibility_count_query(visibility) +      name = "status_visibility_#{visibility}" +      CounterCache.set(name, count) +      Mix.Pleroma.shell_info("Set #{name} to #{count}") +    end) + +    Mix.Pleroma.shell_info("Done") +  end + +  defp status_visibility_count_query(visibility) do +    Activity +    |> where( +      [a], +      fragment( +        "activity_visibility(?, ?, ?) = ?", +        a.actor, +        a.recipients, +        a.data, +        ^visibility +      ) +    ) +    |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) +    |> Repo.aggregate(:count, :id, timeout: :timer.minutes(30)) +  end +end diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robotstxt.ex index e99dd8502..24f08180e 100644 --- a/lib/mix/tasks/pleroma/robotstxt.ex +++ b/lib/mix/tasks/pleroma/robotstxt.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Mix.Tasks.Pleroma.RobotsTxt do diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 85c9e4954..40dd9bdc0 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Mix.Tasks.Pleroma.User do @@ -100,8 +100,7 @@ defmodule Mix.Tasks.Pleroma.User do        User.perform(:delete, user)        shell_info("User #{nickname} deleted.")      else -      _ -> -        shell_error("No local user #{nickname}") +      _ -> shell_error("No local user #{nickname}")      end    end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 72e2256ea..397eb6e3f 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Activity do @@ -310,7 +310,7 @@ defmodule Pleroma.Activity do    def restrict_deactivated_users(query) do      deactivated_users = -      from(u in User.Query.build(deactivated: true), select: u.ap_id) +      from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)        |> Repo.all()      Activity.Queries.exclude_authors(query, deactivated_users) diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index 79f305201..04593b9fb 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Activity.Queries do @@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do    Contains queries for Activity.    """ -  import Ecto.Query, only: [from: 2] +  import Ecto.Query, only: [from: 2, where: 3]    @type query :: Ecto.Queryable.t() | Activity.t() @@ -30,7 +30,7 @@ defmodule Pleroma.Activity.Queries do      )    end -  @spec by_author(query, String.t()) :: query +  @spec by_author(query, User.t()) :: query    def by_author(query \\ Activity, %User{ap_id: ap_id}) do      from(a in query, where: a.actor == ^ap_id)    end @@ -63,6 +63,22 @@ defmodule Pleroma.Activity.Queries do      )    end +  @spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query +  def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do +    query = +      if opts[:skip_preloading] do +        Activity.with_joined_object(query) +      else +        Activity.with_preloaded_object(query) +      end + +    where( +      query, +      [activity, object: o], +      fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id)) +    ) +  end +    @spec by_type(query, String.t()) :: query    def by_type(query \\ Activity, activity_type) do      from( diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f96e208da..ceb365bb3 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Activity.Search do diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex index 7ea5c48ca..db9c88d84 100644 --- a/lib/pleroma/activity_expiration.ex +++ b/lib/pleroma/activity_expiration.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.ActivityExpiration do @@ -62,6 +62,6 @@ defmodule Pleroma.ActivityExpiration do    def expires_late_enough?(scheduled_at) do      now = NaiveDateTime.utc_now()      diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) -    diff >= @min_activity_lifetime +    diff > @min_activity_lifetime    end  end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 27758cf94..18854b850 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Application do diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index c2765a5b8..cf75c3adc 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Captcha do @@ -50,7 +50,7 @@ defmodule Pleroma.Captcha do        token = new_captcha[:token]        secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")        sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") -      # Basicallty copy what Phoenix.Token does here, add the time to +      # Basically copy what Phoenix.Token does here, add the time to        # the actual data and make it a binary to then encrypt it        encrypted_captcha_answer =          %{ @@ -62,7 +62,7 @@ defmodule Pleroma.Captcha do        {          :reply, -        # Repalce the answer with the encrypted answer +        # Replace the answer with the encrypted answer          %{new_captcha | answer_data: encrypted_captcha_answer},          state        } @@ -82,7 +82,8 @@ defmodule Pleroma.Captcha do      valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)      result = -      with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), +      with false <- is_nil(answer_data), +           {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),             %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do          try do            if DateTime.before?(at, valid_if_after), diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index 5306fe1aa..06c479ca9 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Captcha.Native do @@ -10,8 +10,8 @@ defmodule Pleroma.Captcha.Native do    @impl Service    def new do      case Captcha.get() do -      {:timeout} -> -        %{error: dgettext("errors", "Captcha timeout")} +      :error -> +        %{error: dgettext("errors", "Captcha error")}        {:ok, answer_data, img_binary} ->          %{ diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 119251bee..2b43d4c36 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.ConfigDB do diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index d4fe892af..f1a339703 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Config.Holder do diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex index b8787cb49..df2d18725 100644 --- a/lib/pleroma/config/loader.ex +++ b/lib/pleroma/config/loader.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Config.Loader do diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 6c5ba1f95..1846aa22c 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Config.TransferTask do @@ -146,9 +146,7 @@ defmodule Pleroma.Config.TransferTask do    defp update_env(group, key, nil), do: Application.delete_env(group, key)    defp update_env(group, key, value), do: Application.put_env(group, key, value) -  defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted") - -  defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot) +  defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)    defp restart(started_applications, app, _) do      with {^app, _, _} <- List.keyfind(started_applications, app, 0), diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index e5d28ebff..693825cf5 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Conversation.Participation do @@ -133,10 +133,8 @@ defmodule Pleroma.Conversation.Participation do        [user.id | user_ids]        |> Enum.uniq()        |> Enum.reduce([], fn user_id, acc -> -        case FlakeId.Ecto.CompatType.dump(user_id) do -          {:ok, user_id} -> [user_id | acc] -          _ -> acc -        end +        {:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id) +        [user_id | acc]        end)      conversation_subquery = diff --git a/lib/pleroma/counter_cache.ex b/lib/pleroma/counter_cache.ex new file mode 100644 index 000000000..4d348a413 --- /dev/null +++ b/lib/pleroma/counter_cache.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.CounterCache do +  alias Pleroma.CounterCache +  alias Pleroma.Repo +  use Ecto.Schema +  import Ecto.Changeset +  import Ecto.Query + +  schema "counter_cache" do +    field(:name, :string) +    field(:count, :integer) +  end + +  def changeset(struct, params) do +    struct +    |> cast(params, [:name, :count]) +    |> validate_required([:name]) +    |> unique_constraint(:name) +  end + +  def get_as_map(names) when is_list(names) do +    CounterCache +    |> where([cc], cc.name in ^names) +    |> Repo.all() +    |> Enum.group_by(& &1.name, & &1.count) +    |> Map.new(fn {k, v} -> {k, hd(v)} end) +  end + +  def set(name, count) do +    %CounterCache{} +    |> changeset(%{"name" => name, "count" => count}) +    |> Repo.insert( +      on_conflict: [set: [count: count]], +      returning: true, +      conflict_target: :name +    ) +  end +end diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index 5f23345f7..55f61024e 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Emails.AdminEmail do diff --git a/lib/pleroma/emails/new_users_digest_email.ex b/lib/pleroma/emails/new_users_digest_email.ex new file mode 100644 index 000000000..7d16b807f --- /dev/null +++ b/lib/pleroma/emails/new_users_digest_email.ex @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emails.NewUsersDigestEmail do +  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email_styled} + +  defp instance_notify_email do +    Pleroma.Config.get([:instance, :notify_email]) || Pleroma.Config.get([:instance, :email]) +  end + +  def new_users(to, users_and_statuses) do +    instance_name = Pleroma.Config.get([:instance, :name]) +    styling = Pleroma.Config.get([Pleroma.Emails.UserEmail, :styling]) + +    logo_url = +      Pleroma.Web.Endpoint.url() <> +        Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo]) + +    new() +    |> to({to.name, to.email}) +    |> from({instance_name, instance_notify_email()}) +    |> subject("#{instance_name} New Users") +    |> render_body("new_users_digest.html", %{ +      title: "New Users", +      users_and_statuses: users_and_statuses, +      instance: instance_name, +      styling: styling, +      logo_url: logo_url +    }) +  end +end diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index b8cb3bf03..a6d281151 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.FollowingRelationship do diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 90895374d..e2a658cb3 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Formatter do diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 11513106e..d78c5f202 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.HTML do @@ -108,6 +108,7 @@ defmodule Pleroma.HTML do      Cachex.fetch!(:scrubber_cache, key, fn _key ->        result =          content +        |> Floki.parse_fragment!()          |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")          |> Floki.attribute("a", "href")          |> Enum.at(0) diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex index 36771533f..6ee055f50 100644 --- a/lib/pleroma/mime.ex +++ b/lib/pleroma/mime.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.MIME do @@ -9,7 +9,7 @@ defmodule Pleroma.MIME do    @default "application/octet-stream"    @read_bytes 35 -  @spec file_mime_type(String.t()) :: +  @spec file_mime_type(String.t(), String.t()) ::            {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error    def file_mime_type(path, filename) do      with {:ok, content_type} <- file_mime_type(path), diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 66e91fcef..60dba3434 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Notification do diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 52556bf31..9574432f0 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -1,10 +1,13 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Object do    use Ecto.Schema +  import Ecto.Query +  import Ecto.Changeset +    alias Pleroma.Activity    alias Pleroma.Object    alias Pleroma.Object.Fetcher @@ -12,9 +15,6 @@ defmodule Pleroma.Object do    alias Pleroma.Repo    alias Pleroma.User -  import Ecto.Query -  import Ecto.Changeset -    require Logger    @type t() :: %__MODULE__{} @@ -145,18 +145,18 @@ defmodule Pleroma.Object do    # Legacy objects can be mutated by anybody    def authorize_mutation(%Object{}, %User{}), do: true +  @spec get_cached_by_ap_id(String.t()) :: Object.t() | nil    def get_cached_by_ap_id(ap_id) do      key = "object:#{ap_id}" -    Cachex.fetch!(:object_cache, key, fn _ -> -      object = get_by_ap_id(ap_id) - -      if object do -        {:commit, object} -      else -        {:ignore, object} -      end -    end) +    with {:ok, nil} <- Cachex.get(:object_cache, key), +         object when not is_nil(object) <- get_by_ap_id(ap_id), +         {:ok, true} <- Cachex.put(:object_cache, key, object) do +      object +    else +      {:ok, object} -> object +      nil -> nil +    end    end    def context_mapping(context) do @@ -301,4 +301,26 @@ defmodule Pleroma.Object do    def local?(%Object{data: %{"id" => id}}) do      String.starts_with?(id, Pleroma.Web.base_url() <> "/")    end + +  def replies(object, opts \\ []) do +    object = Object.normalize(object) + +    query = +      Object +      |> where( +        [o], +        fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"]) +      ) +      |> order_by([o], asc: o.id) + +    if opts[:self_only] do +      actor = object.data["actor"] +      where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor)) +    else +      query +    end +  end + +  def self_replies(object, opts \\ []), +    do: replies(object, Keyword.put(opts, :self_only, true))  end diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 25aa32f60..9ae6a5600 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Object.Containment do @@ -39,15 +39,8 @@ defmodule Pleroma.Object.Containment do      defp compare_uris(_, %URI{scheme: "tag"}), do: :ok    end -  defp compare_uris(%URI{} = id_uri, %URI{} = other_uri) do -    if id_uri.host == other_uri.host do -      :ok -    else -      :error -    end -  end - -  defp compare_uris(_, _), do: :error +  defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok +  defp compare_uris(_id_uri, _other_uri), do: :error    @doc """    Checks that an imported AP object's actor matches the domain it came from. diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 037c42339..eaa13d1e7 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Object.Fetcher do @@ -10,6 +10,7 @@ defmodule Pleroma.Object.Fetcher do    alias Pleroma.Signature    alias Pleroma.Web.ActivityPub.InternalFetchActor    alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.Federator    require Logger    require Pleroma.Constants @@ -59,20 +60,23 @@ defmodule Pleroma.Object.Fetcher do      end    end -  # TODO: -  # This will create a Create activity, which we need internally at the moment. +  # Note: will create a Create activity, which we need internally at the moment.    def fetch_object_from_id(id, options \\ []) do -    with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, -         {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, -         {:normalize, nil} <- {:normalize, Object.normalize(data, false)}, +    with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, +         {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])}, +         {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, +         {_, nil} <- {:normalize, Object.normalize(data, false)},           params <- prepare_activity_params(data), -         {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, -         {:transmogrifier, {:ok, activity}} <- +         {_, :ok} <- {:containment, Containment.contain_origin(id, params)}, +         {_, {:ok, activity}} <-             {:transmogrifier, Transmogrifier.handle_incoming(params, options)}, -         {:object, _data, %Object{} = object} <- +         {_, _data, %Object{} = object} <-             {:object, data, Object.normalize(activity, false)} do        {:ok, object}      else +      {:allowed_depth, false} -> +        {:error, "Max thread distance exceeded."} +        {:containment, _} ->          {:error, "Object containment failed."} diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 4535ca7c5..d43a96cd2 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Pagination do @@ -12,11 +12,15 @@ defmodule Pleroma.Pagination do    alias Pleroma.Repo +  @type type :: :keyset | :offset +    @default_limit 20 +  @max_limit 40    @page_keys ["max_id", "min_id", "limit", "since_id", "order"]    def page_keys, do: @page_keys +  @spec fetch_paginated(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]    def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil)    def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do @@ -57,6 +61,7 @@ defmodule Pleroma.Pagination do      |> Repo.all()    end +  @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]    def paginate(query, options, method \\ :keyset, table_binding \\ nil)    def paginate(query, options, :keyset, table_binding) do @@ -130,7 +135,11 @@ defmodule Pleroma.Pagination do    end    defp restrict(query, :limit, options, _table_binding) do -    limit = Map.get(options, :limit, @default_limit) +    limit = +      case Map.get(options, :limit, @default_limit) do +        limit when limit < @max_limit -> limit +        _ -> @max_limit +      end      query      |> limit(^limit) diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index b04273979..81e6b4f2a 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.HTTPSecurityPlug do diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex index 23d22a712..036e2a773 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/plugs/http_signature.ex @@ -1,9 +1,10 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do    import Plug.Conn +  import Phoenix.Controller, only: [get_format: 1, text: 2]    require Logger    def init(options) do @@ -15,25 +16,27 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do    end    def call(conn, _opts) do -    headers = get_req_header(conn, "signature") -    signature = Enum.at(headers, 0) +    if get_format(conn) == "activity+json" do +      conn +      |> maybe_assign_valid_signature() +      |> maybe_require_signature() +    else +      conn +    end +  end -    if signature do +  defp maybe_assign_valid_signature(conn) do +    if has_signature_header?(conn) do        # set (request-target) header to the appropriate value        # we also replace the digest header with the one we computed -      conn = -        conn -        |> put_req_header( -          "(request-target)", -          String.downcase("#{conn.method}") <> " #{conn.request_path}" -        ) +      request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"        conn = -        if conn.assigns[:digest] do -          conn -          |> put_req_header("digest", conn.assigns[:digest]) -        else -          conn +        conn +        |> put_req_header("(request-target)", request_target) +        |> case do +          %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest) +          conn -> conn          end        assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) @@ -42,4 +45,21 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do        conn      end    end + +  defp has_signature_header?(conn) do +    conn |> get_req_header("signature") |> Enum.at(0, false) +  end + +  defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn + +  defp maybe_require_signature(conn) do +    if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do +      conn +      |> put_status(:unauthorized) +      |> text("Request not signed") +      |> halt() +    else +      conn +    end +  end  end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index 07c0f7fdb..38df074ad 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.OAuthScopesPlug do diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex index 187582ede..884268d96 100644 --- a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex +++ b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do      DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)    end -  def add_limiter(limiter_name, expiration) do -    {:ok, _pid} = +  def add_or_return_limiter(limiter_name, expiration) do +    result =        DynamicSupervisor.start_child(          __MODULE__,          %{ @@ -28,6 +28,12 @@ defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do               ]}          }        ) + +    case result do +      {:ok, _pid} = result -> result +      {:error, {:already_started, pid}} -> {:ok, pid} +      _ -> result +    end    end    @impl true diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index 7fb92489c..c3f6351c8 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.RateLimiter do @@ -7,12 +7,14 @@ defmodule Pleroma.Plugs.RateLimiter do    ## Configuration -  A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where: +  A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. +  The basic configuration is a tuple where:    * The first element: `scale` (Integer). The time scale in milliseconds.    * The second element: `limit` (Integer). How many requests to limit in the time scale provided. -  It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated. +  It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a +  list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.    To disable a limiter set its value to `nil`. @@ -64,91 +66,102 @@ defmodule Pleroma.Plugs.RateLimiter do    import Pleroma.Web.TranslationHelpers    import Plug.Conn +  alias Pleroma.Config    alias Pleroma.Plugs.RateLimiter.LimiterSupervisor    alias Pleroma.User    require Logger -  def init(opts) do -    limiter_name = Keyword.get(opts, :name) - -    case Pleroma.Config.get([:rate_limit, limiter_name]) do -      nil -> -        nil - -      config -> -        name_root = Keyword.get(opts, :bucket_name, limiter_name) +  @doc false +  def init(plug_opts) do +    plug_opts +  end -        %{ -          name: name_root, -          limits: config, -          opts: opts -        } +  def call(conn, plug_opts) do +    if disabled?() do +      handle_disabled(conn) +    else +      action_settings = action_settings(plug_opts) +      handle(conn, action_settings)      end    end -  # Do not limit if there is no limiter configuration -  def call(conn, nil), do: conn +  defp handle_disabled(conn) do +    if Config.get(:env) == :prod do +      Logger.warn("Rate limiter is disabled for localhost/socket") +    end + +    conn +  end -  def call(conn, settings) do -    case disabled?() do -      true -> -        if Pleroma.Config.get(:env) == :prod, -          do: Logger.warn("Rate limiter is disabled for localhost/socket") +  defp handle(conn, nil), do: conn +  defp handle(conn, action_settings) do +    action_settings +    |> incorporate_conn_info(conn) +    |> check_rate() +    |> case do +      {:ok, _count} ->          conn -      false -> -        settings -        |> incorporate_conn_info(conn) -        |> check_rate() -        |> case do -          {:ok, _count} -> -            conn - -          {:error, _count} -> -            render_throttled_error(conn) -        end +      {:error, _count} -> +        render_throttled_error(conn)      end    end    def disabled? do      localhost_or_socket = -      Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip]) +      Config.get([Pleroma.Web.Endpoint, :http, :ip])        |> Tuple.to_list()        |> Enum.join(".")        |> String.match?(~r/^local|^127.0.0.1/) -    remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled]) +    remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled])      localhost_or_socket and remote_ip_disabled    end -  def inspect_bucket(conn, name_root, settings) do -    settings = -      settings -      |> incorporate_conn_info(conn) +  @inspect_bucket_not_found {:error, :not_found} -    bucket_name = make_bucket_name(%{settings | name: name_root}) -    key_name = make_key_name(settings) -    limit = get_limits(settings) +  def inspect_bucket(conn, bucket_name_root, plug_opts) do +    with %{name: _} = action_settings <- action_settings(plug_opts) do +      action_settings = incorporate_conn_info(action_settings, conn) +      bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root}) +      key_name = make_key_name(action_settings) +      limit = get_limits(action_settings) -    case Cachex.get(bucket_name, key_name) do -      {:error, :no_cache} -> -        {:err, :not_found} +      case Cachex.get(bucket_name, key_name) do +        {:error, :no_cache} -> +          @inspect_bucket_not_found -      {:ok, nil} -> -        {0, limit} +        {:ok, nil} -> +          {0, limit} -      {:ok, value} -> -        {value, limit - value} +        {:ok, value} -> +          {value, limit - value} +      end +    else +      _ -> @inspect_bucket_not_found      end    end -  defp check_rate(settings) do -    bucket_name = make_bucket_name(settings) -    key_name = make_key_name(settings) -    limit = get_limits(settings) +  def action_settings(plug_opts) do +    with limiter_name when is_atom(limiter_name) <- plug_opts[:name], +         limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do +      bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name) + +      %{ +        name: bucket_name_root, +        limits: limits, +        opts: plug_opts +      } +    end +  end + +  defp check_rate(action_settings) do +    bucket_name = make_bucket_name(action_settings) +    key_name = make_key_name(action_settings) +    limit = get_limits(action_settings)      case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do        {:commit, value} -> @@ -158,8 +171,8 @@ defmodule Pleroma.Plugs.RateLimiter do          {:error, value}        {:error, :no_cache} -> -        initialize_buckets(settings) -        check_rate(settings) +        initialize_buckets!(action_settings) +        check_rate(action_settings)      end    end @@ -169,16 +182,19 @@ defmodule Pleroma.Plugs.RateLimiter do    defp increment_value(val, _limit), do: {:commit, val + 1} -  defp incorporate_conn_info(settings, %{assigns: %{user: %User{id: user_id}}, params: params}) do -    Map.merge(settings, %{ +  defp incorporate_conn_info(action_settings, %{ +         assigns: %{user: %User{id: user_id}}, +         params: params +       }) do +    Map.merge(action_settings, %{        mode: :user,        conn_params: params,        conn_info: "#{user_id}"      })    end -  defp incorporate_conn_info(settings, %{params: params} = conn) do -    Map.merge(settings, %{ +  defp incorporate_conn_info(action_settings, %{params: params} = conn) do +    Map.merge(action_settings, %{        mode: :anon,        conn_params: params,        conn_info: "#{ip(conn)}" @@ -197,10 +213,10 @@ defmodule Pleroma.Plugs.RateLimiter do      |> halt()    end -  defp make_key_name(settings) do +  defp make_key_name(action_settings) do      "" -    |> attach_params(settings) -    |> attach_identity(settings) +    |> attach_selected_params(action_settings) +    |> attach_identity(action_settings)    end    defp get_scale(_, {scale, _}), do: scale @@ -215,28 +231,35 @@ defmodule Pleroma.Plugs.RateLimiter do    defp get_limits(%{limits: [{_, limit}, _]}), do: limit -  defp make_bucket_name(%{mode: :user, name: name_root}), -    do: user_bucket_name(name_root) +  defp make_bucket_name(%{mode: :user, name: bucket_name_root}), +    do: user_bucket_name(bucket_name_root) -  defp make_bucket_name(%{mode: :anon, name: name_root}), -    do: anon_bucket_name(name_root) +  defp make_bucket_name(%{mode: :anon, name: bucket_name_root}), +    do: anon_bucket_name(bucket_name_root) -  defp attach_params(input, %{conn_params: conn_params, opts: opts}) do -    param_string = -      opts +  defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do +    params_string = +      plug_opts        |> Keyword.get(:params, [])        |> Enum.sort()        |> Enum.map(&Map.get(conn_params, &1, ""))        |> Enum.join(":") -    "#{input}#{param_string}" +    [input, params_string] +    |> Enum.join(":") +    |> String.replace_leading(":", "")    end -  defp initialize_buckets(%{name: _name, limits: nil}), do: :ok +  defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok + +  defp initialize_buckets!(%{name: name, limits: limits}) do +    {:ok, _pid} = +      LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits)) + +    {:ok, _pid} = +      LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits)) -  defp initialize_buckets(%{name: name, limits: limits}) do -    LimiterSupervisor.add_limiter(anon_bucket_name(name), get_scale(:anon, limits)) -    LimiterSupervisor.add_limiter(user_bucket_name(name), get_scale(:user, limits)) +    :ok    end    defp attach_identity(base, %{mode: :user, conn_info: conn_info}), @@ -245,6 +268,6 @@ defmodule Pleroma.Plugs.RateLimiter do    defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),      do: "ip:#{base}:#{conn_info}" -  defp user_bucket_name(name_root), do: "user:#{name_root}" |> String.to_atom() -  defp anon_bucket_name(name_root), do: "anon:#{name_root}" |> String.to_atom() +  defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom() +  defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()  end diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 1cd5af48a..2eca4f8f6 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.RemoteIp do diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex index 7b304eebc..23e800a74 100644 --- a/lib/pleroma/plugs/user_enabled_plug.ex +++ b/lib/pleroma/plugs/user_enabled_plug.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.UserEnabledPlug do diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 3190163d3..2748102df 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Plugs.UserIsAdminPlug do diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index cb0b6653c..f62138466 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Repo do diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex index e81bfcd7d..8ff06a462 100644 --- a/lib/pleroma/scheduled_activity.ex +++ b/lib/pleroma/scheduled_activity.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.ScheduledActivity do diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index cf590fb01..33f50dda8 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -1,9 +1,10 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Stats do    import Ecto.Query +  alias Pleroma.CounterCache    alias Pleroma.Repo    alias Pleroma.User @@ -96,4 +97,21 @@ defmodule Pleroma.Stats do        }      }    end + +  def get_status_visibility_count do +    counter_cache = +      CounterCache.get_as_map([ +        "status_visibility_public", +        "status_visibility_private", +        "status_visibility_unlisted", +        "status_visibility_direct" +      ]) + +    %{ +      public: counter_cache["status_visibility_public"] || 0, +      unlisted: counter_cache["status_visibility_unlisted"] || 0, +      private: counter_cache["status_visibility_private"] || 0, +      direct: counter_cache["status_visibility_direct"] || 0 +    } +  end  end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 2e0986197..440199d19 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -37,6 +37,7 @@ defmodule Pleroma.Upload do            Plug.Upload.t()            | (data_uri_string :: String.t())            | {:from_local, name :: String.t(), id :: String.t(), path :: String.t()} +          | map()    @type option ::            {:type, :avatar | :banner | :background} diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex index 2e6fe3292..10b3069f4 100644 --- a/lib/pleroma/uploaders/local.ex +++ b/lib/pleroma/uploaders/local.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Uploaders.Local do diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index feb89cea6..a13ff23b6 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Uploaders.S3 do diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index d71e213d2..9a94534e9 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Uploaders.Uploader do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 5ea36fea3..5fe79333e 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User do @@ -749,9 +749,18 @@ defmodule Pleroma.User do      Cachex.del(:user_cache, "nickname:#{user.nickname}")    end +  @spec get_cached_by_ap_id(String.t()) :: User.t() | nil    def get_cached_by_ap_id(ap_id) do      key = "ap_id:#{ap_id}" -    Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end) + +    with {:ok, nil} <- Cachex.get(:user_cache, key), +         user when not is_nil(user) <- get_by_ap_id(ap_id), +         {:ok, true} <- Cachex.put(:user_cache, key, user) do +      user +    else +      {:ok, user} -> user +      nil -> nil +    end    end    def get_cached_by_id(id) do @@ -853,14 +862,14 @@ defmodule Pleroma.User do    @spec get_followers_query(User.t()) :: Ecto.Query.t()    def get_followers_query(user), do: get_followers_query(user, nil) -  @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} +  @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}    def get_followers(user, page \\ nil) do      user      |> get_followers_query(page)      |> Repo.all()    end -  @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} +  @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}    def get_external_followers(user, page \\ nil) do      user      |> get_followers_query(page) @@ -1304,7 +1313,6 @@ defmodule Pleroma.User do      Repo.delete(user)    end -  @spec perform(atom(), User.t()) :: {:ok, User.t()}    def perform(:fetch_initial_posts, %User{} = user) do      pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) @@ -1336,7 +1344,6 @@ defmodule Pleroma.User do      )    end -  @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}    def perform(:follow_import, %User{} = follower, followed_identifiers)        when is_list(followed_identifiers) do      Enum.map( diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 364bc1c89..884e33039 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User.Query do @@ -48,7 +48,7 @@ defmodule Pleroma.User.Query do              followers: User.t(),              friends: User.t(),              recipients_from_activity: [String.t()], -            nickname: [String.t()], +            nickname: [String.t()] | String.t(),              ap_id: [String.t()],              order_by: term(),              select: term(), diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 6b55df483..cec59c372 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User.Search do @@ -33,9 +33,15 @@ defmodule Pleroma.User.Search do      # Strip the beginning @ off if there is a query      query_string = String.trim_leading(query_string, "@") -    with [name, domain] <- String.split(query_string, "@"), -         formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do -      name <> "@" <> to_string(:idna.encode(formatted_domain)) +    with [name, domain] <- String.split(query_string, "@") do +      encoded_domain = +        domain +        |> String.replace(~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") +        |> String.to_charlist() +        |> :idna.encode() +        |> to_string() + +      name <> "@" <> encoded_domain      else        _ -> query_string      end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 3149e10e9..393947942 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.UserRelationship do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 408f6c966..07121a798 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    alias Pleroma.Activity.Ir.Topics    alias Pleroma.ActivityExpiration    alias Pleroma.Config +  alias Pleroma.Constants    alias Pleroma.Conversation    alias Pleroma.Conversation.Participation    alias Pleroma.Notification @@ -125,6 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def increase_poll_votes_if_vote(_create_data), do: :noop +  @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}    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), @@ -242,12 +244,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      :noop    end -  def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do +  @spec create(map(), boolean()) :: {:ok, Activity.t()} | {:error, any()} +  def create(params, fake \\ false) do +    with {:ok, result} <- Repo.transaction(fn -> do_create(params, fake) end) do +      result +    end +  end + +  defp do_create(%{to: to, actor: actor, context: context, object: object} = params, fake) do      additional = params[:additional] || %{}      # only accept false as false value      local = !(params[:local] == false)      published = params[:published] -    quick_insert? = Pleroma.Config.get([:env]) == :benchmark +    quick_insert? = Config.get([:env]) == :benchmark      with create_data <-             make_create_data( @@ -270,10 +279,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          {:ok, activity}        {:error, message} -> -        {:error, message} +        Repo.rollback(message)      end    end +  @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}    def listen(%{to: to, actor: actor, context: context, object: object} = params) do      additional = params[:additional] || %{}      # only accept false as false value @@ -288,20 +298,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           {:ok, activity} <- insert(listen_data, local),           :ok <- maybe_federate(activity) do        {:ok, activity} -    else -      {:error, message} -> -        {:error, message}      end    end +  @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()}    def accept(params) do      accept_or_reject("Accept", params)    end +  @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()}    def reject(params) do      accept_or_reject("Reject", params)    end +  @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}    def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do      local = Map.get(params, :local, true)      activity_id = Map.get(params, :activity_id, nil) @@ -315,6 +325,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end +  @spec update(map()) :: {:ok, Activity.t()} | {:error, any()}    def update(%{to: to, cc: cc, actor: actor, object: object} = params) do      local = !(params[:local] == false)      activity_id = params[:activity_id] @@ -333,7 +344,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end +  @spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) :: +          {:ok, Activity.t(), Object.t()} | {:error, any()}    def react_with_emoji(user, object, emoji, options \\ []) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do +      result +    end +  end + +  defp do_react_with_emoji(user, object, emoji, options) do      with local <- Keyword.get(options, :local, true),           activity_id <- Keyword.get(options, :activity_id, nil),           true <- Pleroma.Emoji.is_unicode_emoji?(emoji), @@ -343,11 +363,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        {:ok, activity, object}      else -      e -> {:error, e} +      false -> {:error, false} +      {:error, error} -> Repo.rollback(error)      end    end +  @spec unreact_with_emoji(User.t(), String.t(), keyword()) :: +          {:ok, Activity.t(), Object.t()} | {:error, any()}    def unreact_with_emoji(user, reaction_id, options \\ []) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do +      result +    end +  end + +  defp do_unreact_with_emoji(user, reaction_id, options) do      with local <- Keyword.get(options, :local, true),           activity_id <- Keyword.get(options, :activity_id, nil),           user_ap_id <- user.ap_id, @@ -359,17 +389,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        {:ok, activity, object}      else -      e -> {:error, e} +      {:error, error} -> Repo.rollback(error)      end    end    # TODO: This is weird, maybe we shouldn't check here if we can make the activity. -  def like( -        %User{ap_id: ap_id} = user, -        %Object{data: %{"id" => _}} = object, -        activity_id \\ nil, -        local \\ true -      ) do +  @spec like(User.t(), Object.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t(), Object.t()} | {:error, any()} +  def like(user, object, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do +      result +    end +  end + +  defp do_like( +         %User{ap_id: ap_id} = user, +         %Object{data: %{"id" => _}} = object, +         activity_id, +         local +       ) do      with nil <- get_existing_like(ap_id, object),           like_data <- make_like_data(user, object, activity_id),           {:ok, activity} <- insert(like_data, local), @@ -377,12 +415,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        {:ok, activity, object}      else -      %Activity{} = activity -> {:ok, activity, object} -      error -> {:error, error} +      %Activity{} = activity -> +        {:ok, activity, object} + +      {:error, error} -> +        Repo.rollback(error)      end    end +  @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}    def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do +      result +    end +  end + +  defp do_unlike(actor, object, activity_id, local) do      with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),           unlike_data <- make_unlike_data(actor, like_activity, activity_id),           {:ok, unlike_activity} <- insert(unlike_data, local), @@ -391,10 +441,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(unlike_activity) do        {:ok, unlike_activity, like_activity, object}      else -      _e -> {:ok, object} +      nil -> {:ok, object} +      {:error, error} -> Repo.rollback(error)      end    end +  @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) :: +          {:ok, Activity.t(), Object.t()} | {:error, any()}    def announce(          %User{ap_id: _} = user,          %Object{data: %{"id" => _}} = object, @@ -402,6 +455,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          local \\ true,          public \\ true        ) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_announce(user, object, activity_id, local, public) end) do +      result +    end +  end + +  defp do_announce(user, object, activity_id, local, public) do      with true <- is_announceable?(object, user, public),           announce_data <- make_announce_data(user, object, activity_id, public),           {:ok, activity} <- insert(announce_data, local), @@ -409,16 +469,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        {:ok, activity, object}      else -      error -> {:error, error} +      false -> {:error, false} +      {:error, error} -> Repo.rollback(error)      end    end +  @spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}    def unannounce(          %User{} = actor,          %Object{} = object,          activity_id \\ nil,          local \\ true        ) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do +      result +    end +  end + +  defp do_unannounce(actor, object, activity_id, local) do      with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),           unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),           {:ok, unannounce_activity} <- insert(unannounce_data, local), @@ -427,30 +497,61 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           {:ok, object} <- remove_announce_from_object(announce_activity, object) do        {:ok, unannounce_activity, object}      else -      _e -> {:ok, object} +      nil -> {:ok, object} +      {:error, error} -> Repo.rollback(error)      end    end +  @spec follow(User.t(), User.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t()} | {:error, any()}    def follow(follower, followed, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_follow(follower, followed, activity_id, local) end) do +      result +    end +  end + +  defp do_follow(follower, followed, activity_id, local) do      with data <- make_follow_data(follower, followed, activity_id),           {:ok, activity} <- insert(data, local),           :ok <- maybe_federate(activity),           _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do        {:ok, activity} +    else +      {:error, error} -> Repo.rollback(error)      end    end +  @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t()} | nil | {:error, any()}    def unfollow(follower, followed, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do +      result +    end +  end + +  defp do_unfollow(follower, followed, activity_id, local) do      with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),           {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),           unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),           {:ok, activity} <- insert(unfollow_data, local),           :ok <- maybe_federate(activity) do        {:ok, activity} +    else +      nil -> nil +      {:error, error} -> Repo.rollback(error) +    end +  end + +  @spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()} +  def delete(entity, options \\ []) do +    with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do +      result      end    end -  def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do +  defp do_delete(%User{ap_id: ap_id, follower_address: follower_address} = user, _) do      with data <- %{             "to" => [follower_address],             "type" => "Delete", @@ -463,7 +564,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end -  def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ []) do +  defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do      local = Keyword.get(options, :local, true)      activity_id = Keyword.get(options, :activity_id, nil)      actor = Keyword.get(options, :actor, actor) @@ -488,11 +589,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           {:ok, _actor} <- decrease_note_count_if_public(user, object),           :ok <- maybe_federate(activity) do        {:ok, activity} +    else +      {:error, error} -> +        Repo.rollback(error)      end    end -  @spec block(User.t(), User.t(), String.t() | nil, boolean) :: {:ok, Activity.t() | nil} +  @spec block(User.t(), User.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t()} | {:error, any()}    def block(blocker, blocked, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do +      result +    end +  end + +  defp do_block(blocker, blocked, activity_id, local) do      outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])      unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) @@ -507,20 +619,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        {:ok, activity}      else -      _e -> {:ok, nil} +      {:error, error} -> Repo.rollback(error)      end    end +  @spec unblock(User.t(), User.t(), String.t() | nil, boolean()) :: +          {:ok, Activity.t()} | {:error, any()} | nil    def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do +    with {:ok, result} <- +           Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do +      result +    end +  end + +  defp do_unblock(blocker, blocked, activity_id, local) do      with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),           unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),           {:ok, activity} <- insert(unblock_data, local),           :ok <- maybe_federate(activity) do        {:ok, activity} +    else +      nil -> nil +      {:error, error} -> Repo.rollback(error)      end    end -  @spec flag(map()) :: {:ok, Activity.t()} | any +  @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}    def flag(          %{            actor: actor, @@ -557,6 +681,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end +  @spec move(User.t(), User.t(), boolean()) :: {:ok, Activity.t()} | {:error, any()}    def move(%User{} = origin, %User{} = target, local \\ true) do      params = %{        "type" => "Move", @@ -582,7 +707,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    defp fetch_activities_for_context_query(context, opts) do -    public = [Pleroma.Constants.as_public()] +    public = [Constants.as_public()]      recipients =        if opts["user"], @@ -627,10 +752,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Repo.one()    end +  @spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]    def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do      opts = Map.drop(opts, ["user"]) -    [Pleroma.Constants.as_public()] +    [Constants.as_public()]      |> fetch_activities_query(opts)      |> restrict_unlisted()      |> Pagination.fetch_paginated(opts, pagination) @@ -781,13 +907,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Enum.reverse()    end -  def fetch_instance_activities(params) do +  def fetch_statuses(reading_user, params) do      params =        params        |> Map.put("type", ["Create", "Announce"]) -      |> Map.put("instance", params["instance"]) -    fetch_activities([Pleroma.Constants.as_public()], params, :offset) +    recipients = +      user_activities_recipients(%{ +        "godmode" => params["godmode"], +        "reading_user" => reading_user +      }) + +    fetch_activities(recipients, params, :offset)      |> Enum.reverse()    end @@ -797,9 +928,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp user_activities_recipients(%{"reading_user" => reading_user}) do      if reading_user do -      [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)] +      [Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]      else -      [Pleroma.Constants.as_public()] +      [Constants.as_public()]      end    end @@ -1006,7 +1137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          fragment(            "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",            activity.data, -          ^[Pleroma.Constants.as_public()] +          ^[Constants.as_public()]          )      )    end @@ -1180,7 +1311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    @doc """    Fetch favorites activities of user with order by sort adds to favorites    """ -  @spec fetch_favourites(User.t(), map(), atom()) :: list(Activity.t()) +  @spec fetch_favourites(User.t(), map(), Pagination.type()) :: list(Activity.t())    def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do      user.ap_id      |> Activity.Queries.by_actor() @@ -1218,7 +1349,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        where:          fragment("? && ?", activity.recipients, ^recipients) or            (fragment("? && ?", activity.recipients, ^recipients_with_public) and -             ^Pleroma.Constants.as_public() in activity.recipients) +             ^Constants.as_public() in activity.recipients)      )    end @@ -1234,6 +1365,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Enum.reverse()    end +  @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}    def upload(file, opts \\ []) do      with {:ok, data} <- Upload.store(file, opts) do        obj_data = @@ -1331,8 +1463,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp normalize_counter(_), do: 0    defp maybe_update_follow_information(data) do -    with {:enabled, true} <- -           {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])}, +    with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},           {:ok, info} <- fetch_follow_information_for_user(data) do        info = Map.merge(data[:info] || %{}, info)        Map.put(data, :info, info) 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 8abe18e29..9e7800997 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 @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do @@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do    # does the post contain links?    defp contains_links?(%{"content" => content} = _object) do      content +    |> Floki.parse_fragment!()      |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")      |> Floki.attribute("a", "href")      |> length() > 0 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 df774b0f7..d9a0acfd3 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 @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do 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 878c57925..cc2ac9d08 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy 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 8b36c1021..4a8bc91ae 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -1,16 +1,15 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do    alias Pleroma.Config    alias Pleroma.User -  alias Pleroma.Web.ActivityPub.MRF    require Pleroma.Constants    @moduledoc "Filter activities depending on their age" -  @behaviour MRF +  @behaviour Pleroma.Web.ActivityPub.MRF    defp check_date(%{"published" => published} = message) do      with %DateTime{} = now <- DateTime.utc_now(), diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 8e53296e7..4edc007fd 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -1,12 +1,12 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.MRF    @moduledoc "Filter activities depending on their origin instance" -  @behaviour MRF +  @behaviour Pleroma.Web.ActivityPub.MRF    require Pleroma.Constants diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex index 566c1e191..c9f20571f 100644 --- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do    require Logger -  @behaviour MRF +  @behaviour Pleroma.Web.ActivityPub.MRF    defp lookup_subchain(actor) do      with matches <- Config.get([:mrf_subchain, :match_actor]), 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 7389d6a96..a927a4ed8 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 @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index c184c3b66..6167a74e2 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index e4e3ab44a..6c558e7f0 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.Publisher do diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index 48a1b71e0..bb5542c89 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.Relay do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a72d8430f..9cd3de705 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.Transmogrifier do @@ -156,10 +156,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        when not is_nil(in_reply_to) do      in_reply_to_id = prepare_in_reply_to(in_reply_to)      object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) +    depth = (options[:depth] || 0) + 1 -    if Federator.allowed_incoming_reply_depth?(options[:depth]) do +    if Federator.allowed_thread_distance?(depth) do        with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options), -           %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do +           %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do          object          |> Map.put("inReplyTo", replied_object.data["id"])          |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) @@ -312,7 +313,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)        when is_binary(reply_id) do -    with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), +    with true <- Federator.allowed_thread_distance?(options[:depth]),           {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do        Map.put(object, "type", "Answer")      else @@ -406,8 +407,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      with nil <- Activity.get_create_by_object_ap_id(object["id"]),           {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do -      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) -      object = fix_object(data["object"], options) +      object = fix_object(object, options)        params = %{          to: data["to"], @@ -424,7 +424,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            ])        } -      ActivityPub.create(params) +      with {:ok, created_activity} <- ActivityPub.create(params) 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 @@ -442,7 +455,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        |> fix_addressing      with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do -      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) +      reply_depth = (options[:depth] || 0) + 1 +      options = Keyword.put(options, :depth, reply_depth)        object = fix_object(object, options)        params = %{ @@ -903,6 +917,50 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def set_reply_to_uri(obj), do: obj +  @doc """ +  Serialized Mastodon-compatible `replies` collection containing _self-replies_. +  Based on Mastodon's ActivityPub::NoteSerializer#replies. +  """ +  def set_replies(obj_data) do +    replies_uris = +      with limit when limit > 0 <- +             Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0), +           %Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do +        object +        |> Object.self_replies() +        |> select([o], fragment("?->>'id'", o.data)) +        |> limit(^limit) +        |> Repo.all() +      else +        _ -> [] +      end + +    set_replies(obj_data, replies_uris) +  end + +  defp set_replies(obj, []) do +    obj +  end + +  defp set_replies(obj, replies_uris) do +    replies_collection = %{ +      "type" => "Collection", +      "items" => replies_uris +    } + +    Map.merge(obj, %{"replies" => replies_collection}) +  end + +  def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do +    items +  end + +  def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do +    items +  end + +  def replies(_), do: [] +    # Prepares the object of an outgoing create activity.    def prepare_object(object) do      object @@ -914,6 +972,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> prepare_attachments      |> set_conversation      |> set_reply_to_uri +    |> set_replies      |> strip_internal_fields      |> strip_internal_tags      |> set_type diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 10ce5eee8..2bc958670 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.Utils do @@ -45,8 +45,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do      Map.put(params, "actor", get_ap_id(params["actor"]))    end -  @spec determine_explicit_mentions(map()) :: map() -  def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do +  @spec determine_explicit_mentions(map()) :: [any] +  def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do      Enum.flat_map(tag, fn        %{"type" => "Mention", "href" => href} -> [href]        _ -> [] @@ -427,7 +427,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do    @doc """    Updates a follow activity's state (for locked accounts).    """ -  @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()} +  @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil}    def update_follow_state_for_all(          %Activity{data: %{"actor" => actor, "object" => object}} = activity,          state diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c95cd182d..558832703 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.AdminAPIController do @@ -8,10 +8,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    import Pleroma.Web.ControllerHelper, only: [json_response: 3]    alias Pleroma.Activity +  alias Pleroma.Config    alias Pleroma.ConfigDB    alias Pleroma.ModerationLog    alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.ReportNote +  alias Pleroma.Stats    alias Pleroma.User    alias Pleroma.UserInviteToken    alias Pleroma.Web.ActivityPub.ActivityPub @@ -97,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug,      %{scopes: ["read"], admin: true} -    when action in [:config_show, :list_log] +    when action in [:config_show, :list_log, :stats]    )    plug( @@ -242,13 +244,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    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)      activities = -      ActivityPub.fetch_instance_activities(%{ +      ActivityPub.fetch_statuses(nil, %{          "instance" => instance,          "limit" => page_size, -        "offset" => (page - 1) * page_size +        "offset" => (page - 1) * page_size, +        "exclude_reblogs" => !with_reblogs && "true"        })      conn @@ -257,6 +261,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def list_user_statuses(conn, %{"nickname" => nickname} = params) do +    with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true      godmode = params["godmode"] == "true" || params["godmode"] == true      with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do @@ -265,7 +270,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        activities =          ActivityPub.fetch_user_activities(user, nil, %{            "limit" => page_size, -          "godmode" => godmode +          "godmode" => godmode, +          "exclude_reblogs" => !with_reblogs && "true"          })        conn @@ -570,8 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    @doc "Sends registration invite via email"    def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do      with true <- -           Pleroma.Config.get([:instance, :invites_enabled]) && -             !Pleroma.Config.get([:instance, :registrations_open]), +           Config.get([:instance, :invites_enabled]) && +             !Config.get([:instance, :registrations_open]),           {:ok, invite_token} <- UserInviteToken.create_invite(),           email <-             Pleroma.Emails.UserEmail.user_invitation_email( @@ -739,6 +745,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      end    end +  def list_statuses(%{assigns: %{user: admin}} = conn, params) do +    godmode = params["godmode"] == "true" || params["godmode"] == true +    local_only = params["local_only"] == "true" || params["local_only"] == true +    {page, page_size} = page_params(params) + +    activities = +      ActivityPub.fetch_statuses(admin, %{ +        "godmode" => godmode, +        "local_only" => local_only, +        "limit" => page_size, +        "offset" => (page - 1) * page_size +      }) + +    conn +    |> put_view(Pleroma.Web.AdminAPI.StatusView) +    |> render("index.json", %{activities: activities, as: :activity}) +  end +    def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do      with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do        {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) @@ -808,7 +832,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        configs = ConfigDB.get_all_as_keyword()        merged = -        Pleroma.Config.Holder.config() +        Config.Holder.config()          |> ConfigDB.merge(configs)          |> Enum.map(fn {group, values} ->            Enum.map(values, fn {key, value} -> @@ -838,7 +862,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do          end)          |> List.flatten() -      json(conn, %{configs: merged}) +      response = %{configs: merged} + +      response = +        if Restarter.Pleroma.need_reboot?() do +          Map.put(response, :need_reboot, true) +        else +          response +        end + +      json(conn, response)      end    end @@ -863,20 +896,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do            Ecto.get_meta(config, :state) == :deleted          end) -      Pleroma.Config.TransferTask.load_and_update_env(deleted, false) +      Config.TransferTask.load_and_update_env(deleted, false)        need_reboot? = -        Enum.any?(updated, fn config -> -          group = ConfigDB.from_string(config.group) -          key = ConfigDB.from_string(config.key) -          value = ConfigDB.from_binary(config.value) -          Pleroma.Config.TransferTask.pleroma_need_restart?(group, key, value) -        end) +        Restarter.Pleroma.need_reboot?() || +          Enum.any?(updated, fn config -> +            group = ConfigDB.from_string(config.group) +            key = ConfigDB.from_string(config.key) +            value = ConfigDB.from_binary(config.value) +            Config.TransferTask.pleroma_need_restart?(group, key, value) +          end)        response = %{configs: updated}        response = -        if need_reboot?, do: Map.put(response, :need_reboot, need_reboot?), else: response +        if need_reboot? do +          Restarter.Pleroma.need_reboot() +          Map.put(response, :need_reboot, need_reboot?) +        else +          response +        end        conn        |> put_view(ConfigView) @@ -886,18 +925,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    def restart(conn, _params) do      with :ok <- configurable_from_database(conn) do -      if Pleroma.Config.get(:env) == :test do -        Logger.warn("pleroma restarted") -      else -        send(Restarter.Pleroma, {:restart, 50}) -      end +      Restarter.Pleroma.restart(Config.get(:env), 50)        json(conn, %{})      end    end    defp configurable_from_database(conn) do -    if Pleroma.Config.get(:configurable_from_database) do +    if Config.get(:configurable_from_database) do        :ok      else        errors( @@ -941,6 +976,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      conn |> json("")    end +  def stats(conn, _) do +    count = Stats.get_status_visibility_count() + +    conn +    |> json(%{"status_visibility" => count}) +  end +    def errors(conn, {:error, :not_found}) do      conn      |> put_status(:not_found) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index ed919833e..29cea1f44 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.Search do @@ -18,7 +18,11 @@ defmodule Pleroma.Web.AdminAPI.Search do    @spec user(map()) :: {:ok, [User.t()], pos_integer()}    def user(params \\ %{}) do -    query = User.Query.build(params) |> order_by([u], u.nickname) +    query = +      params +      |> Map.drop([:page, :page_size]) +      |> User.Query.build() +      |> order_by([u], u.nickname)      paginated_query =        User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index bbb53efcd..587ef760e 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.ConfigView do diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 6f2b2b09c..360ddc22c 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.StatusView do @@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do    alias Pleroma.User    def render("index.json", opts) do -    render_many(opts.activities, __MODULE__, "show.json", opts) +    safe_render_many(opts.activities, __MODULE__, "show.json", opts)    end    def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 03921de27..4ec13aafa 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.CommonAPI do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index ca6c93862..8746273c4 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.CommonAPI.Utils do @@ -228,9 +228,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do          data,          visibility        ) do -    no_attachment_links = +    attachment_links =        data -      |> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links])) +      |> Map.get("attachment_links", Config.get([:instance, :attachment_links]))        |> truthy_param?()      content_type = get_content_type(data["content_type"]) @@ -244,7 +244,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do      status      |> format_input(content_type, options) -    |> maybe_add_attachments(attachments, no_attachment_links) +    |> maybe_add_attachments(attachments, attachment_links)      |> maybe_add_nsfw_tag(data)    end @@ -270,7 +270,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def make_context(%Activity{data: %{"context" => context}}, _), do: context    def make_context(_, _), do: Utils.generate_context_id() -  def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed +  def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed    def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do      text = add_attachments(text, attachments) diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index e3d7a465b..c9a3a2585 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ControllerHelper do diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index a77b73109..118c3ac6f 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Endpoint do diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f506a7d24..fd904ef0a 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Federator do @@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do    require Logger -  @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" +  @doc """ +  Returns `true` if the distance to target object does not exceed max configured value. +  Serves to prevent fetching of very long threads, especially useful on smaller instances. +  Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161). +  Applies to fetching of both ancestor (reply-to) and child (reply) objects. +  """    # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength -  def allowed_incoming_reply_depth?(depth) do -    max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) +  def allowed_thread_distance?(distance) do +    max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) -    if max_replies_depth do -      (depth || 1) <= max_replies_depth +    if max_distance && max_distance >= 0 do +      # Default depth is 0 (an object has zero distance from itself in its thread) +      (distance || 0) <= max_distance      else        true      end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 334802e0a..e18adaea8 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Feed.FeedView do diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index 9accd0872..75c9ea17e 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Feed.TagController do diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index f5096834b..59aabb549 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Feed.UserController do diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 9f7e4943c..43649ad26 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastoFEController do diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index f2508aca4..0c9218454 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.NotificationController do diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5a5db8e00..fcab4ef63 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.SearchController do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 287d1631c..b0048102f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.StatusController do diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index 11f7b85d3..11df6fc4a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.SubscriptionController do diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index b9cc8f104..0cdc7bd8d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.SuggestionController do diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 29964a1d4..09e08271b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.TimelineController do @@ -10,9 +10,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    alias Pleroma.Pagination    alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  # TODO: Replace with a macro when there is a Phoenix release with +  # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e +  # in it + +  plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) +  plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) +  plug(RateLimiter, [name: :timeline, bucket_name: :home_timeline] when action == :home) +  plug(RateLimiter, [name: :timeline, bucket_name: :hashtag_timeline] when action == :hashtag) +  plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list) +    plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])    plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 390a2b190..3fe2be521 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.MastodonAPI do diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index c6d37ead7..6dc191250 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.AccountView do diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex index beba89edb..d934e2107 100644 --- a/lib/pleroma/web/mastodon_api/views/app_view.ex +++ b/lib/pleroma/web/mastodon_api/views/app_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.AppView do diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 360ec10f0..33145c484 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.NotificationView do diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 6bb3652fb..40edbb213 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.PollView do diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e1e92034f..f7469cdff 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.StatusView do @@ -175,9 +175,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      expires_at =        with true <- client_posted_this_activity, -           expiration when not is_nil(expiration) <- +           %ActivityExpiration{scheduled_at: scheduled_at} <-               ActivityExpiration.get_by_activity_id(activity.id) do -        expiration.scheduled_at +        scheduled_at +      else +        _ -> nil        end      thread_muted? = @@ -321,11 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          nil        end -    site_name = rich_media[:site_name] || page_url_data.host -      %{        type: "link", -      provider_name: site_name, +      provider_name: page_url_data.host,        provider_url: page_url_data.scheme <> "://" <> page_url_data.host,        url: page_url,        image: image_url |> MediaProxy.url(), diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex index ee48913a7..bd1459a17 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/feed.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Metadata.Providers.Feed do diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex index f87fc1973..8905c9c72 100644 --- a/lib/pleroma/web/metadata/rel_me.ex +++ b/lib/pleroma/web/metadata/rel_me.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Metadata.Providers.RelMe do @@ -8,8 +8,10 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do    @impl Provider    def build_tags(%{user: user}) do -    (Floki.attribute(user.bio, "link[rel~=me]", "href") ++ -       Floki.attribute(user.bio, "a[rel~=me]", "href")) +    bio_tree = Floki.parse_fragment!(user.bio) + +    (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ +       Floki.attribute(bio_tree, "a[rel~=me]", "href"))      |> Enum.map(fn link ->        {:link, [rel: "me", href: link], []}      end) diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 000bd9f66..2f0dfb474 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Metadata.Utils do diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 03c35cc2a..18eb41333 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Nodeinfo.NodeinfoController do @@ -46,10 +46,10 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do          data          |> Map.merge(%{quarantined_instances: quarantined}) -        |> Map.put(:enabled, Config.get([:instance, :federating]))        else          %{}        end +      |> Map.put(:enabled, Config.get([:instance, :federating]))      features =        [ @@ -92,9 +92,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do        openRegistrations: Config.get([:instance, :registrations_open]),        usage: %{          users: %{ -          total: stats.user_count || 0 +          total: Map.get(stats, :user_count, 0)          }, -        localPosts: stats.status_count || 0 +        localPosts: Map.get(stats, :status_count, 0)        },        metadata: %{          nodeName: Config.get([:instance, :name]), diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 528f08574..46688db7e 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.OAuthController do diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 151467494..8ecf901f3 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.Scopes do diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index a2f6d2287..03e95e020 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -323,7 +323,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s")        {:ok, _} ->          conn |> json("ok") -      {:error, _} -> +      {:error, _, _} ->          conn          |> put_status(:internal_server_error)          |> json(%{error: "Couldn't delete the pack #{name}"}) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 108e48438..0e160bbfc 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do @@ -41,24 +41,29 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do    plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) -  def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do +  def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do      with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),           %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-             Object.normalize(activity) do        reactions =          emoji_reactions          |> Enum.map(fn [emoji, user_ap_ids] -> -          users = -            Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) -            |> Enum.filter(& &1) - -          %{ -            name: emoji, -            count: length(users), -            accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}), -            me: !!(user && user.ap_id in user_ap_ids) -          } +          if params["emoji"] && params["emoji"] != emoji do +            nil +          else +            users = +              Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) +              |> Enum.filter(& &1) + +            %{ +              name: emoji, +              count: length(users), +              accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}), +              me: !!(user && user.ap_id in user_ap_ids) +            } +          end          end) +        |> Enum.filter(& &1)        conn        |> json(reactions) diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 16b1a53d2..e97c398dc 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.RelMe do @@ -27,9 +27,10 @@ defmodule Pleroma.Web.RelMe do    defp parse_url(url) do      with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-             Pleroma.HTTP.get(url, [], adapter: @hackney_options), +         {:ok, html_tree} <- Floki.parse_document(html),           data <- -           Floki.attribute(html, "link[rel~=me]", "href") ++ -             Floki.attribute(html, "a[rel~=me]", "href") do +           Floki.attribute(html_tree, "link[rel~=me]", "href") ++ +             Floki.attribute(html_tree, "a[rel~=me]", "href") do        {:ok, data}      end    rescue diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index c06b0a0f2..0779065ee 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.RichMedia.Parser do @@ -81,18 +81,18 @@ defmodule Pleroma.Web.RichMedia.Parser do        {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)        html -      |> parse_html +      |> parse_html()        |> maybe_parse()        |> Map.put(:url, url)        |> clean_parsed_data()        |> check_parsed_data()      rescue        e -> -        {:error, "Parsing error: #{inspect(e)}"} +        {:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"}      end    end -  defp parse_html(html), do: Floki.parse(html) +  defp parse_html(html), do: Floki.parse_document!(html)    defp maybe_parse(html) do      Enum.reduce_while(parsers(), %{}, fn parser, acc -> diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex index fae3c462e..ae0f36702 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 897215698..980242c68 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Router do @@ -192,6 +192,7 @@ defmodule Pleroma.Web.Router do      put("/statuses/:id", AdminAPIController, :status_update)      delete("/statuses/:id", AdminAPIController, :status_delete) +    get("/statuses", AdminAPIController, :list_statuses)      get("/config", AdminAPIController, :config_show)      post("/config", AdminAPIController, :config_update) @@ -201,6 +202,7 @@ defmodule Pleroma.Web.Router do      get("/moderation_log", AdminAPIController, :list_log)      post("/reload_emoji", AdminAPIController, :reload_emoji) +    get("/stats", AdminAPIController, :stats)    end    scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do @@ -271,6 +273,7 @@ defmodule Pleroma.Web.Router do    scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do      pipe_through(:api) +    get("/statuses/:id/reactions/:emoji", PleromaAPIController, :emoji_reactions_by)      get("/statuses/:id/reactions", PleromaAPIController, :emoji_reactions_by)    end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 5392c1ec3..29f992a67 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Streamer.Worker do diff --git a/lib/pleroma/web/templates/email/new_users_digest.html.eex b/lib/pleroma/web/templates/email/new_users_digest.html.eex new file mode 100644 index 000000000..40d9b8381 --- /dev/null +++ b/lib/pleroma/web/templates/email/new_users_digest.html.eex @@ -0,0 +1,158 @@ +<%= for {user, total_statuses, latest_status} <- @users_and_statuses do %> +	<%# user card START %> +						<div style="background-color:transparent;"> +		<div class="block-grid mixed-two-up no-stack" +			style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> +			<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> +				<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> +				<!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]--> +				<div class="col num3" +					style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;"> +					<div style="width:100% !important;"> +						<!--[if (!mso)&(!IE)]><!--> +						<div +							style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;"> +							<!--<![endif]--> +							<div align="left" class="img-container left " +								style="padding-right: 0px;padding-left: 0px;"> +								<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img +									alt="<%= user.name %>" border="0" class="left " src="<%= avatar_url(user) %>" +									style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;" +									title="<%= user.name %>" width="76" /> +								<!--[if mso]></td></tr></table><![endif]--> +							</div> +							<!--[if (!mso)&(!IE)]><!--> +						</div> +						<!--<![endif]--> +					</div> +				</div> + +				<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +				<!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> +				<div class="col num9" +					style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;"> +					<div style="width:100% !important;"> +						<!--[if (!mso)&(!IE)]><!--> +						<div +							style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> +							<!--<![endif]--> +							<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> +							<div +								style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> +								<div +									style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> +									<p style="font-size: 14px; line-height: 19px; margin: 0;"><span +											style="font-size: 16px; color: <%= @styling.text_color %>;"><%= user.name %></span></p> +									<p style="font-size: 14px; line-height: 19px; margin: 0;"><span +											style="font-size: 16px;"><%= link "@" <> user.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: admin_user_url(user) %></span></p> +									<p style="font-size: 14px; line-height: 19px; margin: 0;"><span +											style="font-size: 16px;">Total: <%= total_statuses %></span></p> +								</div> +							</div> +							<!--[if mso]></td></tr></table><![endif]--> +							<!--[if (!mso)&(!IE)]><!--> +						</div> +						<!--<![endif]--> +					</div> +				</div> +				<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +				<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> +			</div> +		</div> +	</div> +	<%# user card END %> + +	<%= if latest_status do %> +		<div style="background-color:transparent;"> +				<div class="block-grid" +					style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> +					<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> +						<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> +						<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]--> +						<div class="col num12" +							style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> +							<div style="width:100% !important;"> +								<!--[if (!mso)&(!IE)]><!--> +								<div +									style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;"> +									<!--<![endif]--> +									<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> +									<div +										style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> +										<div +											style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> +											<span style="font-size: 16px; line-height: 19px;"><%= raw latest_status.object.data["content"] %></span></div> +									</div> +									<!--[if mso]></td></tr></table><![endif]--> +									<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> +									<div +										style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;"> +										<div +											style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;"> +											<p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date latest_status.object.data["published"] %></p> +										</div> +									</div> +									<!--[if mso]></td></tr></table><![endif]--> +									<!--[if (!mso)&(!IE)]><!--> +								</div> +								<!--<![endif]--> +							</div> +						</div> +						<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +						<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> +					</div> +				</div> +			</div> +	<% end %> +          <%# divider start %> +					<div style="background-color:transparent;"> +						<div class="block-grid" +							style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> +							<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> +								<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> +								<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> +								<div class="col num12" +									style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> +									<div style="width:100% !important;"> +										<!--[if (!mso)&(!IE)]><!--> +										<div +											style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> +											<!--<![endif]--> +											<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" +												style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" +												valign="top" width="100%"> +												<tbody> +													<tr style="vertical-align: top;" valign="top"> +														<td class="divider_inner" +															style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;" +															valign="top"> +															<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" +																height="0" role="presentation" +																style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;" +																valign="top" width="100%"> +																<tbody> +																	<tr style="vertical-align: top;" valign="top"> +																		<td height="0" +																			style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" +																			valign="top"><span></span></td> +																	</tr> +																</tbody> +															</table> +														</td> +													</tr> +												</tbody> +											</table> +											<!--[if (!mso)&(!IE)]><!--> +										</div> +										<!--<![endif]--> +									</div> +								</div> +								<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +								<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> +							</div> +						</div> +					</div> + +          <%# divider end %> +	<%# user card END %> +<% end %> diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex new file mode 100644 index 000000000..ca2caaf4d --- /dev/null +++ b/lib/pleroma/web/templates/layout/email_styled.html.eex @@ -0,0 +1,193 @@ +<!DOCTYPE html +	PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" +	xmlns:v="urn:schemas-microsoft-com:vml"> + +<head> +	<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]--> +	<meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> +	<meta content="width=device-width" name="viewport" /> +	<!--[if !mso]><!--> +	<meta content="IE=edge" http-equiv="X-UA-Compatible" /> +	<!--<![endif]--> +	<title><%= @email.subject %></title> +	<!--[if !mso]><!--> +	<!--<![endif]--> +	<style type="text/css"> +		body { +			margin: 0; +			padding: 0; +		} + +		a { + +			color: <%= @styling.link_color %>; +			text-decoration: none; +		} + +		table, +		td, +		tr { +			vertical-align: top; +			border-collapse: collapse; +		} + +		* { +			line-height: inherit; +		} + +		a[x-apple-data-detectors=true] { +			color: inherit !important; +			text-decoration: none !important; +		} +	</style> +	<style id="media-query" type="text/css"> +		@media (max-width: 610px) { + +			.block-grid, +			.col { +				min-width: 320px !important; +				max-width: 100% !important; +				display: block !important; +			} + +			.block-grid { +				width: 100% !important; +			} + +			.col { +				width: 100% !important; +			} + +			.col>div { +				margin: 0 auto; +			} + +			.no-stack .col { +				min-width: 0 !important; +				display: table-cell !important; +			} + +			.no-stack.two-up .col { +				width: 50% !important; +			} + +			.no-stack .col.num4 { +				width: 33% !important; +			} + +			.no-stack .col.num8 { +				width: 66% !important; +			} + +			.no-stack .col.num4 { +				width: 33% !important; +			} + +			.no-stack .col.num3 { +				width: 25% !important; +			} + +			.no-stack .col.num6 { +				width: 50% !important; +			} + +			.no-stack .col.num9 { +				width: 75% !important; +			} + +		} +	</style> +</head> + +<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;"> +	<!--[if IE]><div class="ie-browser"><![endif]--> +	<table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" +		style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;" +		valign="top" width="100%"> +		<tbody> +			<tr style="vertical-align: top;" valign="top"> +				<td style="word-break: break-word; vertical-align: top;" valign="top"> +					<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]--> + +					<%# header %> +					<div style="background-color:transparent;"> +						<div class="block-grid" +							style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> +							<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> +								<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> +								<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> +								<div class="col num12" +									style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> +									<div style="width:100% !important;"> +										<!--[if (!mso)&(!IE)]><!--> +										<div +											style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> +											<!--<![endif]--> +											<div align="center" class="img-container center" +												style="padding-right: 0px;padding-left: 0px;"> +												<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img +													align="center" alt="Image" border="0" class="center" src="<%= @logo_url %>" +													style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;" +													title="Image" height="80" /> +												<!--[if mso]></td></tr></table><![endif]--> +											</div> +											<!--[if (!mso)&(!IE)]><!--> +										</div> +										<!--<![endif]--> +									</div> +								</div> +								<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +								<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> +							</div> +						</div> +					</div> + + +					<%# title %> +					<%= if @title do %> +						<div style="background-color:transparent;"> +							<div class="block-grid" +								style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> +								<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> +									<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> +									<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> +									<div class="col num12" +										style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> +										<div style="width:100% !important;"> +											<!--[if (!mso)&(!IE)]><!--> +											<div +												style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> +												<!--<![endif]--> +												<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> +												<div +													style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> +													<div +														style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;"> +														<p style="line-height: 36px; text-align: center; margin: 0;"><span +																style="font-size: 30px; color: <%= @styling.header_color %>;"><%= @title %></span></p> +													</div> +												</div> +												<!--[if mso]></td></tr></table><![endif]--> +												<!--[if (!mso)&(!IE)]><!--> +											</div> +											<!--<![endif]--> +										</div> +									</div> +									<!--[if (mso)|(IE)]></td></tr></table><![endif]--> +									<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> +								</div> +							</div> +						</div> +					<% end %> +					<%= render @view_module, @view_template, assigns %> + +				</td> +			</tr> +		</tbody> +	</table> +	<!--[if (IE)]></div><![endif]--> +</body> + +</html> 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 e0d4d5632..fbf31c7eb 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do @@ -69,7 +69,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do      with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},           {:ok, _, _, _} <- CommonAPI.follow(user, followee) do -      render(conn, "followed.html", %{error: false}) +      redirect(conn, to: "/users/#{followee.id}")      else        error ->          handle_follow_error(conn, error) @@ -80,7 +80,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do      with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},           {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},           {:ok, _, _, _} <- CommonAPI.follow(user, followee) do -      render(conn, "followed.html", %{error: false}) +      redirect(conn, to: "/users/#{followee.id}")      else        error ->          handle_follow_error(conn, error) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index f08b9d28c..bca0e26eb 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.TwitterAPI.UtilController do diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex index d469c4726..c05c7821c 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index b506a234b..6b0fbe61e 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -12,4 +12,8 @@ defmodule Pleroma.Web.EmailView do      |> Timex.parse!("{ISO:Extended:Z}")      |> Timex.format!("{Mshort} {D}, {YYYY} {h24}:{m}")    end + +  def admin_user_url(%{id: id}) do +    Pleroma.Web.Endpoint.url() <> "/pleroma/admin/#/users/" <> id +  end  end diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index 2cbc6b64d..3c5820a86 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.AttachmentsCleanupWorker do diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index ac2fe6946..598df6580 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.BackgroundWorker do diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex index a24407874..341eff054 100644 --- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex +++ b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex index 0a00129df..c589a59eb 100644 --- a/lib/pleroma/workers/cron/digest_emails_worker.ex +++ b/lib/pleroma/workers/cron/digest_emails_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.Cron.DigestEmailsWorker do diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex new file mode 100644 index 000000000..951c2c054 --- /dev/null +++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User + +  import Ecto.Query + +  use Pleroma.Workers.WorkerHelper, queue: "new_users_digest" + +  @impl Oban.Worker +  def perform(_args, _job) do +    if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do +      today = NaiveDateTime.utc_now() |> Timex.beginning_of_day() + +      a_day_ago = +        today +        |> Timex.shift(days: -1) +        |> Timex.beginning_of_day() + +      users_and_statuses = +        %{ +          local: true, +          order_by: :inserted_at +        } +        |> User.Query.build() +        |> where([u], u.inserted_at >= ^a_day_ago and u.inserted_at < ^today) +        |> Repo.all() +        |> Enum.map(fn user -> +          latest_status = +            Activity +            |> Activity.Queries.by_actor(user.ap_id) +            |> Activity.Queries.by_type("Create") +            |> Activity.with_preloaded_object() +            |> order_by(desc: :inserted_at) +            |> limit(1) +            |> Repo.one() + +          total_statuses = +            Activity +            |> Activity.Queries.by_actor(user.ap_id) +            |> Activity.Queries.by_type("Create") +            |> Repo.aggregate(:count, :id) + +          {user, total_statuses, latest_status} +        end) + +      if users_and_statuses != [] do +        %{is_admin: true} +        |> User.Query.build() +        |> Repo.all() +        |> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses)) +        |> Enum.each(&Pleroma.Emails.Mailer.deliver/1) +      end +    end +  end +end diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex index 7a52860a9..b8953dd7f 100644 --- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex +++ b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex index 425ad41ca..e9b8d59c4 100644 --- a/lib/pleroma/workers/cron/stats_worker.ex +++ b/lib/pleroma/workers/cron/stats_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.Cron.StatsWorker do diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex new file mode 100644 index 000000000..ec6534f21 --- /dev/null +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.RemoteFetcherWorker do +  alias Pleroma.Object.Fetcher + +  use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher" + +  @impl Oban.Worker +  def perform( +        %{ +          "op" => "fetch_remote", +          "id" => id +        } = args, +        _job +      ) do +    {:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"]) +  end +end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index bd41ab4ce..8905f4ad0 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Workers.ScheduledActivityWorker do | 
