aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/config.ex1
-rw-r--r--lib/mix/tasks/pleroma/refresh_counter_cache.ex49
-rw-r--r--lib/pleroma/application.ex2
-rw-r--r--lib/pleroma/application_requirements.ex107
-rw-r--r--lib/pleroma/config/config_db.ex4
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex44
-rw-r--r--lib/pleroma/counter_cache.ex66
-rw-r--r--lib/pleroma/emoji/pack.ex1
-rw-r--r--lib/pleroma/following_relationship.ex1
-rw-r--r--lib/pleroma/http/ex_aws.ex22
-rw-r--r--lib/pleroma/http/http.ex8
-rw-r--r--lib/pleroma/http/tzdata.ex25
-rw-r--r--lib/pleroma/repo.ex37
-rw-r--r--lib/pleroma/stats.ex21
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex22
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex3
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex15
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex28
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex11
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/update_validator.ex59
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex20
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex33
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex6
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex53
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex2
-rw-r--r--lib/pleroma/web/router.ex1
28 files changed, 471 insertions, 176 deletions
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index f1b3a8766..d5129d410 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -52,6 +52,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do
+ shell_info("Migrating settings from file: #{Path.expand(config_file)}")
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
diff --git a/lib/mix/tasks/pleroma/refresh_counter_cache.ex b/lib/mix/tasks/pleroma/refresh_counter_cache.ex
index 15b4dbfa6..efcbaa3b1 100644
--- a/lib/mix/tasks/pleroma/refresh_counter_cache.ex
+++ b/lib/mix/tasks/pleroma/refresh_counter_cache.ex
@@ -17,30 +17,53 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
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}")
+ instances =
+ Activity
+ |> distinct([a], true)
+ |> select([a], fragment("split_part(?, '/', 3)", a.actor))
+ |> Repo.all()
+
+ instances
+ |> Enum.with_index(1)
+ |> Enum.each(fn {instance, i} ->
+ counters = instance_counters(instance)
+ CounterCache.set(instance, counters)
+
+ Mix.Pleroma.shell_info(
+ "[#{i}/#{length(instances)}] Setting #{instance} counters: #{inspect(counters)}"
+ )
end)
Mix.Pleroma.shell_info("Done")
end
- defp status_visibility_count_query(visibility) do
+ defp instance_counters(instance) do
+ counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
+
Activity
- |> where(
+ |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
+ |> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance))
+ |> select(
+ [a],
+ {fragment(
+ "activity_visibility(?, ?, ?)",
+ a.actor,
+ a.recipients,
+ a.data
+ ), count(a.id)}
+ )
+ |> group_by(
[a],
fragment(
- "activity_visibility(?, ?, ?) = ?",
+ "activity_visibility(?, ?, ?)",
a.actor,
a.recipients,
- a.data,
- ^visibility
+ a.data
)
)
- |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
- |> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
+ |> Repo.all(timeout: :timer.minutes(30))
+ |> Enum.reduce(counters, fn {visibility, count}, acc ->
+ Map.put(acc, visibility, count)
+ end)
end
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 4a21bf138..9615af122 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Application do
Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
- Pleroma.Repo.check_migrations_applied!()
+ Pleroma.ApplicationRequirements.verify!()
setup_instrumenters()
load_custom_modules()
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
new file mode 100644
index 000000000..88575a498
--- /dev/null
+++ b/lib/pleroma/application_requirements.ex
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ApplicationRequirements do
+ @moduledoc """
+ The module represents the collection of validations to runs before start server.
+ """
+
+ defmodule VerifyError, do: defexception([:message])
+
+ import Ecto.Query
+
+ require Logger
+
+ @spec verify!() :: :ok | VerifyError.t()
+ def verify! do
+ :ok
+ |> check_migrations_applied!()
+ |> check_rum!()
+ |> handle_result()
+ end
+
+ defp handle_result(:ok), do: :ok
+ defp handle_result({:error, message}), do: raise(VerifyError, message: message)
+
+ # Checks for pending migrations.
+ #
+ def check_migrations_applied!(:ok) do
+ unless Pleroma.Config.get(
+ [:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
+ false
+ ) do
+ {_, res, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ down_migrations =
+ Ecto.Migrator.migrations(repo)
+ |> Enum.reject(fn
+ {:up, _, _} -> true
+ {:down, _, _} -> false
+ end)
+
+ if length(down_migrations) > 0 do
+ down_migrations_text =
+ Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
+
+ Logger.error(
+ "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
+ )
+
+ {:error, "Unapplied Migrations detected"}
+ else
+ :ok
+ end
+ end)
+
+ res
+ else
+ :ok
+ end
+ end
+
+ def check_migrations_applied!(result), do: result
+
+ # Checks for settings of RUM indexes.
+ #
+ defp check_rum!(:ok) do
+ {_, res, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ migrate =
+ from(o in "columns",
+ where: o.table_name == "objects",
+ where: o.column_name == "fts_content"
+ )
+ |> repo.exists?(prefix: "information_schema")
+
+ setting = Pleroma.Config.get([:database, :rum_enabled], false)
+
+ do_check_rum!(setting, migrate)
+ end)
+
+ res
+ end
+
+ defp check_rum!(result), do: result
+
+ defp do_check_rum!(setting, migrate) do
+ case {setting, migrate} do
+ {true, false} ->
+ Logger.error(
+ "Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"
+ )
+
+ {:error, "Unapplied RUM Migrations detected"}
+
+ {false, true} ->
+ Logger.error(
+ "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"
+ )
+
+ {:error, "RUM Migrations detected"}
+
+ _ ->
+ :ok
+ end
+ end
+end
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex
index 2f4eb8581..1a89d8895 100644
--- a/lib/pleroma/config/config_db.ex
+++ b/lib/pleroma/config/config_db.ex
@@ -167,7 +167,9 @@ defmodule Pleroma.ConfigDB do
end)
end
- @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
+ @spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
+ def delete(%ConfigDB{} = config), do: Repo.delete(config)
+
def delete(params) do
search_opts = Map.delete(params, :subkeys)
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index b68ded01f..0a6c724fb 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -3,9 +3,23 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
+ alias Pleroma.Config
+
require Logger
alias Pleroma.Config
+ @type config_namespace() :: [atom()]
+ @type config_map() :: {config_namespace(), config_namespace(), String.t()}
+
+ @mrf_config_map [
+ {[:instance, :rewrite_policy], [:mrf, :policies],
+ "\n* `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`"},
+ {[:instance, :mrf_transparency], [:mrf, :transparency],
+ "\n* `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`"},
+ {[:instance, :mrf_transparency_exclusions], [:mrf, :transparency_exclusions],
+ "\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
+ ]
+
def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@@ -39,5 +53,35 @@ defmodule Pleroma.Config.DeprecationWarnings do
def warn do
check_hellthread_threshold()
mrf_user_allowlist()
+ check_old_mrf_config()
+ end
+
+ def check_old_mrf_config do
+ warning_preface = """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
+ """
+
+ move_namespace_and_warn(@mrf_config_map, warning_preface)
+ end
+
+ @spec move_namespace_and_warn([config_map()], String.t()) :: :ok
+ def move_namespace_and_warn(config_map, warning_preface) do
+ warning =
+ Enum.reduce(config_map, "", fn
+ {old, new, err_msg}, acc ->
+ old_config = Config.get(old)
+
+ if old_config do
+ Config.put(new, old_config)
+ acc <> err_msg
+ else
+ acc
+ end
+ end)
+
+ if warning != "" do
+ Logger.warn(warning_preface <> warning)
+ end
end
end
diff --git a/lib/pleroma/counter_cache.ex b/lib/pleroma/counter_cache.ex
index 4d348a413..ebd1f603d 100644
--- a/lib/pleroma/counter_cache.ex
+++ b/lib/pleroma/counter_cache.ex
@@ -10,32 +10,70 @@ defmodule Pleroma.CounterCache do
import Ecto.Query
schema "counter_cache" do
- field(:name, :string)
- field(:count, :integer)
+ field(:instance, :string)
+ field(:public, :integer)
+ field(:unlisted, :integer)
+ field(:private, :integer)
+ field(:direct, :integer)
end
def changeset(struct, params) do
struct
- |> cast(params, [:name, :count])
- |> validate_required([:name])
- |> unique_constraint(:name)
+ |> cast(params, [:instance, :public, :unlisted, :private, :direct])
+ |> validate_required([:instance])
+ |> unique_constraint(:instance)
end
- def get_as_map(names) when is_list(names) do
+ def get_by_instance(instance) 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)
+ |> select([c], %{
+ "public" => c.public,
+ "unlisted" => c.unlisted,
+ "private" => c.private,
+ "direct" => c.direct
+ })
+ |> where([c], c.instance == ^instance)
+ |> Repo.one()
+ |> case do
+ nil -> %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
+ val -> val
+ end
end
- def set(name, count) do
+ def get_sum do
+ CounterCache
+ |> select([c], %{
+ "public" => type(sum(c.public), :integer),
+ "unlisted" => type(sum(c.unlisted), :integer),
+ "private" => type(sum(c.private), :integer),
+ "direct" => type(sum(c.direct), :integer)
+ })
+ |> Repo.one()
+ end
+
+ def set(instance, values) do
+ params =
+ Enum.reduce(
+ ["public", "private", "unlisted", "direct"],
+ %{"instance" => instance},
+ fn param, acc ->
+ Map.put_new(acc, param, Map.get(values, param, 0))
+ end
+ )
+
%CounterCache{}
- |> changeset(%{"name" => name, "count" => count})
+ |> changeset(params)
|> Repo.insert(
- on_conflict: [set: [count: count]],
+ on_conflict: [
+ set: [
+ public: params["public"],
+ private: params["private"],
+ unlisted: params["unlisted"],
+ direct: params["direct"]
+ ]
+ ],
returning: true,
- conflict_target: :name
+ conflict_target: :instance
)
end
end
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index 787ff8141..d076ae312 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -45,6 +45,7 @@ defmodule Pleroma.Emoji.Pack do
shortcodes =
pack.files
|> Map.keys()
+ |> Enum.sort()
|> paginate(opts[:page], opts[:page_size])
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index 093b1f405..c2020d30a 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -124,6 +124,7 @@ defmodule Pleroma.FollowingRelationship do
|> join(:inner, [r], f in assoc(r, :follower))
|> where([r], r.state == ^:follow_pending)
|> where([r], r.following_id == ^id)
+ |> where([r, f], f.deactivated != true)
|> select([r, f], f)
|> Repo.all()
end
diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex
new file mode 100644
index 000000000..e53e64077
--- /dev/null
+++ b/lib/pleroma/http/ex_aws.ex
@@ -0,0 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.ExAws do
+ @moduledoc false
+
+ @behaviour ExAws.Request.HttpClient
+
+ alias Pleroma.HTTP
+
+ @impl true
+ def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do
+ case HTTP.request(method, url, body, headers, http_opts) do
+ {:ok, env} ->
+ {:ok, %{status_code: env.status, headers: env.headers, body: env.body}}
+
+ {:error, reason} ->
+ {:error, %{reason: reason}}
+ end
+ end
+end
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index 583b56484..66ca75367 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.HTTP do
require Logger
@type t :: __MODULE__
+ @type method() :: :get | :post | :put | :delete | :head
@doc """
Performs GET request.
@@ -28,6 +29,9 @@ defmodule Pleroma.HTTP do
def get(nil, _, _), do: nil
def get(url, headers, options), do: request(:get, url, "", headers, options)
+ @spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
+ def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
+
@doc """
Performs POST request.
@@ -42,7 +46,7 @@ defmodule Pleroma.HTTP do
Builds and performs http request.
# Arguments:
- `method` - :get, :post, :put, :delete
+ `method` - :get, :post, :put, :delete, :head
`url` - full url
`body` - request body
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
@@ -52,7 +56,7 @@ defmodule Pleroma.HTTP do
`{:ok, %Tesla.Env{}}` or `{:error, error}`
"""
- @spec request(atom(), Request.url(), String.t(), Request.headers(), keyword()) ::
+ @spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) ::
{:ok, Env.t()} | {:error, any()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)
diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex
new file mode 100644
index 000000000..34bb253a7
--- /dev/null
+++ b/lib/pleroma/http/tzdata.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Tzdata do
+ @moduledoc false
+
+ @behaviour Tzdata.HTTPClient
+
+ alias Pleroma.HTTP
+
+ @impl true
+ def get(url, headers, options) do
+ with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
+ {:ok, {env.status, env.headers, env.body}}
+ end
+ end
+
+ @impl true
+ def head(url, headers, options) do
+ with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
+ {:ok, {env.status, env.headers}}
+ end
+ end
+end
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index 6d85d70bc..f317e4d58 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -11,9 +11,7 @@ defmodule Pleroma.Repo do
import Ecto.Query
require Logger
- defmodule Instrumenter do
- use Prometheus.EctoInstrumenter
- end
+ defmodule Instrumenter, do: use(Prometheus.EctoInstrumenter)
@doc """
Dynamically loads the repository url from the
@@ -51,35 +49,6 @@ defmodule Pleroma.Repo do
end
end
- def check_migrations_applied!() do
- unless Pleroma.Config.get(
- [:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
- false
- ) do
- Ecto.Migrator.with_repo(__MODULE__, fn repo ->
- down_migrations =
- Ecto.Migrator.migrations(repo)
- |> Enum.reject(fn
- {:up, _, _} -> true
- {:down, _, _} -> false
- end)
-
- if length(down_migrations) > 0 do
- down_migrations_text =
- Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
-
- Logger.error(
- "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
- )
-
- raise Pleroma.Repo.UnappliedMigrationsError
- end
- end)
- else
- :ok
- end
- end
-
def chunk_stream(query, chunk_size) do
# We don't actually need start and end funcitons of resource streaming,
# but it seems to be the only way to not fetch records one-by-one and
@@ -107,7 +76,3 @@ defmodule Pleroma.Repo do
)
end
end
-
-defmodule Pleroma.Repo.UnappliedMigrationsError do
- defexception message: "Unapplied Migrations detected"
-end
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 6b3a8a41f..9a03f01db 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -97,20 +97,11 @@ 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
- }
+ def get_status_visibility_count(instance \\ nil) do
+ if is_nil(instance) do
+ CounterCache.get_sum()
+ else
+ CounterCache.get_by_instance(instance)
+ end
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 3e4d0a2be..7cd3eab39 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -321,28 +321,6 @@ 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]
-
- data =
- %{
- "to" => to,
- "cc" => cc,
- "type" => "Update",
- "actor" => actor,
- "object" => object
- }
- |> Maps.put_if_present("id", activity_id)
-
- with {:ok, activity} <- insert(data, local),
- _ <- notify_and_stream(activity),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- end
- end
-
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
{:ok, Activity.t()} | {:error, any()}
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index f0b5c6e93..220c4fe52 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -514,7 +514,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{new_user, for_user}
end
- # TODO: Add support for "object" field
@doc """
Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
@@ -525,6 +524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
Response:
- HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field
+
+ Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
"""
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 1aac62c69..135a5c431 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -123,6 +123,21 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ # Retricted to user updates for now, always public
+ @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
+ def update(actor, object) do
+ to = [Pleroma.Constants.as_public(), actor.follower_address]
+
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "type" => "Update",
+ "actor" => actor.ap_id,
+ "object" => object,
+ "to" => to
+ }, []}
+ end
+
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
def announce(actor, object, options \\ []) do
public? = Keyword.get(options, :public, false)
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 5a4a76085..206d6af52 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def filter(%{} = object), do: get_policies() |> filter(object)
def get_policies do
- Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
+ Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
@@ -51,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
base =
%{
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index b7dcb1b86..9cea6bcf9 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -3,21 +3,23 @@
# 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 Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF
+
require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
- Pleroma.Config.get([:mrf_simple, :accept])
+ Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do
accepts == [] -> {:ok, object}
- actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
+ actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
end
@@ -25,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
- Pleroma.Config.get([:mrf_simple, :reject])
+ Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@@ -41,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
)
when length(child_attachment) > 0 do
media_removal =
- Pleroma.Config.get([:mrf_simple, :media_removal])
+ Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object =
@@ -65,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
} = object
) do
media_nsfw =
- Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object =
@@ -85,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
object =
@@ -108,7 +110,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
- Pleroma.Config.get([:mrf_simple, :report_removal])
+ Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
@@ -122,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
- Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@@ -136,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
- Pleroma.Config.get([:mrf_simple, :banner_removal])
+ Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@@ -197,10 +199,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@impl true
def describe do
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Config.get([:mrf, :transparency_exclusions])
mrf_simple =
- Pleroma.Config.get(:mrf_simple)
+ Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 6a83a2c33..2c657b467 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -19,10 +19,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
def validate(object, meta)
+ def validate(%{"type" => "Update"} = update_activity, meta) do
+ with {:ok, update_activity} <-
+ update_activity
+ |> UpdateValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ update_activity = stringify_keys(update_activity)
+ {:ok, update_activity, meta}
+ end
+ end
+
def validate(%{"type" => "Undo"} = object, meta) do
with {:ok, object} <-
object
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
new file mode 100644
index 000000000..b4ba5ede0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ # In this case, we save the full object in this activity instead of just a
+ # reference, so we can always see what was actually changed by this.
+ field(:object, :map)
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :cc, :object])
+ |> validate_inclusion(:type, ["Update"])
+ |> validate_actor_presence()
+ |> validate_updating_rights()
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+
+ # For now we only support updating users, and here the rule is easy:
+ # object id == actor id
+ def validate_updating_rights(cng) do
+ with actor = get_field(cng, :actor),
+ object = get_field(cng, :object),
+ {:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
+ true <- actor == object_id do
+ cng
+ else
+ _e ->
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 1a1cc675c..de143b8f0 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -21,6 +21,26 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle(object, meta \\ [])
# Tasks this handles:
+ # - Update the user
+ #
+ # For a local user, we also get a changeset with the full information, so we
+ # can update non-federating, non-activitypub settings as well.
+ def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
+ if changeset = Keyword.get(meta, :user_update_changeset) do
+ changeset
+ |> User.update_and_set_cache()
+ else
+ {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
+
+ User.get_by_ap_id(updated_object["id"])
+ |> User.remote_user_changeset(new_user_data)
+ |> User.update_and_set_cache()
+ end
+
+ {:ok, object, meta}
+ end
+
+ # Tasks this handles:
# - Add like to object
# - Set up notification
def handle(%{data: %{"type" => "Like"}} = object, meta) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 1c60ef8f5..4e318e89c 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -684,35 +684,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
- data,
+ %{"type" => "Update"} = data,
_options
- )
- when object_type in [
- "Person",
- "Application",
- "Service",
- "Organization"
- ] do
- with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
- {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
-
- actor
- |> User.remote_user_changeset(new_user_data)
- |> User.update_and_set_cache()
-
- ActivityPub.update(%{
- local: false,
- to: data["to"] || [],
- cc: data["cc"] || [],
- object: object,
- actor: actor_id,
- activity_id: data["id"]
- })
- else
- e ->
- Logger.error(e)
- :error
+ ) do
+ with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index db2413dfe..f9545d895 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -643,10 +643,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
json(conn, "")
end
- def stats(conn, _) do
- count = Stats.get_status_visibility_count()
+ def stats(conn, params) do
+ counters = Stats.get_status_visibility_count(params["instance"])
- json(conn, %{"status_visibility" => count})
+ json(conn, %{"status_visibility" => counters})
end
defp page_params(params) do
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index d0d8bc8eb..43ec70021 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastoFEController do
|> render("manifest.json")
end
- @doc "PUT /api/web/settings"
+ @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{})
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index d50e7c5dd..7a88a847c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
@@ -182,34 +184,39 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
- changeset = User.update_changeset(user, user_params)
-
- with {:ok, user} <- User.update_and_set_cache(changeset) do
- user
- |> build_update_activity_params()
- |> ActivityPub.update()
-
- render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
+ # What happens here:
+ #
+ # We want to update the user through the pipeline, but the ActivityPub
+ # update information is not quite enough for this, because this also
+ # contains local settings that don't federate and don't even appear
+ # in the Update activity.
+ #
+ # So we first build the normal local changeset, then apply it to the
+ # user data, but don't persist it. With this, we generate the object
+ # data for our update activity. We feed this and the changeset as meta
+ # inforation into the pipeline, where they will be properly updated and
+ # federated.
+ with changeset <- User.update_changeset(user, user_params),
+ {:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
+ updated_object <-
+ Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
+ |> Map.delete("@context"),
+ {:ok, update_data, []} <- Builder.update(user, updated_object),
+ {:ok, _update, _} <-
+ Pipeline.common_pipeline(update_data,
+ local: true,
+ user_update_changeset: changeset
+ ) do
+ render(conn, "show.json",
+ user: unpersisted_user,
+ for: unpersisted_user,
+ with_pleroma_settings: true
+ )
else
_e -> render_error(conn, :forbidden, "Invalid request")
end
end
- # Hotfix, handling will be redone with the pipeline
- defp build_update_activity_params(user) do
- object =
- Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
- |> Map.delete("@context")
-
- %{
- local: true,
- to: [user.follower_address],
- cc: [],
- object: object,
- actor: user.ap_id
- }
- end
-
defp normalize_fields_attributes(fields) do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index c6b54e570..35c2fc25c 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -78,7 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
def federation do
quarantined = Config.get([:instance, :quarantined_instances], [])
- if Config.get([:instance, :mrf_transparency]) do
+ if Config.get([:mrf, :transparency]) do
{:ok, data} = MRF.describe()
data
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index eda74a171..419aa55e4 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -467,6 +467,7 @@ defmodule Pleroma.Web.Router do
scope "/api/web", Pleroma.Web do
pipe_through(:authenticated_api)
+ # Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
put("/settings", MastoFEController, :put_settings)
end