aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorlain <lain@soykaf.club>2020-08-25 11:44:51 +0200
committerlain <lain@soykaf.club>2020-08-25 11:44:51 +0200
commitc1d51944c71ff90650b1a631241daf0d8be5b861 (patch)
tree2e78efaeb892c4b09cac0439f268d6a60637b4dc /lib
parent199ad47c22e5d72741f5809eb015bac9b00cca03 (diff)
parent5722b0e2e5e7113f0422798a6ea8cc40d1953291 (diff)
downloadpleroma-c1d51944c71ff90650b1a631241daf0d8be5b861.tar.gz
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into frontend-bundles-downloads
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/pleroma.ex2
-rw-r--r--lib/mix/tasks/pleroma/database.ex34
-rw-r--r--lib/mix/tasks/pleroma/ecto/migrate.ex4
-rw-r--r--lib/mix/tasks/pleroma/ecto/rollback.ex4
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex10
-rw-r--r--lib/mix/tasks/pleroma/relay.ex10
-rw-r--r--lib/mix/tasks/pleroma/release_env.ex76
-rw-r--r--lib/pleroma/activity.ex6
-rw-r--r--lib/pleroma/activity_expiration.ex17
-rw-r--r--lib/pleroma/config.ex10
-rw-r--r--lib/pleroma/emails/user_email.ex31
-rw-r--r--lib/pleroma/following_relationship.ex8
-rw-r--r--lib/pleroma/job_queue_monitor.ex14
-rw-r--r--lib/pleroma/object.ex15
-rw-r--r--lib/pleroma/object/containment.ex2
-rw-r--r--lib/pleroma/object/fetcher.ex43
-rw-r--r--lib/pleroma/upload.ex11
-rw-r--r--lib/pleroma/upload/filter/mogrifun.ex4
-rw-r--r--lib/pleroma/user.ex98
-rw-r--r--lib/pleroma/user/query.ex1
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex48
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex50
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex99
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex56
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/answer_validator.ex62
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex42
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_validator.ex106
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex22
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex60
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex144
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex9
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/delete_validator.ex28
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/event_validator.ex96
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex23
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex37
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex111
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/undo_validator.ex4
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex56
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex139
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex170
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex7
-rw-r--r--lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex43
-rw-r--r--lib/pleroma/web/admin_api/controllers/relay_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex3
-rw-r--r--lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex8
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex47
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/relay_operation.ex50
-rw-r--r--lib/pleroma/web/auth/ldap_authenticator.ex43
-rw-r--r--lib/pleroma/web/common_api/common_api.ex61
-rw-r--r--lib/pleroma/web/common_api/utils.ex11
-rw-r--r--lib/pleroma/web/controller_helper.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex18
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex19
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy.ex13
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex7
-rw-r--r--lib/pleroma/web/preload/timelines.ex2
-rw-r--r--lib/pleroma/workers/cron/clear_oauth_token_worker.ex4
-rw-r--r--lib/pleroma/workers/cron/digest_emails_worker.ex4
-rw-r--r--lib/pleroma/workers/cron/new_users_digest_worker.ex6
-rw-r--r--lib/pleroma/workers/cron/purge_expired_activities_worker.ex6
-rw-r--r--lib/pleroma/workers/cron/stats_worker.ex1
65 files changed, 1518 insertions, 618 deletions
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 074492a46..fe9b0d16c 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -14,7 +14,7 @@ defmodule Mix.Pleroma do
:swoosh,
:timex
]
- @cachex_children ["object", "user"]
+ @cachex_children ["object", "user", "scrubber"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 82e2abdcb..7d8f00b08 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -10,6 +10,7 @@ defmodule Mix.Tasks.Pleroma.Database do
alias Pleroma.User
require Logger
require Pleroma.Constants
+ import Ecto.Query
import Mix.Pleroma
use Mix.Task
@@ -53,8 +54,6 @@ defmodule Mix.Tasks.Pleroma.Database do
end
def run(["prune_objects" | args]) do
- import Ecto.Query
-
{options, [], []} =
OptionParser.parse(
args,
@@ -94,8 +93,6 @@ defmodule Mix.Tasks.Pleroma.Database do
end
def run(["fix_likes_collections"]) do
- import Ecto.Query
-
start_pleroma()
from(object in Object,
@@ -130,4 +127,33 @@ defmodule Mix.Tasks.Pleroma.Database do
Maintenance.vacuum(args)
end
+
+ def run(["ensure_expiration"]) do
+ start_pleroma()
+ days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
+
+ Pleroma.Activity
+ |> join(:left, [a], u in assoc(a, :expiration))
+ |> join(:inner, [a, _u], o in Object,
+ on:
+ fragment(
+ "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
+ o.data,
+ a.data,
+ a.data
+ )
+ )
+ |> where(local: true)
+ |> where([a, u], is_nil(u))
+ |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
+ |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data))
+ |> Pleroma.RepoStreamer.chunk_stream(100)
+ |> Stream.each(fn activities ->
+ Enum.each(activities, fn activity ->
+ expires_at = Timex.shift(activity.inserted_at, days: days)
+ Pleroma.ActivityExpiration.create(activity, expires_at, false)
+ end)
+ end)
+ |> Stream.run()
+ end
end
diff --git a/lib/mix/tasks/pleroma/ecto/migrate.ex b/lib/mix/tasks/pleroma/ecto/migrate.ex
index bc8ed29fb..e903bd171 100644
--- a/lib/mix/tasks/pleroma/ecto/migrate.ex
+++ b/lib/mix/tasks/pleroma/ecto/migrate.ex
@@ -41,6 +41,10 @@ defmodule Mix.Tasks.Pleroma.Ecto.Migrate do
load_pleroma()
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+ if Application.get_env(:pleroma, Pleroma.Repo)[:ssl] do
+ Application.ensure_all_started(:ssl)
+ end
+
opts =
if opts[:to] || opts[:step] || opts[:all],
do: opts,
diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex
index f43bd0b98..3dba952cb 100644
--- a/lib/mix/tasks/pleroma/ecto/rollback.ex
+++ b/lib/mix/tasks/pleroma/ecto/rollback.ex
@@ -40,6 +40,10 @@ defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
load_pleroma()
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+ if Application.get_env(:pleroma, Pleroma.Repo)[:ssl] do
+ Application.ensure_all_started(:ssl)
+ end
+
opts =
if opts[:to] || opts[:step] || opts[:all],
do: opts,
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index f4eaeac98..8f52ee98d 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -15,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
{options, [], []} = parse_global_opts(args)
url_or_path = options[:manifest] || default_manifest()
- manifest = fetch_and_decode(url_or_path)
+ manifest = fetch_and_decode!(url_or_path)
Enum.each(manifest, fn {name, info} ->
to_print = [
@@ -42,7 +42,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
url_or_path = options[:manifest] || default_manifest()
- manifest = fetch_and_decode(url_or_path)
+ manifest = fetch_and_decode!(url_or_path)
for pack_name <- pack_names do
if Map.has_key?(manifest, pack_name) do
@@ -92,7 +92,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
])
)
- files = fetch_and_decode(files_loc)
+ files = fetch_and_decode!(files_loc)
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
@@ -243,9 +243,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
IO.puts("Emoji packs have been reloaded.")
end
- defp fetch_and_decode(from) do
+ defp fetch_and_decode!(from) do
with {:ok, json} <- fetch(from) do
Jason.decode!(json)
+ else
+ {:error, error} -> raise "#{from} cannot be fetched. Error: #{error} occur."
end
end
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index c3312507e..a6d8d6c1c 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -35,10 +35,16 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
- with {:ok, list} <- Relay.list(true) do
- list |> Enum.each(&shell_info(&1))
+ with {:ok, list} <- Relay.list() do
+ Enum.each(list, &print_relay_url/1)
else
{:error, e} -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
end
+
+ defp print_relay_url(%{followed_back: false} = relay) do
+ shell_info("#{relay.actor} - no Accept received (relay didn't follow back)")
+ end
+
+ defp print_relay_url(relay), do: shell_info(relay.actor)
end
diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex
deleted file mode 100644
index 9da74ffcf..000000000
--- a/lib/mix/tasks/pleroma/release_env.ex
+++ /dev/null
@@ -1,76 +0,0 @@
-# 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.ReleaseEnv do
- use Mix.Task
- import Mix.Pleroma
-
- @shortdoc "Generate Pleroma environment file."
- @moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
-
- def run(["gen" | rest]) do
- {options, [], []} =
- OptionParser.parse(
- rest,
- strict: [
- force: :boolean,
- path: :string
- ],
- aliases: [
- p: :path,
- f: :force
- ]
- )
-
- file_path =
- get_option(
- options,
- :path,
- "Environment file path",
- "./config/pleroma.env"
- )
-
- env_path = Path.expand(file_path)
-
- proceed? =
- if File.exists?(env_path) do
- get_option(
- options,
- :force,
- "Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
- "n"
- ) === "y"
- else
- true
- end
-
- if proceed? do
- case do_generate(env_path) do
- {:error, reason} ->
- shell_error(
- File.Error.message(%{action: "write to file", reason: reason, path: env_path})
- )
-
- _ ->
- shell_info("\nThe file generated: #{env_path}.\n")
-
- shell_info("""
- WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
- Example:
- chmod 0444 #{file_path}
- chattr +i #{file_path}
- """)
- end
- else
- shell_info("\nThe file is exist. #{env_path}.\n")
- end
- end
-
- def do_generate(path) do
- content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
-
- File.mkdir_p!(Path.dirname(path))
- File.write(path, content)
- end
-end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index c3cea8d2a..97feebeaa 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -340,4 +340,10 @@ defmodule Pleroma.Activity do
_ -> nil
end
end
+
+ @spec pinned_by_actor?(Activity.t()) :: boolean()
+ def pinned_by_actor?(%Activity{} = activity) do
+ actor = user_actor(activity)
+ activity.id in actor.pinned_activities
+ end
end
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
index db9c88d84..955f0578e 100644
--- a/lib/pleroma/activity_expiration.ex
+++ b/lib/pleroma/activity_expiration.ex
@@ -20,11 +20,11 @@ defmodule Pleroma.ActivityExpiration do
field(:scheduled_at, :naive_datetime)
end
- def changeset(%ActivityExpiration{} = expiration, attrs) do
+ def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do
expiration
|> cast(attrs, [:scheduled_at])
|> validate_required([:scheduled_at])
- |> validate_scheduled_at()
+ |> validate_scheduled_at(validate_scheduled_at)
end
def get_by_activity_id(activity_id) do
@@ -33,9 +33,9 @@ defmodule Pleroma.ActivityExpiration do
|> Repo.one()
end
- def create(%Activity{} = activity, scheduled_at) do
+ def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do
%ActivityExpiration{activity_id: activity.id}
- |> changeset(%{scheduled_at: scheduled_at})
+ |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at)
|> Repo.insert()
end
@@ -46,10 +46,17 @@ defmodule Pleroma.ActivityExpiration do
ActivityExpiration
|> where([exp], exp.scheduled_at < ^naive_datetime)
+ |> limit(50)
+ |> preload(:activity)
|> Repo.all()
+ |> Enum.reject(fn %{activity: activity} ->
+ Activity.pinned_by_actor?(activity)
+ end)
end
- def validate_scheduled_at(changeset) do
+ def validate_scheduled_at(changeset, false), do: changeset
+
+ def validate_scheduled_at(changeset, true) do
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
if not expires_late_enough?(scheduled_at) do
[scheduled_at: "an ephemeral activity must live for at least one hour"]
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index a8329cc1e..97f877595 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -81,6 +81,16 @@ defmodule Pleroma.Config do
Application.delete_env(:pleroma, key)
end
+ def restrict_unauthenticated_access?(resource, kind) do
+ setting = get([:restrict_unauthenticated, resource, kind])
+
+ if setting in [nil, :if_instance_is_private] do
+ !get!([:instance, :public])
+ else
+ setting
+ end
+ end
+
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 313533859..1d8c72ae9 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -107,25 +107,34 @@ defmodule Pleroma.Emails.UserEmail do
|> Enum.filter(&(&1.activity.data["type"] == "Create"))
|> Enum.map(fn notification ->
object = Pleroma.Object.normalize(notification.activity)
- object = update_in(object.data["content"], &format_links/1)
- %{
- data: notification,
- object: object,
- from: User.get_by_ap_id(notification.activity.actor)
- }
+ if not is_nil(object) do
+ object = update_in(object.data["content"], &format_links/1)
+
+ %{
+ data: notification,
+ object: object,
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
end)
+ |> Enum.filter(& &1)
followers =
notifications
|> Enum.filter(&(&1.activity.data["type"] == "Follow"))
|> Enum.map(fn notification ->
- %{
- data: notification,
- object: Pleroma.Object.normalize(notification.activity),
- from: User.get_by_ap_id(notification.activity.actor)
- }
+ from = User.get_by_ap_id(notification.activity.actor)
+
+ if not is_nil(from) do
+ %{
+ data: notification,
+ object: Pleroma.Object.normalize(notification.activity),
+ from: User.get_by_ap_id(notification.activity.actor)
+ }
+ end
end)
+ |> Enum.filter(& &1)
unless Enum.empty?(mentions) do
styling = Config.get([__MODULE__, :styling])
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index 83b366dd4..2039a259d 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -264,4 +264,12 @@ defmodule Pleroma.FollowingRelationship do
end
end)
end
+
+ @spec following_ap_ids(User.t()) :: [String.t()]
+ def following_ap_ids(%User{} = user) do
+ user
+ |> following_query()
+ |> select([r, u], u.ap_id)
+ |> Repo.all()
+ end
end
diff --git a/lib/pleroma/job_queue_monitor.ex b/lib/pleroma/job_queue_monitor.ex
index 2ecf261f3..c255a61ec 100644
--- a/lib/pleroma/job_queue_monitor.ex
+++ b/lib/pleroma/job_queue_monitor.ex
@@ -15,8 +15,8 @@ defmodule Pleroma.JobQueueMonitor do
@impl true
def init(state) do
- :telemetry.attach("oban-monitor-failure", [:oban, :failure], &handle_event/4, nil)
- :telemetry.attach("oban-monitor-success", [:oban, :success], &handle_event/4, nil)
+ :telemetry.attach("oban-monitor-failure", [:oban, :job, :exception], &handle_event/4, nil)
+ :telemetry.attach("oban-monitor-success", [:oban, :job, :stop], &handle_event/4, nil)
{:ok, state}
end
@@ -25,8 +25,11 @@ defmodule Pleroma.JobQueueMonitor do
GenServer.call(__MODULE__, :stats)
end
- def handle_event([:oban, status], %{duration: duration}, meta, _) do
- GenServer.cast(__MODULE__, {:process_event, status, duration, meta})
+ def handle_event([:oban, :job, event], %{duration: duration}, meta, _) do
+ GenServer.cast(
+ __MODULE__,
+ {:process_event, mapping_status(event), duration, meta}
+ )
end
@impl true
@@ -75,4 +78,7 @@ defmodule Pleroma.JobQueueMonitor do
|> Map.update!(:processed_jobs, &(&1 + 1))
|> Map.update!(status, &(&1 + 1))
end
+
+ defp mapping_status(:stop), do: :success
+ defp mapping_status(:exception), do: :failure
end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 546c4ea01..052ad413b 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -255,6 +255,10 @@ defmodule Pleroma.Object do
end
end
+ defp poll_is_multiple?(%Object{data: %{"anyOf" => [_ | _]}}), do: true
+
+ defp poll_is_multiple?(_), do: false
+
def decrease_replies_count(ap_id) do
Object
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
@@ -281,10 +285,10 @@ defmodule Pleroma.Object do
def increase_vote_count(ap_id, name, actor) do
with %Object{} = object <- Object.normalize(ap_id),
"Question" <- object.data["type"] do
- multiple = Map.has_key?(object.data, "anyOf")
+ key = if poll_is_multiple?(object), do: "anyOf", else: "oneOf"
options =
- (object.data["anyOf"] || object.data["oneOf"] || [])
+ object.data[key]
|> Enum.map(fn
%{"name" => ^name} = option ->
Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
@@ -296,11 +300,8 @@ defmodule Pleroma.Object do
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
data =
- if multiple do
- Map.put(object.data, "anyOf", options)
- else
- Map.put(object.data, "oneOf", options)
- end
+ object.data
+ |> Map.put(key, options)
|> Map.put("voters", voters)
object
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 99608b8a5..bc88e8a0c 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -55,7 +55,7 @@ defmodule Pleroma.Object.Containment do
defp compare_uris(_id_uri, _other_uri), do: :error
@doc """
- Checks that an imported AP object's actor matches the domain it came from.
+ Checks that an imported AP object's actor matches the host it came from.
"""
def contain_origin(_id, %{"actor" => nil}), do: :error
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index e74c87269..6fdbc8efd 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.Repo
alias Pleroma.Signature
alias Pleroma.Web.ActivityPub.InternalFetchActor
+ alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
@@ -23,21 +24,39 @@ defmodule Pleroma.Object.Fetcher do
Ecto.Changeset.put_change(changeset, :updated_at, updated_at)
end
- defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do
+ defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do
internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields())
- Map.merge(data, internal_fields)
+ Map.merge(new_data, internal_fields)
end
- defp maybe_reinject_internal_fields(data, _), do: data
+ defp maybe_reinject_internal_fields(_, new_data), do: new_data
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
- defp reinject_object(struct, data) do
- Logger.debug("Reinjecting object #{data["id"]}")
+ defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do
+ Logger.debug("Reinjecting object #{new_data["id"]}")
- with data <- Transmogrifier.fix_object(data),
- data <- maybe_reinject_internal_fields(data, struct),
- changeset <- Object.change(struct, %{data: data}),
+ with new_data <- Transmogrifier.fix_object(new_data),
+ data <- maybe_reinject_internal_fields(object, new_data),
+ {:ok, data, _} <- ObjectValidator.validate(data, %{}),
+ changeset <- Object.change(object, %{data: data}),
+ changeset <- touch_changeset(changeset),
+ {:ok, object} <- Repo.insert_or_update(changeset),
+ {:ok, object} <- Object.set_cache(object) do
+ {:ok, object}
+ else
+ e ->
+ Logger.error("Error while processing object: #{inspect(e)}")
+ {:error, e}
+ end
+ end
+
+ defp reinject_object(%Object{} = object, new_data) do
+ Logger.debug("Reinjecting object #{new_data["id"]}")
+
+ with new_data <- Transmogrifier.fix_object(new_data),
+ data <- maybe_reinject_internal_fields(object, new_data),
+ changeset <- Object.change(object, %{data: data}),
changeset <- touch_changeset(changeset),
{:ok, object} <- Repo.insert_or_update(changeset),
{:ok, object} <- Object.set_cache(object) do
@@ -51,8 +70,8 @@ defmodule Pleroma.Object.Fetcher do
def refetch_object(%Object{data: %{"id" => id}} = object) do
with {:local, false} <- {:local, Object.local?(object)},
- {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
- {:ok, object} <- reinject_object(object, data) do
+ {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
+ {:ok, object} <- reinject_object(object, new_data) do
{:ok, object}
else
{:local, true} -> {:ok, object}
@@ -106,8 +125,8 @@ defmodule Pleroma.Object.Fetcher do
defp prepare_activity_params(data) do
%{
"type" => "Create",
- "to" => data["to"],
- "cc" => data["cc"],
+ "to" => data["to"] || [],
+ "cc" => data["cc"] || [],
# Should we seriously keep this attributedTo thing?
"actor" => data["actor"] || data["attributedTo"],
"object" => data
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 0fa6b89dc..015c87593 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -56,6 +56,15 @@ defmodule Pleroma.Upload do
}
defstruct [:id, :name, :tempfile, :content_type, :path]
+ defp get_description(opts, upload) do
+ case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
+ {description, _} when is_binary(description) -> description
+ {_, :filename} -> upload.name
+ {_, str} when is_binary(str) -> str
+ _ -> ""
+ end
+ end
+
@spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()}
def store(upload, opts \\ []) do
opts = get_opts(opts)
@@ -63,7 +72,7 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = Map.get(opts, :description) || upload.name,
+ description = get_description(opts, upload),
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex
index a8503ac24..c8fa7b190 100644
--- a/lib/pleroma/upload/filter/mogrifun.ex
+++ b/lib/pleroma/upload/filter/mogrifun.ex
@@ -6,6 +6,10 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
@behaviour Pleroma.Upload.Filter
alias Pleroma.Upload.Filter
+ @moduledoc """
+ This module is just an example of an Upload filter. It's not supposed to be used in production.
+ """
+
@filters [
{"implode", "1"},
{"-raise", "20"},
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 09e606b37..d2ad9516f 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -247,6 +247,13 @@ defmodule Pleroma.User do
end
end
+ defdelegate following_count(user), to: FollowingRelationship
+ defdelegate following(user), to: FollowingRelationship
+ defdelegate following?(follower, followed), to: FollowingRelationship
+ defdelegate following_ap_ids(user), to: FollowingRelationship
+ defdelegate get_follow_requests(user), to: FollowingRelationship
+ defdelegate search(query, opts \\ []), to: User.Search
+
@doc """
Dumps Flake Id to SQL-compatible format (16-byte UUID).
E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
@@ -311,10 +318,12 @@ defmodule Pleroma.User do
def visible_for(_, _), do: :invisible
- defp restrict_unauthenticated?(%User{local: local}) do
- config_key = if local, do: :local, else: :remote
+ defp restrict_unauthenticated?(%User{local: true}) do
+ Config.restrict_unauthenticated_access?(:profiles, :local)
+ end
- Config.get([:restrict_unauthenticated, :profiles, config_key], false)
+ defp restrict_unauthenticated?(%User{local: _}) do
+ Config.restrict_unauthenticated_access?(:profiles, :remote)
end
defp visible_account_status(user) do
@@ -370,8 +379,6 @@ defmodule Pleroma.User do
from(u in query, where: u.deactivated != ^true)
end
- defdelegate following_count(user), to: FollowingRelationship
-
defp truncate_fields_param(params) do
if Map.has_key?(params, :fields) do
Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
@@ -638,6 +645,34 @@ defmodule Pleroma.User do
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def force_password_reset(user), do: update_password_reset_pending(user, true)
+ # Used to auto-register LDAP accounts which won't have a password hash stored locally
+ def register_changeset_ldap(struct, params = %{password: password})
+ when is_nil(password) do
+ params = Map.put_new(params, :accepts_chat_messages, true)
+
+ params =
+ if Map.has_key?(params, :email) do
+ Map.put_new(params, :email, params[:email])
+ else
+ params
+ end
+
+ struct
+ |> cast(params, [
+ :name,
+ :nickname,
+ :email,
+ :accepts_chat_messages
+ ])
+ |> validate_required([:name, :nickname])
+ |> unique_constraint(:nickname)
+ |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_format(:nickname, local_nickname_regex())
+ |> put_ap_id()
+ |> unique_constraint(:ap_id)
+ |> put_following_and_follower_address()
+ end
+
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
@@ -838,8 +873,6 @@ defmodule Pleroma.User do
set_cache(follower)
end
- defdelegate following(user), to: FollowingRelationship
-
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
@@ -893,8 +926,6 @@ defmodule Pleroma.User do
end
end
- defdelegate following?(follower, followed), to: FollowingRelationship
-
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following)
@@ -1159,8 +1190,6 @@ defmodule Pleroma.User do
|> Repo.all()
end
- defdelegate get_follow_requests(user), to: FollowingRelationship
-
def increase_note_count(%User{} = user) do
User
|> where(id: ^user.id)
@@ -1553,6 +1582,49 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
+ @spec purge_user_changeset(User.t()) :: Changeset.t()
+ def purge_user_changeset(user) do
+ # "Right to be forgotten"
+ # https://gdpr.eu/right-to-be-forgotten/
+ change(user, %{
+ bio: nil,
+ raw_bio: nil,
+ email: nil,
+ name: nil,
+ password_hash: nil,
+ keys: nil,
+ public_key: nil,
+ avatar: %{},
+ tags: [],
+ last_refreshed_at: nil,
+ last_digest_emailed_at: nil,
+ banner: %{},
+ background: %{},
+ note_count: 0,
+ follower_count: 0,
+ following_count: 0,
+ locked: false,
+ confirmation_pending: false,
+ password_reset_pending: false,
+ approval_pending: false,
+ registration_reason: nil,
+ confirmation_token: nil,
+ domain_blocks: [],
+ deactivated: true,
+ ap_enabled: false,
+ is_moderator: false,
+ is_admin: false,
+ mastofe_settings: nil,
+ mascot: nil,
+ emoji: %{},
+ pleroma_settings_store: %{},
+ fields: [],
+ raw_fields: [],
+ discoverable: false,
+ also_known_as: []
+ })
+ end
+
def delete(users) when is_list(users) do
for user <- users, do: delete(user)
end
@@ -1580,7 +1652,7 @@ defmodule Pleroma.User do
_ ->
user
- |> change(%{deactivated: true, email: nil})
+ |> purge_user_changeset()
|> update_and_set_cache()
end
end
@@ -2090,8 +2162,6 @@ defmodule Pleroma.User do
|> Repo.all()
end
- defdelegate search(query, opts \\ []), to: User.Search
-
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 45553cb6c..d618432ff 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -130,6 +130,7 @@ defmodule Pleroma.User.Query do
defp compose_query({:active, _}, query) do
User.restrict_deactivated(query)
|> where([u], not is_nil(u.nickname))
+ |> where([u], u.approval_pending == false)
end
defp compose_query({:legacy_active, _}, query) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a4db1d87c..624a508ae 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -66,7 +66,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp check_remote_limit(_), do: true
- defp increase_note_count_if_public(actor, object) do
+ def increase_note_count_if_public(actor, object) do
if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
end
@@ -85,17 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
- defp increase_poll_votes_if_vote(%{
- "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
- "type" => "Create",
- "actor" => actor
- }) do
- Object.increase_vote_count(reply_ap_id, name, actor)
- end
-
- defp increase_poll_votes_if_vote(_create_data), do: :noop
-
- @object_types ["ChatMessage"]
+ @object_types ~w[ChatMessage Question Answer Audio Event]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -258,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {:ok, activity} <- insert(create_data, local, fake),
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
- _ <- increase_poll_votes_if_vote(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
_ <- notify_and_stream(activity),
@@ -296,32 +285,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
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()}
- defp 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)
-
- data =
- %{"to" => to, "type" => type, "actor" => actor.ap_id, "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 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
@@ -1381,9 +1344,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def maybe_handle_clashing_nickname(data) do
- nickname = data[:nickname]
-
- with %User{} = old_user <- User.get_by_nickname(nickname),
+ with nickname when is_binary(nickname) <- data[:nickname],
+ %User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info(
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
@@ -1397,7 +1359,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
else
{:ap_id_comparison, true} ->
Logger.info(
- "Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
+ "Found an old user for #{data[:nickname]}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
)
_ ->
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index d5f3610ed..9a7b7d9de 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -14,6 +14,28 @@ defmodule Pleroma.Web.ActivityPub.Builder do
require Pleroma.Constants
+ def accept_or_reject(actor, activity, type) do
+ data = %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => actor.ap_id,
+ "type" => type,
+ "object" => activity.data["id"],
+ "to" => [activity.actor]
+ }
+
+ {:ok, data, []}
+ end
+
+ @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+ def reject(actor, rejected_activity) do
+ accept_or_reject(actor, rejected_activity, "Reject")
+ end
+
+ @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+ def accept(actor, accepted_activity) do
+ accept_or_reject(actor, accepted_activity, "Accept")
+ end
+
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
def follow(follower, followed) do
data = %{
@@ -80,6 +102,13 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
def create(actor, object, recipients) do
+ context =
+ if is_map(object) do
+ object["context"]
+ else
+ nil
+ end
+
{:ok,
%{
"id" => Utils.generate_activity_id(),
@@ -88,7 +117,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"object" => object,
"type" => "Create",
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
- }, []}
+ }
+ |> Pleroma.Maps.put_if_present("context", context), []}
end
def chat_message(actor, recipient, content, opts \\ []) do
@@ -115,6 +145,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ def answer(user, object, name) do
+ {:ok,
+ %{
+ "type" => "Answer",
+ "actor" => user.ap_id,
+ "attributedTo" => user.ap_id,
+ "cc" => [object.data["actor"]],
+ "to" => [],
+ "name" => name,
+ "inReplyTo" => object.data["id"],
+ "context" => object.data["context"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "id" => Utils.generate_object_id()
+ }, []}
+ end
+
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
def tombstone(actor, id) do
{:ok,
@@ -169,7 +215,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
to =
cond do
- actor.ap_id == Relay.relay_ap_id() ->
+ actor.ap_id == Relay.ap_id() ->
[actor.follower_address]
public? ->
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 0dcc7be4d..b77c06395 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -13,20 +13,47 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
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" => type} = object, meta)
+ when type in ~w[Accept Reject] do
+ with {:ok, object} <-
+ object
+ |> AcceptRejectValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Event"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> EventValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => "Follow"} = object, meta) do
with {:ok, object} <-
object
@@ -112,17 +139,50 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(%{"type" => "Question"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> QuestionValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Audio"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> AudioValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Answer"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> AnswerValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => "EmojiReact"} = object, meta) do
with {:ok, object} <-
object
|> EmojiReactValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
- def validate(%{"type" => "Create", "object" => object} = create_activity, meta) do
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
+ meta
+ ) do
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@@ -134,12 +194,28 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
+ meta
+ )
+ when objtype in ~w[Question Answer Audio Event] do
+ with {:ok, object_data} <- cast_and_apply(object),
+ meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
+ {:ok, create_activity} <-
+ create_activity
+ |> CreateGenericValidator.cast_and_validate(meta)
+ |> Ecto.Changeset.apply_action(:insert) do
+ create_activity = stringify_keys(create_activity)
+ {:ok, create_activity, meta}
+ end
+ end
+
def validate(%{"type" => "Announce"} = object, meta) do
with {:ok, object} <-
object
|> AnnounceValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
@@ -148,8 +224,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
ChatMessageValidator.cast_and_apply(object)
end
+ def cast_and_apply(%{"type" => "Question"} = object) do
+ QuestionValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Answer"} = object) do
+ AnswerValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Audio"} = object) do
+ AudioValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Event"} = object) do
+ EventValidator.cast_and_apply(object)
+ end
+
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
+ # is_struct/1 isn't present in Elixir 1.8.x
def stringify_keys(%{__struct__: _} = object) do
object
|> Map.from_struct()
diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
new file mode 100644
index 000000000..179beda58
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
@@ -0,0 +1,56 @@
+# 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.AcceptRejectValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Activity
+ 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(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ 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, ["Accept", "Reject"])
+ |> validate_actor_presence()
+ |> validate_object_presence(allowed_types: ["Follow"])
+ |> validate_accept_reject_rights()
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+
+ def validate_accept_reject_rights(cng) do
+ with object_id when is_binary(object_id) <- get_field(cng, :object),
+ %Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id),
+ true <- followed_actor == get_field(cng, :actor) do
+ cng
+ else
+ _e ->
+ cng
+ |> add_error(:actor, "can't accept or reject the given activity")
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
new file mode 100644
index 000000000..b9fbaf4f6
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -0,0 +1,62 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ field(:type, :string)
+ field(:name, :string)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:attributedTo, ObjectValidators.ObjectID)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data()
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Answer"])
+ |> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index f53bb02be..c8b148280 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -41,34 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
end
def fix_media_type(data) do
- data =
- data
- |> Map.put_new("mediaType", data["mimeType"])
+ data = Map.put_new(data, "mediaType", data["mimeType"])
if MIME.valid?(data["mediaType"]) do
data
else
- data
- |> Map.put("mediaType", "application/octet-stream")
+ Map.put(data, "mediaType", "application/octet-stream")
end
end
- def fix_url(data) do
- case data["url"] do
- url when is_binary(url) ->
- data
- |> Map.put(
- "url",
- [
- %{
- "href" => url,
- "type" => "Link",
- "mediaType" => data["mediaType"]
- }
- ]
- )
-
- _ ->
+ defp handle_href(href, mediaType) do
+ [
+ %{
+ "href" => href,
+ "type" => "Link",
+ "mediaType" => mediaType
+ }
+ ]
+ end
+
+ defp fix_url(data) do
+ cond do
+ is_binary(data["url"]) ->
+ Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
+
+ is_binary(data["href"]) and data["url"] == nil ->
+ Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
+
+ true ->
data
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
new file mode 100644
index 000000000..d1869f188
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
@@ -0,0 +1,106 @@
+# 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.AudioValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+ field(:content, :string)
+ field(:context, :string)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:summary, :string)
+ field(:published, ObjectValidators.DateTime)
+ # TODO: Write type
+ field(:emoji, :map, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, :string)
+ field(:url, ObjectValidators.Uri)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ field(:likes, {:array, :string}, default: [])
+ field(:announcements, {:array, :string}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_url(%{"url" => url} = data) when is_list(url) do
+ attachment =
+ Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
+
+ link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
+
+ data
+ |> Map.put("attachment", [attachment])
+ |> Map.put("url", link_element["href"])
+ end
+
+ defp fix_url(data), do: data
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> fix_url()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Audio"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
new file mode 100644
index 000000000..721749de0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.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.Web.ActivityPub.ObjectValidators.CommonFixes do
+ alias Pleroma.Web.ActivityPub.Utils
+
+ # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
+ def fix_defaults(data) do
+ %{data: %{"id" => context}, id: context_id} =
+ Utils.create_context(data["context"] || data["conversation"])
+
+ data
+ |> Map.put_new("context", context)
+ |> Map.put_new("context_id", context_id)
+ end
+
+ def fix_attribution(data) do
+ data
+ |> Map.put_new("actor", data["attributedTo"])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index bd46f8034..603d87b8e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
alias Pleroma.Object
alias Pleroma.User
- def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
+ def validate_any_presence(cng, fields) do
non_empty =
fields
|> Enum.map(fn field -> get_field(cng, field) end)
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
fields
|> Enum.reduce(cng, fn field, cng ->
cng
- |> add_error(field, "no recipients in any field")
+ |> add_error(field, "none of #{inspect(fields)} present")
end)
end
end
@@ -82,4 +82,60 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
if actor_cng.valid?, do: actor_cng, else: object_cng
end
+
+ def validate_host_match(cng, fields \\ [:id, :actor]) do
+ if same_domain?(cng, fields) do
+ cng
+ else
+ fields
+ |> Enum.reduce(cng, fn field, cng ->
+ cng
+ |> add_error(field, "hosts of #{inspect(fields)} aren't matching")
+ end)
+ end
+ end
+
+ def validate_fields_match(cng, fields) do
+ if map_unique?(cng, fields) do
+ cng
+ else
+ fields
+ |> Enum.reduce(cng, fn field, cng ->
+ cng
+ |> add_error(field, "Fields #{inspect(fields)} aren't matching")
+ end)
+ end
+ end
+
+ defp map_unique?(cng, fields, func \\ & &1) do
+ Enum.reduce_while(fields, nil, fn field, acc ->
+ value =
+ cng
+ |> get_field(field)
+ |> func.()
+
+ case {value, acc} do
+ {value, nil} -> {:cont, value}
+ {value, value} -> {:cont, value}
+ _ -> {:halt, false}
+ end
+ end)
+ end
+
+ def same_domain?(cng, fields \\ [:actor, :object]) do
+ map_unique?(cng, fields, fn value -> URI.parse(value).host end)
+ end
+
+ # This figures out if a user is able to create, delete or modify something
+ # based on the domain and superuser status
+ def validate_modification_rights(cng) do
+ actor = User.get_cached_by_ap_id(get_field(cng, :actor))
+
+ if User.superuser?(actor) || same_domain?(cng) do
+ cng
+ else
+ cng
+ |> add_error(:actor, "is not allowed to modify object")
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
new file mode 100644
index 000000000..b3dbeea57
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -0,0 +1,144 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# Code based on CreateChatMessageValidator
+# NOTES
+# - doesn't embed, will only get the object id
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Object
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:type, :string)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ field(:expires_at, ObjectValidators.DateTime)
+
+ # Should be moved to object, done for CommonAPI.Utils.make_context
+ field(:context, :string)
+ end
+
+ def cast_data(data, meta \\ []) do
+ data = fix(data, meta)
+
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data, meta \\ []) do
+ data
+ |> cast_data(meta)
+ |> validate_data(meta)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ defp fix_context(data, meta) do
+ if object = meta[:object_data] do
+ Map.put_new(data, "context", object["context"])
+ else
+ data
+ end
+ end
+
+ defp fix_addressing(data, meta) do
+ if object = meta[:object_data] do
+ data
+ |> Map.put_new("to", object["to"] || [])
+ |> Map.put_new("cc", object["cc"] || [])
+ else
+ data
+ end
+ end
+
+ defp fix(data, meta) do
+ data
+ |> fix_context(meta)
+ |> fix_addressing(meta)
+ end
+
+ def validate_data(cng, meta \\ []) do
+ cng
+ |> validate_required([:actor, :type, :object])
+ |> validate_inclusion(:type, ["Create"])
+ |> validate_actor_presence()
+ |> validate_any_presence([:to, :cc])
+ |> validate_actors_match(meta)
+ |> validate_context_match(meta)
+ |> validate_object_nonexistence()
+ |> validate_object_containment()
+ end
+
+ def validate_object_containment(cng) do
+ actor = get_field(cng, :actor)
+
+ cng
+ |> validate_change(:object, fn :object, object_id ->
+ %URI{host: object_id_host} = URI.parse(object_id)
+ %URI{host: actor_host} = URI.parse(actor)
+
+ if object_id_host == actor_host do
+ []
+ else
+ [{:object, "The host of the object id doesn't match with the host of the actor"}]
+ end
+ end)
+ end
+
+ def validate_object_nonexistence(cng) do
+ cng
+ |> validate_change(:object, fn :object, object_id ->
+ if Object.get_cached_by_ap_id(object_id) do
+ [{:object, "The object to create already exists"}]
+ else
+ []
+ end
+ end)
+ end
+
+ def validate_actors_match(cng, meta) do
+ attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
+
+ cng
+ |> validate_change(:actor, fn :actor, actor ->
+ if actor == attributed_to do
+ []
+ else
+ [{:actor, "Actor doesn't match with object attributedTo"}]
+ end
+ end)
+ end
+
+ def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
+ cng
+ |> validate_change(:context, fn :context, context ->
+ if context == object_context do
+ []
+ else
+ [{:context, "context field not matching between Create and object (#{object_context})"}]
+ end
+ end)
+ end
+
+ def validate_context_match(cng, _), do: cng
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
index 316bd0c07..9b9743c4a 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
@@ -16,11 +16,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
- field(:to, {:array, :string})
- field(:cc, {:array, :string})
- field(:bto, {:array, :string}, default: [])
- field(:bcc, {:array, :string}, default: [])
-
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
embeds_one(:object, NoteValidator)
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index 93a7b0e0b..2634e8d4d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -59,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Delete"])
|> validate_actor_presence()
- |> validate_deletion_rights()
+ |> validate_modification_rights()
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|> add_deleted_activity_id()
end
@@ -68,31 +67,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
!same_domain?(cng)
end
- defp same_domain?(cng) do
- actor_uri =
- cng
- |> get_field(:actor)
- |> URI.parse()
-
- object_uri =
- cng
- |> get_field(:object)
- |> URI.parse()
-
- object_uri.host == actor_uri.host
- end
-
- def validate_deletion_rights(cng) do
- actor = User.get_cached_by_ap_id(get_field(cng, :actor))
-
- if User.superuser?(actor) || same_domain?(cng) do
- cng
- else
- cng
- |> add_error(:actor, "is not allowed to delete object")
- end
- end
-
def cast_and_validate(data) do
data
|> cast_data
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index a543af1f8..336c92d35 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -20,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
field(:actor, ObjectValidators.ObjectID)
field(:context, :string)
field(:content, :string)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
new file mode 100644
index 000000000..07e4821a4
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -0,0 +1,96 @@
+# 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.EventValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ # Extends from NoteValidator
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
+ field(:content, :string)
+
+ field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ # TODO: Write type
+ field(:emoji, :map, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Event"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
index 56b93dde8..20e735619 100644
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
@@ -13,18 +13,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
- field(:bto, {:array, :string}, default: [])
- field(:bcc, {:array, :string}, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
field(:content, :string)
+
field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
- field(:summary, :string)
field(:published, ObjectValidators.DateTime)
# TODO: Write type
field(:emoji, :map, default: %{})
@@ -34,14 +40,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
- field(:inRepyTo, :string)
- field(:uri, ObjectValidators.Uri)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
field(:likes, {:array, :string}, default: [])
field(:announcements, {:array, :string}, default: [])
-
- # see if needed
- field(:context_id, :string)
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
new file mode 100644
index 000000000..478b3b5cf
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
@@ -0,0 +1,37 @@
+# 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.QuestionOptionsValidator do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:name, :string)
+
+ embeds_one :replies, Replies, primary_key: false do
+ field(:totalItems, :integer)
+ field(:type, :string)
+ end
+
+ field(:type, :string)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, [:name, :type])
+ |> cast_embed(:replies, with: &replies_changeset/2)
+ |> validate_inclusion(:type, ["Note"])
+ |> validate_required([:name, :type])
+ end
+
+ def replies_changeset(struct, data) do
+ struct
+ |> cast(data, [:totalItems, :type])
+ |> validate_inclusion(:type, ["Collection"])
+ |> validate_required([:type])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
new file mode 100644
index 000000000..712047424
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -0,0 +1,111 @@
+# 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.QuestionValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ # Extends from NoteValidator
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+ field(:content, :string)
+ field(:context, :string)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:summary, :string)
+ field(:published, ObjectValidators.DateTime)
+ # TODO: Write type
+ field(:emoji, :map, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ field(:likes, {:array, :string}, default: [])
+ field(:announcements, {:array, :string}, default: [])
+
+ field(:closed, ObjectValidators.DateTime)
+ field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
+ embeds_many(:anyOf, QuestionOptionsValidator)
+ embeds_many(:oneOf, QuestionOptionsValidator)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_closed(data) do
+ cond do
+ is_binary(data["closed"]) -> data
+ is_binary(data["endTime"]) -> Map.put(data, "closed", data["endTime"])
+ true -> Map.drop(data, ["closed"])
+ end
+ end
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> fix_closed()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
+ |> cast_embed(:attachment)
+ |> cast_embed(:anyOf)
+ |> cast_embed(:oneOf)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Question"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_any_presence([:oneOf, :anyOf])
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index e8d2d39c1..8cae94467 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -18,8 +18,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
index f64fac46d..881030f38 100644
--- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
embedded_schema do
field(:type, :string)
field(:href, ObjectValidators.Uri)
- field(:mediaType, :string)
+ field(:mediaType, :string, default: "application/octet-stream")
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index b09764d2b..b65710a94 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,19 +10,13 @@ defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.Web.CommonAPI
require Logger
- @relay_nickname "relay"
+ @nickname "relay"
- def get_actor do
- actor =
- relay_ap_id()
- |> User.get_or_create_service_actor_by_ap_id(@relay_nickname)
+ @spec ap_id() :: String.t()
+ def ap_id, do: "#{Pleroma.Web.Endpoint.url()}/#{@nickname}"
- actor
- end
-
- def relay_ap_id do
- "#{Pleroma.Web.Endpoint.url()}/relay"
- end
+ @spec get_actor() :: User.t() | nil
+ def get_actor, do: User.get_or_create_service_actor_by_ap_id(ap_id(), @nickname)
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def follow(target_instance) do
@@ -61,34 +55,38 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def publish(_), do: {:error, "Not implemented"}
- @spec list(boolean()) :: {:ok, [String.t()]} | {:error, any()}
- def list(with_not_accepted \\ false) do
+ @spec list() :: {:ok, [%{actor: String.t(), followed_back: boolean()}]} | {:error, any()}
+ def list do
with %User{} = user <- get_actor() do
accepted =
user
- |> User.following()
- |> Enum.map(fn entry -> URI.parse(entry).host end)
- |> Enum.uniq()
-
- list =
- if with_not_accepted do
- without_accept =
- user
- |> Pleroma.Activity.following_requests_for_actor()
- |> Enum.map(fn a -> URI.parse(a.data["object"]).host <> " (no Accept received)" end)
- |> Enum.uniq()
+ |> following()
+ |> Enum.map(fn actor -> %{actor: actor, followed_back: true} end)
- accepted ++ without_accept
- else
- accepted
- end
+ without_accept =
+ user
+ |> Pleroma.Activity.following_requests_for_actor()
+ |> Enum.map(fn activity -> %{actor: activity.data["object"], followed_back: false} end)
+ |> Enum.uniq()
- {:ok, list}
+ {:ok, accepted ++ without_accept}
else
error -> format_error(error)
end
end
+ @spec following() :: [String.t()]
+ def following do
+ get_actor()
+ |> following()
+ end
+
+ defp following(user) do
+ user
+ |> User.following_ap_ids()
+ |> Enum.uniq()
+ end
+
defp format_error({:error, error}), do: format_error(error)
defp format_error(error) do
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 1d2c296a5..a5e2323bd 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
+ alias Pleroma.ActivityExpiration
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
@@ -15,13 +16,70 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
+ alias Pleroma.Workers.BackgroundWorker
+
+ require Logger
def handle(object, meta \\ [])
+ # Task this handles
+ # - Follows
+ # - Sends a notification
+ def handle(
+ %{
+ data: %{
+ "actor" => actor,
+ "type" => "Accept",
+ "object" => follow_activity_id
+ }
+ } = object,
+ meta
+ ) do
+ with %Activity{actor: follower_id} = follow_activity <-
+ Activity.get_by_ap_id(follow_activity_id),
+ %User{} = followed <- User.get_cached_by_ap_id(actor),
+ %User{} = follower <- User.get_cached_by_ap_id(follower_id),
+ {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
+ Notification.update_notification_type(followed, follow_activity)
+ User.update_follower_count(followed)
+ User.update_following_count(follower)
+ end
+
+ {:ok, object, meta}
+ end
+
+ # Task this handles
+ # - Rejects all existing follow activities for this person
+ # - Updates the follow state
+ # - Dismisses notification
+ def handle(
+ %{
+ data: %{
+ "actor" => actor,
+ "type" => "Reject",
+ "object" => follow_activity_id
+ }
+ } = object,
+ meta
+ ) do
+ with %Activity{actor: follower_id} = follow_activity <-
+ Activity.get_by_ap_id(follow_activity_id),
+ %User{} = followed <- User.get_cached_by_ap_id(actor),
+ %User{} = follower <- User.get_cached_by_ap_id(follower_id),
+ {:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do
+ FollowingRelationship.update(follower, followed, :follow_reject)
+ Notification.dismiss(follow_activity)
+ end
+
+ {:ok, object, meta}
+ end
+
# Tasks this handle
# - Follows if possible
# - Sends a notification
@@ -42,33 +100,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{_, {:ok, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
if followed.local && !followed.locked do
- Utils.update_follow_state_for_all(object, "accept")
- FollowingRelationship.update(follower, followed, :follow_accept)
- User.update_follower_count(followed)
- User.update_following_count(follower)
-
- %{
- to: [following_user],
- actor: followed,
- object: follow_id,
- local: true
- }
- |> ActivityPub.accept()
+ {:ok, accept_data, _} = Builder.accept(followed, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
end
else
- {:following, {:error, _}, follower, followed} ->
- Utils.update_follow_state_for_all(object, "reject")
- FollowingRelationship.update(follower, followed, :follow_reject)
-
- if followed.local do
- %{
- to: [follower.ap_id],
- actor: followed,
- object: follow_id,
- local: true
- }
- |> ActivityPub.reject()
- end
+ {:following, {:error, _}, _follower, followed} ->
+ {:ok, reject_data, _} = Builder.reject(followed, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(reject_data, local: true)
_ ->
nil
@@ -135,10 +173,26 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles
# - Actually create object
# - Rollback if we couldn't create it
+ # - Increase the user note count
+ # - Increase the reply count
+ # - Increase replies count
+ # - Set up ActivityExpiration
# - Set up notifications
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
- with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
+ with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
+ {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
+
+ if in_reply_to = object.data["inReplyTo"] do
+ Object.increase_replies_count(in_reply_to)
+ end
+
+ if expires_at = activity.data["expires_at"] do
+ ActivityExpiration.create(activity, expires_at)
+ end
+
+ BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
meta =
meta
@@ -199,13 +253,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Stream out the activity
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
- Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object)
+ Object.normalize(deleted_object, false) ||
+ User.get_cached_by_ap_id(deleted_object)
result =
case deleted_object do
%Object{} ->
with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
- %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do
+ {_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
+ %User{} = user <- User.get_cached_by_ap_id(actor) do
User.remove_pinnned_activity(user, activity)
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
@@ -219,6 +275,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
ActivityPub.stream_out(object)
ActivityPub.stream_out_participations(deleted_object, user)
:ok
+ else
+ {:actor, _} ->
+ Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
+ :no_object_actor
end
%User{} ->
@@ -268,9 +328,28 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
+ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
+ Object.increase_vote_count(
+ object.data["inReplyTo"],
+ object.data["name"],
+ object.data["actor"]
+ )
+
+ {:ok, object, meta}
+ end
+ end
+
+ def handle_object_creation(%{"type" => objtype} = object, meta)
+ when objtype in ~w[Audio Question Event] do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
+ {:ok, object, meta}
+ end
+ end
+
# Nothing to do
- def handle_object_creation(object) do
- {:ok, object}
+ def handle_object_creation(object, meta) do
+ {:ok, object, meta}
end
defp undo_like(nil, object), do: delete_object(object)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 35aa05eb5..76298c4a0 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -9,9 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Activity
alias Pleroma.EarmarkRenderer
alias Pleroma.EctoType.ActivityPub.ObjectValidators
- alias Pleroma.FollowingRelationship
alias Pleroma.Maps
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -157,7 +155,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def fix_actor(%{"attributedTo" => actor} = object) do
- Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
+ actor = Containment.get_actor(%{"actor" => actor})
+
+ # TODO: Remove actor field for Objects
+ object
+ |> Map.put("actor", actor)
+ |> Map.put("attributedTo", actor)
end
def fix_in_reply_to(object, options \\ [])
@@ -240,13 +243,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
if href do
attachment_url =
- %{"href" => href}
+ %{
+ "href" => href,
+ "type" => Map.get(url || %{}, "type", "Link")
+ }
|> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", Map.get(url || %{}, "type"))
- %{"url" => [attachment_url]}
+ %{
+ "url" => [attachment_url],
+ "type" => data["type"] || "Document"
+ }
|> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", data["type"])
|> Maps.put_if_present("name", data["name"])
else
nil
@@ -269,13 +276,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- def fix_url(%{"type" => object_type, "url" => url} = object)
- when object_type in ["Video", "Audio"] and is_list(url) do
+ def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
attachment =
Enum.find(url, fn x ->
media_type = x["mediaType"] || x["mimeType"] || ""
- is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
+ is_map(x) and String.starts_with?(media_type, "video/")
end)
link_element =
@@ -382,32 +388,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp fix_content(object), do: object
- defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
- with true <- id =~ "follows",
- %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
- %Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do
- {:ok, activity}
- else
- _ -> {:error, nil}
- end
- end
-
- defp mastodon_follow_hack(_, _), do: {:error, nil}
-
- defp get_follow_activity(follow_object, followed) do
- with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object),
- {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do
- {:ok, activity}
- else
- # Can't find the activity. This might a Mastodon 2.3 "Accept"
- {:activity, nil} ->
- mastodon_follow_hack(follow_object, followed)
-
- _ ->
- {:error, nil}
- end
- end
-
# Reduce the object list to find the reported user.
defp get_reported(objects) do
Enum.reduce_while(objects, nil, fn ap_id, _ ->
@@ -419,6 +399,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end)
end
+ # Compatibility wrapper for Mastodon votes
+ defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
+ handle_incoming(data)
+ end
+
+ defp handle_create(%{"object" => object} = data, user) do
+ %{
+ to: data["to"],
+ object: object,
+ actor: user,
+ context: object["context"],
+ local: false,
+ published: data["published"],
+ additional:
+ Map.take(data, [
+ "cc",
+ "directMessage",
+ "id"
+ ])
+ }
+ |> ActivityPub.create()
+ end
+
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -457,30 +460,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
- when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
+ when objtype in ~w{Article Note Video Page} do
actor = Containment.get_actor(data)
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
- data <- Map.put(data, "actor", actor) |> fix_addressing() do
- object = fix_object(object, options)
-
- params = %{
- to: data["to"],
- object: object,
- actor: user,
- context: object["context"],
- local: false,
- published: data["published"],
- additional:
- Map.take(data, [
- "cc",
- "directMessage",
- "id"
- ])
- }
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
+ data =
+ data
+ |> Map.put("object", fix_object(object, options))
+ |> Map.put("actor", actor)
+ |> fix_addressing()
- with {:ok, created_activity} <- ActivityPub.create(params) do
+ with {:ok, created_activity} <- handle_create(data, user) do
reply_depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(reply_depth) do
@@ -531,60 +522,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
- _options
- ) do
- with actor <- Containment.get_actor(data),
- {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
- User.update_follower_count(followed)
- User.update_following_count(follower)
-
- Notification.update_notification_type(followed, follow_activity)
-
- ActivityPub.accept(%{
- to: follow_activity.data["to"],
- type: "Accept",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- })
- else
- _e ->
- :error
- end
- end
-
- def handle_incoming(
- %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data,
- _options
- ) do
- with actor <- Containment.get_actor(data),
- {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
- {:ok, activity} <-
- ActivityPub.reject(%{
- to: follow_activity.data["to"],
- type: "Reject",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- }) do
- {:ok, activity}
- else
- _e -> :error
- end
- end
-
@misskey_reactions %{
"like" => "👍",
"love" => "❤️",
@@ -614,9 +551,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype}} = data,
_options
- ) do
+ )
+ when objtype in ~w{Question Answer ChatMessage Audio Event} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@@ -638,9 +576,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => type} = data,
_options
)
- when type in ~w{Update Block Follow} do
+ when type in ~w{Update Block Follow Accept Reject} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
- {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
end
end
@@ -649,7 +588,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Delete"} = data,
_options
) do
- with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ with {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
{:error, {:validate_object, _}} = e ->
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 343f41caa..5c349bb7a 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -59,12 +59,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
def visible_for_user?(%{local: local} = activity, nil) do
- cfg_key =
- if local,
- do: :local,
- else: :remote
+ cfg_key = if local, do: :local, else: :remote
- if Pleroma.Config.get([:restrict_unauthenticated, :activities, cfg_key]),
+ if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
do: false,
else: is_public?(activity)
end
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
index e2759d59f..131e22d78 100644
--- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -26,29 +26,40 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
defdelegate open_api_operation(action), to: Spec.MediaProxyCacheOperation
def index(%{assigns: %{user: _}} = conn, params) do
- cursor =
- :banned_urls_cache
- |> :ets.table([{:traverse, {:select, Cachex.Query.create(true, :key)}}])
- |> :qlc.cursor()
+ entries = fetch_entries(params)
+ urls = paginate_entries(entries, params.page, params.page_size)
+
+ render(conn, "index.json",
+ urls: urls,
+ page_size: params.page_size,
+ count: length(entries)
+ )
+ end
+
+ defp fetch_entries(params) do
+ MediaProxy.cache_table()
+ |> Cachex.stream!(Cachex.Query.create(true, :key))
+ |> filter_entries(params[:query])
+ end
- urls =
- case params.page do
- 1 ->
- :qlc.next_answers(cursor, params.page_size)
+ defp filter_entries(stream, query) when is_binary(query) do
+ regex = ~r/#{query}/i
- _ ->
- :qlc.next_answers(cursor, (params.page - 1) * params.page_size)
- :qlc.next_answers(cursor, params.page_size)
- end
+ stream
+ |> Enum.filter(fn url -> String.match?(url, regex) end)
+ |> Enum.to_list()
+ end
- :qlc.delete_cursor(cursor)
+ defp filter_entries(stream, _), do: Enum.to_list(stream)
- render(conn, "index.json", urls: urls)
+ defp paginate_entries(entries, page, page_size) do
+ offset = page_size * (page - 1)
+ Enum.slice(entries, offset, page_size)
end
def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do
MediaProxy.remove_from_banned_urls(urls)
- render(conn, "index.json", urls: urls)
+ json(conn, %{})
end
def purge(%{assigns: %{user: _}, body_params: %{urls: urls, ban: ban}} = conn, _) do
@@ -58,6 +69,6 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
MediaProxy.put_in_banned_urls(urls)
end
- render(conn, "index.json", urls: urls)
+ json(conn, %{})
end
end
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
index cf9f3a14b..95d06dde7 100644
--- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
target: target
})
- json(conn, target)
+ json(conn, %{actor: target, followed_back: target in Relay.following()})
else
_ ->
conn
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 333e72e42..9c477feab 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -79,7 +79,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"confirmation_pending" => user.confirmation_pending,
"approval_pending" => user.approval_pending,
"url" => user.uri || user.ap_id,
- "registration_reason" => user.registration_reason
+ "registration_reason" => user.registration_reason,
+ "actor_type" => user.actor_type
}
end
diff --git a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
index c97400beb..a803bda0b 100644
--- a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
+++ b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
@@ -5,7 +5,11 @@
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheView do
use Pleroma.Web, :view
- def render("index.json", %{urls: urls}) do
- %{urls: urls}
+ def render("index.json", %{urls: urls, page_size: page_size, count: count}) do
+ %{
+ urls: urls,
+ count: count,
+ page_size: page_size
+ }
end
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
index 20d033f66..ab45d6633 100644
--- a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
@@ -22,6 +22,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
security: [%{"oAuth" => ["read:media_proxy_caches"]}],
parameters: [
Operation.parameter(
+ :query,
+ :query,
+ %Schema{type: :string, default: nil},
+ "Page"
+ ),
+ Operation.parameter(
:page,
:query,
%Schema{type: :integer, default: 1},
@@ -36,7 +42,26 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
| admin_api_params()
],
responses: %{
- 200 => success_response()
+ 200 =>
+ Operation.response(
+ "Array of banned MediaProxy URLs in Cachex",
+ "application/json",
+ %Schema{
+ type: :object,
+ properties: %{
+ count: %Schema{type: :integer},
+ page_size: %Schema{type: :integer},
+ urls: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :string,
+ format: :uri,
+ description: "MediaProxy URLs"
+ }
+ }
+ }
+ }
+ )
}
}
end
@@ -61,7 +86,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
required: true
),
responses: %{
- 200 => success_response(),
+ 200 => empty_object_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
@@ -88,25 +113,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
required: true
),
responses: %{
- 200 => success_response(),
+ 200 => empty_object_response(),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
-
- defp success_response do
- Operation.response("Array of banned MediaProxy URLs in Cachex", "application/json", %Schema{
- type: :object,
- properties: %{
- urls: %Schema{
- type: :array,
- items: %Schema{
- type: :string,
- format: :uri,
- description: "MediaProxy URLs"
- }
- }
- }
- })
- end
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
index 67ee5eee0..e06b2d164 100644
--- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
@@ -27,8 +27,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
properties: %{
relays: %Schema{
type: :array,
- items: %Schema{type: :string},
- example: ["lain.com", "mstdn.io"]
+ items: relay()
}
}
})
@@ -43,19 +42,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
- requestBody:
- request_body("Parameters", %Schema{
- type: :object,
- properties: %{
- relay_url: %Schema{type: :string, format: :uri}
- }
- }),
+ requestBody: request_body("Parameters", relay_url()),
responses: %{
- 200 =>
- Operation.response("Status", "application/json", %Schema{
- type: :string,
- example: "http://mastodon.example.org/users/admin"
- })
+ 200 => Operation.response("Status", "application/json", relay())
}
}
end
@@ -67,13 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
- requestBody:
- request_body("Parameters", %Schema{
- type: :object,
- properties: %{
- relay_url: %Schema{type: :string, format: :uri}
- }
- }),
+ requestBody: request_body("Parameters", relay_url()),
responses: %{
200 =>
Operation.response("Status", "application/json", %Schema{
@@ -83,4 +66,29 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
}
}
end
+
+ defp relay do
+ %Schema{
+ type: :object,
+ properties: %{
+ actor: %Schema{
+ type: :string,
+ example: "https://example.com/relay"
+ },
+ followed_back: %Schema{
+ type: :boolean,
+ description: "Is relay followed back by this actor?"
+ }
+ }
+ }
+ end
+
+ defp relay_url do
+ %Schema{
+ type: :object,
+ properties: %{
+ relay_url: %Schema{type: :string, format: :uri}
+ }
+ }
+ end
end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index f63a66c03..402ab428b 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -28,10 +28,6 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
%User{} = user <- ldap_user(name, password) do
{:ok, user}
else
- {:error, {:ldap_connection_error, _}} ->
- # When LDAP is unavailable, try default authenticator
- @base.get_user(conn)
-
{:ldap, _} ->
@base.get_user(conn)
@@ -92,7 +88,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
user
_ ->
- register_user(connection, base, uid, name, password)
+ register_user(connection, base, uid, name)
end
error ->
@@ -100,34 +96,31 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
end
end
- defp register_user(connection, base, uid, name, password) do
+ defp register_user(connection, base, uid, name) do
case :eldap.search(connection, [
{:base, to_charlist(base)},
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
{:scope, :eldap.wholeSubtree()},
- {:attributes, ['mail', 'email']},
{:timeout, @search_timeout}
]) do
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
- with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
- params = %{
- email: :erlang.list_to_binary(mail),
- name: name,
- nickname: name,
- password: password,
- password_confirmation: password
- }
-
- changeset = User.register_changeset(%User{}, params)
-
- case User.register(changeset) do
- {:ok, user} -> user
- error -> error
+ params = %{
+ name: name,
+ nickname: name,
+ password: nil
+ }
+
+ params =
+ case List.keyfind(attributes, 'mail', 0) do
+ {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
+ _ -> params
end
- else
- _ ->
- Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
- {:error, :ldap_registration_missing_attributes}
+
+ changeset = User.register_changeset_ldap(%User{}, params)
+
+ case User.register(changeset) do
+ {:ok, user} -> user
+ error -> error
end
error ->
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 4d5b0decf..a8141b28f 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -6,9 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
- alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.User
@@ -122,33 +120,16 @@ defmodule Pleroma.Web.CommonAPI do
def accept_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
- {:ok, follower} <- User.follow(follower, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
- {:ok, _activity} <-
- ActivityPub.accept(%{
- to: [follower.ap_id],
- actor: followed,
- object: follow_activity.data["id"],
- type: "Accept"
- }) do
- Notification.update_notification_type(followed, follow_activity)
+ {:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
+ {:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do
{:ok, follower}
end
end
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
- {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
- {:ok, _notifications} <- Notification.dismiss(follow_activity),
- {:ok, _activity} <-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: follow_activity.data["id"],
- type: "Reject"
- }) do
+ {:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
+ {:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do
{:ok, follower}
end
end
@@ -308,18 +289,19 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
answer_activities =
Enum.map(choices, fn index ->
- answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
-
- {:ok, activity} =
- ActivityPub.create(%{
- to: answer_data["to"],
- actor: user,
- context: object.data["context"],
- object: answer_data,
- additional: %{"cc" => answer_data["cc"]}
- })
-
- activity
+ {:ok, answer_object, _meta} =
+ Builder.answer(user, object, Enum.at(options, index)["name"])
+
+ {:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
+
+ {:ok, activity, _meta} =
+ activity_data
+ |> Map.put("cc", answer_object["cc"])
+ |> Map.put("context", answer_object["context"])
+ |> Pipeline.common_pipeline(local: true)
+
+ # TODO: Do preload of Pleroma.Object in Pipeline
+ Activity.normalize(activity.data)
end)
object = Object.get_cached_by_ap_id(object.data["id"])
@@ -340,8 +322,13 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)}
- defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1}
+ defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
+ when is_list(any_of) and any_of != [],
+ do: {any_of, Enum.count(any_of)}
+
+ defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
+ when is_list(one_of) and one_of != [],
+ do: {one_of, 1}
defp normalize_and_validate_choices(choices, object) do
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 9c38b73eb..9d7b24eb2 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -548,17 +548,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- def make_answer_data(%User{ap_id: ap_id}, object, name) do
- %{
- "type" => "Answer",
- "actor" => ap_id,
- "cc" => [object.data["actor"]],
- "to" => [],
- "name" => name,
- "inReplyTo" => object.data["id"]
- }
- end
-
def validate_character_limit("" = _full_payload, [] = _attachments) do
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
end
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 69946fb81..6445966e0 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -18,6 +18,12 @@ defmodule Pleroma.Web.ControllerHelper do
def truthy_param?(value), do: not falsy_param?(value)
+ def json_response(conn, status, _) when status in [204, :no_content] do
+ conn
+ |> put_resp_header("content-type", "application/json")
+ |> send_resp(status, "")
+ end
+
def json_response(conn, status, json) do
conn
|> put_status(status)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index f45678184..95d8452df 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -226,7 +226,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
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)
+ Pleroma.Web.ActivityPub.UserView.render("user.json", user: unpersisted_user)
|> Map.delete("@context"),
{:ok, update_data, []} <- Builder.update(user, updated_object),
{:ok, _update, _} <-
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index ab7b1d6aa..9244316ed 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, add_link_headers: 3]
+ alias Pleroma.Config
alias Pleroma.Pagination
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
@@ -89,11 +90,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
end
defp restrict_unauthenticated?(true = _local_only) do
- Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
+ Config.restrict_unauthenticated_access?(:timelines, :local)
end
defp restrict_unauthenticated?(_) do
- Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
+ Config.restrict_unauthenticated_access?(:timelines, :federated)
end
# GET /api/v1/timelines/public
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 59a5deb28..1208dc9a0 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -28,10 +28,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
def render("show.json", %{object: object} = params) do
case object.data do
- %{"anyOf" => options} when is_list(options) ->
+ %{"anyOf" => [_ | _] = options} ->
render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
- %{"oneOf" => options} when is_list(options) ->
+ %{"oneOf" => [_ | _] = options} ->
render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
_ ->
@@ -40,15 +40,13 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
end
defp end_time_and_expired(object) do
- case object.data["closed"] || object.data["endTime"] do
- end_time when is_binary(end_time) ->
- end_time = NaiveDateTime.from_iso8601!(end_time)
- expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
+ if object.data["closed"] do
+ end_time = NaiveDateTime.from_iso8601!(object.data["closed"])
+ expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
- {Utils.to_masto_date(end_time), expired}
-
- _ ->
- {nil, false}
+ {Utils.to_masto_date(end_time), expired}
+ else
+ {nil, false}
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 91b41ef59..01b8bb6bb 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -473,23 +473,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
end
- def render_content(%{data: %{"type" => object_type}} = object)
- when object_type in ["Video", "Event", "Audio"] do
- with name when not is_nil(name) and name != "" <- object.data["name"] do
- "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
- else
- _ -> object.data["content"] || ""
- end
- end
+ def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do
+ url = object.data["url"] || object.data["id"]
- def render_content(%{data: %{"type" => object_type}} = object)
- when object_type in ["Article", "Page"] do
- with summary when not is_nil(summary) and summary != "" <- object.data["name"],
- url when is_bitstring(url) <- object.data["url"] do
- "<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"
- else
- _ -> object.data["content"] || ""
- end
+ "<p><a href=\"#{url}\">#{name}</a></p>#{object.data["content"]}"
end
def render_content(object), do: object.data["content"] || ""
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index dfbfcea6b..e18dd8224 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -9,28 +9,31 @@ defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Web.MediaProxy.Invalidation
@base64_opts [padding: false]
+ @cache_table :banned_urls_cache
+
+ def cache_table, do: @cache_table
@spec in_banned_urls(String.t()) :: boolean()
- def in_banned_urls(url), do: elem(Cachex.exists?(:banned_urls_cache, url(url)), 1)
+ def in_banned_urls(url), do: elem(Cachex.exists?(@cache_table, url(url)), 1)
def remove_from_banned_urls(urls) when is_list(urls) do
- Cachex.execute!(:banned_urls_cache, fn cache ->
+ Cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.del(cache, &1))
end)
end
def remove_from_banned_urls(url) when is_binary(url) do
- Cachex.del(:banned_urls_cache, url(url))
+ Cachex.del(@cache_table, url(url))
end
def put_in_banned_urls(urls) when is_list(urls) do
- Cachex.execute!(:banned_urls_cache, fn cache ->
+ Cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &Cachex.put(cache, &1, true))
end)
end
def put_in_banned_urls(url) when is_binary(url) do
- Cachex.put(:banned_urls_cache, url(url), true)
+ Cachex.put(@cache_table, url(url), true)
end
def url(url) when is_nil(url) or url == "", do: nil
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index f29b3cb57..dd00600ea 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -76,6 +76,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
available_scopes = (app && app.scopes) || []
scopes = Scopes.fetch_scopes(params, available_scopes)
+ scopes =
+ if scopes == [] do
+ available_scopes
+ else
+ scopes
+ end
+
# Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
render(conn, Authenticator.auth_template(), %{
response_type: params["response_type"],
diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/timelines.ex
index 57de04051..b279a865d 100644
--- a/lib/pleroma/web/preload/timelines.ex
+++ b/lib/pleroma/web/preload/timelines.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Preload.Providers.Timelines do
end
def build_public_tag(acc, params) do
- if Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated], true) do
+ if Pleroma.Config.restrict_unauthenticated_access?(:timelines, :federated) do
acc
else
Map.put(acc, @public_url, public_timeline(params))
diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
index d41be4e87..276f47efc 100644
--- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
+++ b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
@@ -16,8 +16,8 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
def perform(_job) do
if Config.get([:oauth2, :clean_expired_tokens], false) do
Token.delete_expired_tokens()
- else
- :ok
end
+
+ :ok
end
end
diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex
index ee646229f..0c56f00fb 100644
--- a/lib/pleroma/workers/cron/digest_emails_worker.ex
+++ b/lib/pleroma/workers/cron/digest_emails_worker.ex
@@ -37,9 +37,9 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
)
|> Repo.all()
|> send_emails
- else
- :ok
end
+
+ :ok
end
def send_emails(users) do
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
index abc8a5e95..8bbaed83d 100644
--- a/lib/pleroma/workers/cron/new_users_digest_worker.ex
+++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -55,11 +55,9 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
|> Repo.all()
|> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
|> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
- else
- :ok
end
- else
- :ok
end
+
+ :ok
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 e926c5dc8..6549207fc 100644
--- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
+++ b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
@@ -23,9 +23,9 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
def perform(_job) do
if Config.get([ActivityExpiration, :enabled]) do
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
- else
- :ok
end
+ after
+ :ok
end
def delete_activity(%ActivityExpiration{activity_id: activity_id}) do
@@ -41,7 +41,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
{:user, _} ->
Logger.error(
- "#{__MODULE__} Couldn't delete expired activity: not found actorof ##{activity_id}"
+ "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}"
)
end
end
diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex
index e54bd9a7f..6a79540bc 100644
--- a/lib/pleroma/workers/cron/stats_worker.ex
+++ b/lib/pleroma/workers/cron/stats_worker.ex
@@ -12,5 +12,6 @@ defmodule Pleroma.Workers.Cron.StatsWorker do
@impl Oban.Worker
def perform(_job) do
Pleroma.Stats.do_collect()
+ :ok
end
end