Merge branch 'by-approval' into 'develop'
authorlain <lain@soykaf.club>
Wed, 29 Jul 2020 11:27:26 +0000 (11:27 +0000)
committerlain <lain@soykaf.club>
Wed, 29 Jul 2020 11:27:26 +0000 (11:27 +0000)
Registrations "by approval" mode

Closes #1931

See merge request pleroma/pleroma!2757

17 files changed:
config/config.exs
config/description.exs
docs/configuration/cheatsheet.md
lib/pleroma/gun/connection_pool.ex
lib/pleroma/gun/connection_pool/worker.ex
lib/pleroma/plugs/frontend_static.ex [new file with mode: 0644]
lib/pleroma/plugs/instance_static.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/pipeline.ex
lib/pleroma/web/endpoint.ex
lib/pleroma/web/views/masto_fe_view.ex
mix.exs
test/plugs/frontend_static_test.exs [new file with mode: 0644]
test/plugs/instance_static_test.exs
test/web/activity_pub/pipeline_test.exs
test/web/activity_pub/side_effects_test.exs
test/web/common_api/common_api_test.exs

index 1dc196a6b2a96a6c284b73b3131ab767a2a54b4e..4b91a58b746de9830726a80d8a8524e8a997852e 100644 (file)
@@ -647,6 +647,16 @@ config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true
 
 config :pleroma, :static_fe, enabled: false
 
+# Example of frontend configuration
+# This example will make us serve the primary frontend from the
+# frontends directory within your `:pleroma, :instance, static_dir`.
+# e.g., instance/static/frontends/pleroma/develop/
+#
+# With no frontend configuration, the bundled files from the `static` directory will
+# be used.
+#
+# config :pleroma, :frontends, primary: %{"name" => "pleroma", "ref" => "develop"}
+
 config :pleroma, :web_cache_ttl,
   activity_pub: nil,
   activity_pub_question: 30_000
index df9f256ef6854199e2d886bf411656f6afb826ed..30a503696c2271a14738316d837e2f56a38610a6 100644 (file)
@@ -3494,5 +3494,30 @@ config :pleroma, :config_description, [
         suggestions: ["s3.eu-central-1.amazonaws.com"]
       }
     ]
+  },
+  %{
+    group: :pleroma,
+    key: :frontends,
+    type: :group,
+    description: "Installed frontends management",
+    children: [
+      %{
+        key: :primary,
+        type: :map,
+        description: "Primary frontend, the one that is served for all pages by default",
+        children: [
+          %{
+            key: "name",
+            type: :string,
+            description: "Name of the installed primary frontend"
+          },
+          %{
+            key: "ref",
+            type: :string,
+            description: "reference of the installed primary frontend to be used"
+          }
+        ]
+      }
+    ]
   }
 ]
index c89df24cc9b6e3a6df4d820f4597644a6ebda3b2..d6a9276eeb6f7613b3c61f79c11da79ca3df156d 100644 (file)
@@ -1048,3 +1048,25 @@ Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practi
 Control favicons for instances.
 
 * `enabled`: Allow/disallow displaying and getting instances favicons
+
+## Frontend management
+
+Frontends in Pleroma are swappable - you can specify which one to use here.
+
+For now, you can set a frontend with the key `primary` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref.
+
+The key `primary` refers to the frontend that will be served by default for general requests. In the future, other frontends like the admin frontend will also be configurable here.
+
+If you don't set anything here, the bundled frontend will be used.
+
+Example:
+
+```
+config :pleroma, :frontends,
+  primary: %{
+    "name" => "pleroma",
+    "ref" => "stable"
+  }
+```
+
+This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit.
index 8b41a668c0268c504ec55ddc05bbb7545aedcffe..49e9885bbac3b82d224c901c458a7c41d5225901 100644 (file)
@@ -19,7 +19,7 @@ defmodule Pleroma.Gun.ConnectionPool do
         get_gun_pid_from_worker(worker_pid, true)
 
       [{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] ->
-        GenServer.cast(worker_pid, {:add_client, self(), false})
+        GenServer.call(worker_pid, :add_client)
         {:ok, gun_pid}
 
       [] ->
@@ -45,7 +45,7 @@ defmodule Pleroma.Gun.ConnectionPool do
     # so instead we use cast + monitor
 
     ref = Process.monitor(worker_pid)
-    if register, do: GenServer.cast(worker_pid, {:add_client, self(), true})
+    if register, do: GenServer.cast(worker_pid, {:add_client, self()})
 
     receive do
       {:conn_pid, pid} ->
@@ -70,7 +70,7 @@ defmodule Pleroma.Gun.ConnectionPool do
 
     case query_result do
       [worker_pid] ->
-        GenServer.cast(worker_pid, {:remove_client, self()})
+        GenServer.call(worker_pid, :remove_client)
 
       [] ->
         :ok
index f33447cb6352c137f88e2b15757c1145bd9445b0..fec9d0efa9daa0323a02e26159c491e00313424d 100644 (file)
@@ -36,7 +36,24 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
   end
 
   @impl true
-  def handle_cast({:add_client, client_pid, send_pid_back}, %{key: key} = state) do
+  def handle_cast({:add_client, client_pid}, state) do
+    case handle_call(:add_client, {client_pid, nil}, state) do
+      {:reply, conn_pid, state, :hibernate} ->
+        send(client_pid, {:conn_pid, conn_pid})
+        {:noreply, state, :hibernate}
+    end
+  end
+
+  @impl true
+  def handle_cast({:remove_client, client_pid}, state) do
+    case handle_call(:remove_client, {client_pid, nil}, state) do
+      {:reply, _, state, :hibernate} ->
+        {:noreply, state, :hibernate}
+    end
+  end
+
+  @impl true
+  def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do
     time = :erlang.monotonic_time(:millisecond)
 
     {{conn_pid, _, _, _}, _} =
@@ -44,8 +61,6 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
         {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
       end)
 
-    if send_pid_back, do: send(client_pid, {:conn_pid, conn_pid})
-
     state =
       if state.timer != nil do
         Process.cancel_timer(state[:timer])
@@ -57,11 +72,11 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
     ref = Process.monitor(client_pid)
 
     state = put_in(state.client_monitors[client_pid], ref)
-    {:noreply, state, :hibernate}
+    {:reply, conn_pid, state, :hibernate}
   end
 
   @impl true
-  def handle_cast({:remove_client, client_pid}, %{key: key} = state) do
+  def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
     {{_conn_pid, used_by, _crf, _last_reference}, _} =
       Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
         {conn_pid, List.delete(used_by, client_pid), crf, last_reference}
@@ -78,7 +93,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
         nil
       end
 
-    {:noreply, %{state | timer: timer}, :hibernate}
+    {:reply, :ok, %{state | timer: timer}, :hibernate}
   end
 
   @impl true
@@ -102,22 +117,13 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
 
   @impl true
   def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
-    # Sometimes the client is dead before we demonitor it in :remove_client, so the message
-    # arrives anyway
+    :telemetry.execute(
+      [:pleroma, :connection_pool, :client_death],
+      %{client_pid: pid, reason: reason},
+      %{key: state.key}
+    )
 
-    case state.client_monitors[pid] do
-      nil ->
-        {:noreply, state, :hibernate}
-
-      _ref ->
-        :telemetry.execute(
-          [:pleroma, :connection_pool, :client_death],
-          %{client_pid: pid, reason: reason},
-          %{key: state.key}
-        )
-
-        handle_cast({:remove_client, pid}, state)
-    end
+    handle_cast({:remove_client, pid}, state)
   end
 
   # LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/plugs/frontend_static.ex
new file mode 100644 (file)
index 0000000..f549ca7
--- /dev/null
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.FrontendStatic do
+  require Pleroma.Constants
+
+  @moduledoc """
+  This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
+  """
+  @behaviour Plug
+
+  def file_path(path, frontend_type \\ :primary) do
+    if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
+      instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+
+      Path.join([
+        instance_static_path,
+        "frontends",
+        configuration["name"],
+        configuration["ref"],
+        path
+      ])
+    else
+      nil
+    end
+  end
+
+  def init(opts) do
+    opts
+    |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
+    |> Plug.Static.init()
+  end
+
+  def call(conn, opts) do
+    frontend_type = Map.get(opts, :frontend_type, :primary)
+    path = file_path("", frontend_type)
+
+    if path do
+      conn
+      |> call_static(opts, path)
+    else
+      conn
+    end
+  end
+
+  defp call_static(conn, opts, from) do
+    opts =
+      opts
+      |> Map.put(:from, from)
+
+    Plug.Static.call(conn, opts)
+  end
+end
index 7516f75c38bd747f37823a44b8bba4544aed102c..0fb57e42257f5927cd6c7e6299782d8526ff9c4f 100644 (file)
@@ -16,28 +16,24 @@ defmodule Pleroma.Plugs.InstanceStatic do
     instance_path =
       Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
 
-    if File.exists?(instance_path) do
-      instance_path
-    else
+    frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary)
+
+    (File.exists?(instance_path) && instance_path) ||
+      (frontend_path && File.exists?(frontend_path) && frontend_path) ||
       Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
-    end
   end
 
   def init(opts) do
     opts
     |> Keyword.put(:from, "__unconfigured_instance_static_plug")
-    |> Keyword.put(:at, "/__unconfigured_instance_static_plug")
     |> Plug.Static.init()
   end
 
   for only <- Pleroma.Constants.static_only_files() do
-    at = Plug.Router.Utils.split("/")
-
     def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
       call_static(
         conn,
         opts,
-        unquote(at),
         Pleroma.Config.get([:instance, :static_dir], "instance/static")
       )
     end
@@ -47,11 +43,10 @@ defmodule Pleroma.Plugs.InstanceStatic do
     conn
   end
 
-  defp call_static(conn, opts, at, from) do
+  defp call_static(conn, opts, from) do
     opts =
       opts
       |> Map.put(:from, from)
-      |> Map.put(:at, at)
 
     Plug.Static.call(conn, opts)
   end
index df926829c4eb42d36201df45f2c2dc13e7bfe6b5..0dcc7be4dbd778a1eebace51c8848e4f86cb76cd 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   the system.
   """
 
+  alias Pleroma.Activity
   alias Pleroma.EctoType.ActivityPub.ObjectValidators
   alias Pleroma.Object
   alias Pleroma.User
@@ -71,6 +72,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
            |> UndoValidator.cast_and_validate()
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
+      undone_object = Activity.get_by_ap_id(object["object"])
+
+      meta =
+        meta
+        |> Keyword.put(:object_data, undone_object.data)
+
       {:ok, object, meta}
     end
   end
index 6875c47f67e230e412b1f1f050bbc1e170326256..36e325c373d1463c9ac21f7a51913bf440201617 100644 (file)
@@ -52,6 +52,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
       do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
 
       if !do_not_federate && local do
+        activity =
+          if object = Keyword.get(meta, :object_data) do
+            %{activity | data: Map.put(activity.data, "object", object)}
+          else
+            activity
+          end
+
         Federator.publish(activity)
         {:ok, :federated}
       else
index 226d42c2c7a0171922f668c56bd4f5816b131728..527fb288d39766e101ead334d0571904e10dce46 100644 (file)
@@ -28,6 +28,17 @@ defmodule Pleroma.Web.Endpoint do
     }
   )
 
+  # Careful! No `only` restriction here, as we don't know what frontends contain.
+  plug(Pleroma.Plugs.FrontendStatic,
+    at: "/",
+    frontend_type: :primary,
+    gzip: true,
+    cache_control_for_etags: @static_cache_control,
+    headers: %{
+      "cache-control" => @static_cache_control
+    }
+  )
+
   # Serve at "/" the static files from "priv/static" directory.
   #
   # You should set gzip to true if you are running phoenix.digest
index f739dacb61221722a3c53f744529fb9fa11d2a27..b1669d1986809d98b9985405f82e5e7f6ab021b7 100644 (file)
@@ -9,36 +9,6 @@ defmodule Pleroma.Web.MastoFEView do
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.CustomEmojiView
 
-  @default_settings %{
-    onboarded: true,
-    home: %{
-      shows: %{
-        reblog: true,
-        reply: true
-      }
-    },
-    notifications: %{
-      alerts: %{
-        follow: true,
-        favourite: true,
-        reblog: true,
-        mention: true
-      },
-      shows: %{
-        follow: true,
-        favourite: true,
-        reblog: true,
-        mention: true
-      },
-      sounds: %{
-        follow: true,
-        favourite: true,
-        reblog: true,
-        mention: true
-      }
-    }
-  }
-
   def initial_state(token, user, custom_emojis) do
     limit = Config.get([:instance, :limit])
 
@@ -86,7 +56,7 @@ defmodule Pleroma.Web.MastoFEView do
           "video\/mp4"
         ]
       },
-      settings: user.mastofe_settings || @default_settings,
+      settings: user.mastofe_settings || %{},
       push_subscription: nil,
       accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
       custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
diff --git a/mix.exs b/mix.exs
index da0e88287e9992e2b7935f3f816fe722d8aafc59..a14b0c51a350fea0bff83b69056792a1f1219ca0 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -145,7 +145,7 @@ defmodule Pleroma.Mixfile do
       {:ex_aws, "~> 2.1"},
       {:ex_aws_s3, "~> 2.0"},
       {:sweet_xml, "~> 0.6.6"},
-      {:earmark, "~> 1.3"},
+      {:earmark, "1.4.3"},
       {:bbcode_pleroma, "~> 0.2.0"},
       {:ex_machina, "~> 2.3", only: :test},
       {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
diff --git a/test/plugs/frontend_static_test.exs b/test/plugs/frontend_static_test.exs
new file mode 100644 (file)
index 0000000..d11d91d
--- /dev/null
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FrontendStaticPlugTest do
+  use Pleroma.Web.ConnCase
+
+  @dir "test/tmp/instance_static"
+
+  setup do
+    File.mkdir_p!(@dir)
+    on_exit(fn -> File.rm_rf(@dir) end)
+  end
+
+  setup do: clear_config([:instance, :static_dir], @dir)
+
+  test "overrides existing static files", %{conn: conn} do
+    name = "pelmora"
+    ref = "uguu"
+
+    clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+    path = "#{@dir}/frontends/#{name}/#{ref}"
+
+    File.mkdir_p!(path)
+    File.write!("#{path}/index.html", "from frontend plug")
+
+    index = get(conn, "/")
+    assert html_response(index, 200) == "from frontend plug"
+  end
+end
index be2613ad098ce0907345679811639017a7a91b30..d42ba817efd87366e412f21b07a7dbb98fd11c4e 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.RuntimeStaticPlugTest do
+defmodule Pleroma.Web.InstanceStaticPlugTest do
   use Pleroma.Web.ConnCase
 
   @dir "test/tmp/instance_static"
@@ -24,6 +24,28 @@ defmodule Pleroma.Web.RuntimeStaticPlugTest do
     assert html_response(index, 200) == "hello world"
   end
 
+  test "also overrides frontend files", %{conn: conn} do
+    name = "pelmora"
+    ref = "uguu"
+
+    clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+
+    bundled_index = get(conn, "/")
+    refute html_response(bundled_index, 200) == "from frontend plug"
+
+    path = "#{@dir}/frontends/#{name}/#{ref}"
+    File.mkdir_p!(path)
+    File.write!("#{path}/index.html", "from frontend plug")
+
+    index = get(conn, "/")
+    assert html_response(index, 200) == "from frontend plug"
+
+    File.write!(@dir <> "/index.html", "from instance static")
+
+    index = get(conn, "/")
+    assert html_response(index, 200) == "from instance static"
+  end
+
   test "overrides any file in static/static" do
     bundled_index = get(build_conn(), "/static/terms-of-service.html")
 
index 8deb64501380858e083e6757bd5d03a2e6391469..f2a231eaf1780e86d9433ccbc846f06eb5ee637f 100644 (file)
@@ -14,6 +14,51 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
       :ok
     end
 
+    test "when given an `object_data` in meta, Federation will receive a the original activity with the `object` field set to this embedded object" do
+      activity = insert(:note_activity)
+      object = %{"id" => "1", "type" => "Love"}
+      meta = [local: true, object_data: object]
+
+      activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
+
+      with_mocks([
+        {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
+        {
+          Pleroma.Web.ActivityPub.MRF,
+          [],
+          [filter: fn o -> {:ok, o} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.ActivityPub,
+          [],
+          [persist: fn o, m -> {:ok, o, m} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.SideEffects,
+          [],
+          [
+            handle: fn o, m -> {:ok, o, m} end,
+            handle_after_transaction: fn m -> m end
+          ]
+        },
+        {
+          Pleroma.Web.Federator,
+          [],
+          [publish: fn _o -> :ok end]
+        }
+      ]) do
+        assert {:ok, ^activity, ^meta} =
+                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+
+        assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
+        assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
+        refute called(Pleroma.Web.Federator.publish(activity))
+        assert_called(Pleroma.Web.Federator.publish(activity_with_object))
+      end
+    end
+
     test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
       activity = insert(:note_activity)
       meta = [local: true]
index 2649b060ab53f0881a4473670f50e64b83101b12..4a08eb7ee4c36329423e201b7794db9b242e126d 100644 (file)
@@ -312,8 +312,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
       }
     end
 
-    test "deletes the original block", %{block_undo: block_undo, block: block} do
-      {:ok, _block_undo, _} = SideEffects.handle(block_undo)
+    test "deletes the original block", %{
+      block_undo: block_undo,
+      block: block
+    } do
+      {:ok, _block_undo, _meta} = SideEffects.handle(block_undo)
+
       refute Activity.get_by_id(block.id)
     end
 
index 7e11fede3d8247563bfe38a7ae7bbc94a56971f3..313dda21b491006786b01aa2beef24b62e476fc8 100644 (file)
@@ -624,14 +624,27 @@ defmodule Pleroma.Web.CommonAPITest do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
-      {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+      clear_config([:instance, :federating], true)
+
+      with_mock Pleroma.Web.Federator,
+        publish: fn _ -> nil end do
+        {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+        {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+        {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
 
-      {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
+        assert unreaction.data["type"] == "Undo"
+        assert unreaction.data["object"] == reaction.data["id"]
+        assert unreaction.local
 
-      assert unreaction.data["type"] == "Undo"
-      assert unreaction.data["object"] == reaction.data["id"]
-      assert unreaction.local
+        # On federation, it contains the undone (and deleted) object
+        unreaction_with_object = %{
+          unreaction
+          | data: Map.put(unreaction.data, "object", reaction.data)
+        }
+
+        assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
+      end
     end
 
     test "repeating a status" do