From b777083f3f396a7d8c357ec968f72679befc691c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 25 Oct 2019 19:14:18 +0700 Subject: Add `also_known_as` field to Pleroma.User --- lib/pleroma/user.ex | 10 +++++++--- lib/pleroma/web/activity_pub/activity_pub.ex | 3 ++- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7bef6e281..f25345140 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -105,6 +105,7 @@ defmodule Pleroma.User do field(:discoverable, :boolean, default: false) field(:invisible, :boolean, default: false) field(:skip_thread_containment, :boolean, default: false) + field(:also_known_as, {:array, :string}, default: []) field(:notification_settings, :map, default: %{ @@ -324,7 +325,8 @@ defmodule Pleroma.User do :fields, :following_count, :discoverable, - :invisible + :invisible, + :also_known_as ] ) |> validate_required([:name, :ap_id]) @@ -373,7 +375,8 @@ defmodule Pleroma.User do :fields, :raw_fields, :pleroma_settings_store, - :discoverable + :discoverable, + :also_known_as ] ) |> unique_constraint(:nickname) @@ -413,7 +416,8 @@ defmodule Pleroma.User do :hide_followers, :discoverable, :hide_followers_count, - :hide_follows_count + :hide_follows_count, + :also_known_as ] ) |> unique_constraint(:nickname) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 07dde3537..dc962673c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1117,7 +1117,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do name: data["name"], follower_address: data["followers"], following_address: data["following"], - bio: data["summary"] + bio: data["summary"], + also_known_as: Map.get(data, "alsoKnownAs", []) } # nickname can be nil because of virtual actors diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 9b3ee842b..6ada38e1a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -616,7 +616,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do update_data = new_user_data - |> Map.take([:avatar, :banner, :bio, :name]) + |> Map.take([:avatar, :banner, :bio, :name, :also_known_as]) |> Map.put(:fields, fields) |> Map.put(:locked, locked) |> Map.put(:invisible, invisible) -- cgit v1.2.3 From 61fc739ab8917ccb5a12d6ab6db6130dc231990b Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 30 Oct 2019 18:21:49 +0700 Subject: Handle "Move" activity --- lib/pleroma/following_relationship.ex | 24 ++++++++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 24 ++++++++++++++++++++++++ lib/pleroma/web/activity_pub/transmogrifier.ex | 18 ++++++++++++++++++ lib/pleroma/web/activity_pub/visibility.ex | 1 + lib/pleroma/workers/background_worker.ex | 7 +++++++ 5 files changed, 74 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 2ffac17ee..2f89eb4cf 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -107,4 +107,28 @@ defmodule Pleroma.FollowingRelationship do [user.follower_address | following] end end + + def move_following(origin, target) do + following_relationships = + __MODULE__ + |> where(following_id: ^origin.id) + |> preload([:follower]) + |> limit(50) + |> Repo.all() + + case following_relationships do + [] -> + :ok + + following_relationships -> + Enum.each(following_relationships, fn following_relationship -> + Repo.transaction(fn -> + Repo.delete(following_relationship) + User.follow(following_relationship.follower, target) + end) + end) + + move_following(origin, target) + end + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 6adadacdc..67c2cb659 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -514,6 +514,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def move(%User{} = origin, %User{} = target, local \\ true) do + params = %{ + "type" => "Move", + "actor" => origin.ap_id, + "object" => origin.ap_id, + "target" => target.ap_id + } + + with true <- origin.ap_id in target.also_known_as, + {:ok, activity} <- insert(params, local) do + maybe_federate(activity) + + BackgroundWorker.enqueue("move_following", %{ + "origin_id" => origin.id, + "target_id" => target.id + }) + + {:ok, activity} + else + false -> {:error, "Target account must have the origin in `alsoKnownAs`"} + err -> err + end + end + defp fetch_activities_for_context_query(context, opts) do public = [Pleroma.Constants.as_public()] diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index c23f2dcd0..78ee6192a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -785,6 +785,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + def handle_incoming( + %{ + "type" => "Move", + "actor" => origin_actor, + "object" => origin_actor, + "target" => target_actor + }, + _options + ) do + with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor), + {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor), + true <- origin_actor in target_user.also_known_as do + ActivityPub.move(origin_user, target_user, false) + else + _e -> :error + end + end + def handle_incoming(_, _), do: :error @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index cd4097493..e172f6d3f 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do @spec is_public?(Object.t() | Activity.t() | map()) :: boolean() def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false def is_public?(%Object{data: data}), do: is_public?(data) + def is_public?(%Activity{data: %{"type" => "Move"}}), do: true def is_public?(%Activity{data: data}), do: is_public?(data) def is_public?(%{"directMessage" => true}), do: false def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 7ffc8eabe..323a4da1e 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -71,4 +71,11 @@ defmodule Pleroma.Workers.BackgroundWorker do activity = Activity.get_by_id(activity_id) Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity) end + + def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do + origin = User.get_cached_by_id(origin_id) + target = User.get_cached_by_id(target_id) + + Pleroma.FollowingRelationship.move_following(origin, target) + end end -- cgit v1.2.3 From 3db988250bcd279f20bd1742ca454aa187d89368 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 10 Nov 2019 16:30:21 +0300 Subject: [#1335] User: refactored :blocks field into :blocked_users relation. Introduced UserBlock. --- lib/pleroma/notification.ex | 4 +- lib/pleroma/user.ex | 57 +++++++++++------ lib/pleroma/user/search.ex | 15 ++++- lib/pleroma/user_block.ex | 72 ++++++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 8 +-- .../mastodon_api/controllers/account_controller.ex | 4 +- lib/pleroma/web/streamer/worker.ex | 2 +- 7 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 lib/pleroma/user_block.ex (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7ecf51e4..82faef85e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -60,8 +60,10 @@ defmodule Pleroma.Notification do end defp exclude_blocked(query, user) do + blocked_ap_ids = User.blocked_ap_ids(user) + query - |> where([n, a], a.actor not in ^user.blocks) + |> where([n, a], a.actor not in ^blocked_ap_ids) |> where( [n, a], fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f8c2db1e1..d4fc1c093 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -7,6 +7,7 @@ defmodule Pleroma.User do import Ecto.Changeset import Ecto.Query + import Ecto, only: [assoc: 2] alias Comeonin.Pbkdf2 alias Ecto.Multi @@ -21,6 +22,7 @@ defmodule Pleroma.User do alias Pleroma.Repo alias Pleroma.RepoStreamer alias Pleroma.User + alias Pleroma.UserBlock alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils @@ -74,7 +76,6 @@ defmodule Pleroma.User do field(:password_reset_pending, :boolean, default: false) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") - field(:blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: []) field(:mutes, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: []) @@ -118,9 +119,16 @@ defmodule Pleroma.User do has_many(:notifications, Notification) has_many(:registrations, Registration) has_many(:deliveries, Delivery) + has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id) + has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id) + has_many(:blocked_users, through: [:blocker_blocks, :blockee]) + has_many(:blocker_users, through: [:blockee_blocks, :blocker]) field(:info, :map, default: %{}) + # `:blocks` is deprecated (replaced with `blocked_users` relation) + field(:blocks, {:array, :string}, default: []) + timestamps() end @@ -986,7 +994,7 @@ defmodule Pleroma.User do end end - def block(blocker, %User{ap_id: ap_id} = blocked) do + def block(blocker, %User{} = blocked) do # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) blocker = if following?(blocker, blocked) do @@ -1015,7 +1023,7 @@ defmodule Pleroma.User do {:ok, blocker} = update_follower_count(blocker) {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked) - add_to_block(blocker, ap_id) + add_to_block(blocker, blocked) end # helper to handle the block given only an actor's AP id @@ -1023,8 +1031,13 @@ defmodule Pleroma.User do block(blocker, get_cached_by_ap_id(ap_id)) end + def unblock(blocker, %User{} = blocked) do + remove_from_block(blocker, blocked) + end + + # helper to handle the block given only an actor's AP id def unblock(blocker, %{ap_id: ap_id}) do - remove_from_block(blocker, ap_id) + unblock(blocker, get_cached_by_ap_id(ap_id)) end def mutes?(nil, _), do: false @@ -1043,7 +1056,7 @@ defmodule Pleroma.User do def blocks?(nil, _), do: false def blocks_ap_id?(%User{} = user, %User{} = target) do - Enum.member?(user.blocks, target.ap_id) + UserBlock.exists?(user, target) end def blocks_ap_id?(_, _), do: false @@ -1070,10 +1083,20 @@ defmodule Pleroma.User do @spec blocked_users(User.t()) :: [User.t()] def blocked_users(user) do - User.Query.build(%{ap_id: user.blocks, deactivated: false}) + user + |> assoc(:blocked_users) + |> restrict_deactivated() |> Repo.all() end + def blocked_ap_ids(user) do + Repo.all( + from(u in assoc(user, :blocked_users), + select: u.ap_id + ) + ) + end + @spec subscribers(User.t()) :: [User.t()] def subscribers(user) do User.Query.build(%{ap_id: user.subscribers, deactivated: false}) @@ -1179,7 +1202,7 @@ defmodule Pleroma.User do blocked_identifiers, fn blocked_identifier -> with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, blocker} <- block(blocker, blocked), + {:ok, _user_block} <- block(blocker, blocked), {:ok, _} <- ActivityPub.block(blocker, blocked) do blocked else @@ -1844,21 +1867,15 @@ defmodule Pleroma.User do set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked)) end - defp set_blocks(user, blocks) do - params = %{blocks: blocks} - - user - |> cast(params, [:blocks]) - |> validate_required([:blocks]) - |> update_and_set_cache() - end - - def add_to_block(user, blocked) do - set_blocks(user, Enum.uniq([blocked | user.blocks])) + @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()} + defp add_to_block(%User{} = user, %User{} = blocked) do + UserBlock.create(user, blocked) end - def remove_from_block(user, blocked) do - set_blocks(user, List.delete(user.blocks, blocked)) + @spec add_to_block(User.t(), User.t()) :: + {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} + defp remove_from_block(%User{} = user, %User{} = blocked) do + UserBlock.delete(user, blocked) end defp set_mutes(user, mutes) do diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 09664db76..8fa1e2e97 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -98,9 +98,18 @@ defmodule Pleroma.User.Search do defp base_query(_user, false), do: User defp base_query(user, true), do: User.get_followers_query(user) - defp filter_blocked_user(query, %User{blocks: blocks}) - when length(blocks) > 0 do - from(q in query, where: not (q.ap_id in ^blocks)) + defp filter_blocked_user(query, %User{} = blocker) do + blocker_id = FlakeId.from_string(blocker.id) + + from( + q in query, + where: + fragment( + "? NOT IN (SELECT blockee_id FROM user_blocks WHERE user_blocks.blocker_id = ?)", + q.id, + ^blocker_id + ) + ) end defp filter_blocked_user(query, _), do: query diff --git a/lib/pleroma/user_block.ex b/lib/pleroma/user_block.ex new file mode 100644 index 000000000..1cdb15886 --- /dev/null +++ b/lib/pleroma/user_block.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.UserBlock do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.UserBlock + + schema "user_blocks" do + belongs_to(:blocker, User, type: FlakeId.Ecto.CompatType) + belongs_to(:blockee, User, type: FlakeId.Ecto.CompatType) + + timestamps(updated_at: false) + end + + def changeset(%UserBlock{} = user_block, params \\ %{}) do + user_block + |> cast(params, [:blocker_id, :blockee_id]) + |> validate_required([:blocker_id, :blockee_id]) + |> unique_constraint(:blockee_id, name: :user_blocks_blocker_id_blockee_id_index) + |> validate_not_self_block() + end + + def exists?(%User{} = blocker, %User{} = blockee) do + UserBlock + |> where(blocker_id: ^blocker.id, blockee_id: ^blockee.id) + |> Repo.exists?() + end + + def create(%User{} = blocker, %User{} = blockee) do + %UserBlock{} + |> changeset(%{blocker_id: blocker.id, blockee_id: blockee.id}) + |> Repo.insert( + on_conflict: :replace_all_except_primary_key, + conflict_target: [:blocker_id, :blockee_id] + ) + end + + def delete(%User{} = blocker, %User{} = blockee) do + attrs = %{blocker_id: blocker.id, blockee_id: blockee.id} + + if is_nil(existing_record = Repo.get_by(UserBlock, attrs)) do + {:ok, nil} + else + Repo.delete(existing_record) + end + end + + defp validate_not_self_block(%Ecto.Changeset{} = changeset) do + changeset + |> validate_change(:blockee_id, fn _, blockee_id -> + if blockee_id == changeset.changes[:blocker_id] || changeset.data.blocker_id do + [blockee_id: "can't be equal to blocker_id"] + else + [] + end + end) + |> validate_change(:blocker_id, fn _, blocker_id -> + if blocker_id == changeset.changes[:blockee_id] || changeset.data.blockee_id do + [blocker_id: "can't be equal to blockee_id"] + else + [] + end + end) + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 65dd251f3..82203ef5c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -876,7 +876,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, _), do: query defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do - blocks = user.blocks || [] + blocked_ap_ids = User.blocked_ap_ids(user) domain_blocks = user.domain_blocks || [] query = @@ -884,14 +884,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do from( [activity, object: o] in query, - where: fragment("not (? = ANY(?))", activity.actor, ^blocks), - where: fragment("not (? && ?)", activity.recipients, ^blocks), + where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), + where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), where: fragment( "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", activity.data, activity.data, - ^blocks + ^blocked_ap_ids ), where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks), where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 73fad519e..7c94a35d9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -341,7 +341,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/block" def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do - with {:ok, blocker} <- User.block(blocker, blocked), + with {:ok, _user_block} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else @@ -351,7 +351,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/unblock" def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do - with {:ok, blocker} <- User.unblock(blocker, blocked), + with {:ok, _user_block} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 33b24840d..672dd52ee 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -129,7 +129,7 @@ defmodule Pleroma.Web.Streamer.Worker do end defp should_send?(%User{} = user, %Activity{} = item) do - blocks = user.blocks || [] + blocks = User.blocked_ap_ids(user) mutes = user.mutes || [] reblog_mutes = user.muted_reblogs || [] recipient_blocks = MapSet.new(blocks ++ mutes) -- cgit v1.2.3 From 5cf2c7422b006d477498af8d7618a0d6f7659290 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 11 Nov 2019 14:16:57 +0300 Subject: [#1335] Applied code review suggestions. --- lib/pleroma/user.ex | 9 ++++----- lib/pleroma/user/search.ex | 15 +++++---------- lib/pleroma/user_block.ex | 11 +++++------ 3 files changed, 14 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d4fc1c093..9dd1ad8fc 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1090,11 +1090,10 @@ defmodule Pleroma.User do end def blocked_ap_ids(user) do - Repo.all( - from(u in assoc(user, :blocked_users), - select: u.ap_id - ) - ) + user + |> assoc(:blocked_users) + |> select([u], u.ap_id) + |> Repo.all() end @spec subscribers(User.t()) :: [User.t()] diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 8fa1e2e97..049e1a634 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -99,17 +99,12 @@ defmodule Pleroma.User.Search do defp base_query(user, true), do: User.get_followers_query(user) defp filter_blocked_user(query, %User{} = blocker) do - blocker_id = FlakeId.from_string(blocker.id) - - from( - q in query, - where: - fragment( - "? NOT IN (SELECT blockee_id FROM user_blocks WHERE user_blocks.blocker_id = ?)", - q.id, - ^blocker_id - ) + query + |> join(:left, [u], b in Pleroma.UserBlock, + as: :blocks, + on: b.blocker_id == ^blocker.id and u.id == b.blockee_id ) + |> where([blocks: b], is_nil(b.blockee_id)) end defp filter_blocked_user(query, _), do: query diff --git a/lib/pleroma/user_block.ex b/lib/pleroma/user_block.ex index 1cdb15886..bcf4b64d9 100644 --- a/lib/pleroma/user_block.ex +++ b/lib/pleroma/user_block.ex @@ -45,24 +45,23 @@ defmodule Pleroma.UserBlock do def delete(%User{} = blocker, %User{} = blockee) do attrs = %{blocker_id: blocker.id, blockee_id: blockee.id} - if is_nil(existing_record = Repo.get_by(UserBlock, attrs)) do - {:ok, nil} - else - Repo.delete(existing_record) + case Repo.get_by(UserBlock, attrs) do + %UserBlock{} = existing_record -> Repo.delete(existing_record) + nil -> {:ok, nil} end end defp validate_not_self_block(%Ecto.Changeset{} = changeset) do changeset |> validate_change(:blockee_id, fn _, blockee_id -> - if blockee_id == changeset.changes[:blocker_id] || changeset.data.blocker_id do + if blockee_id == get_field(changeset, :blocker_id) do [blockee_id: "can't be equal to blocker_id"] else [] end end) |> validate_change(:blocker_id, fn _, blocker_id -> - if blocker_id == changeset.changes[:blockee_id] || changeset.data.blockee_id do + if blocker_id == get_field(changeset, :blockee_id) do [blocker_id: "can't be equal to blockee_id"] else [] -- cgit v1.2.3 From f86a7d5d8bb07ed57dcbb50e0746bc37916eb627 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Sat, 9 Nov 2019 13:56:27 +0300 Subject: Fix exclude_visibilities filter for followers-only Like notifications --- lib/pleroma/notification.ex | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7ecf51e4..98bcadc98 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -87,10 +87,28 @@ defmodule Pleroma.Notification do when is_list(visibility) do if Enum.all?(visibility, &(&1 in @valid_visibilities)) do query + |> join(:left, [n, a], mutated_activity in Pleroma.Activity, + on: + fragment("?->>'context'", a.data) == + fragment("?->>'context'", mutated_activity.data) and + fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and + fragment("?->>'type'", mutated_activity.data) == "Create", + as: :mutated_activity + ) |> where( - [n, a], + [n, a, mutated_activity: mutated_activity], not fragment( - "activity_visibility(?, ?, ?) = ANY (?)", + """ + CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce' + THEN (activity_visibility(?, ?, ?) = ANY (?)) + ELSE (activity_visibility(?, ?, ?) = ANY (?)) END + """, + a.data, + a.data, + mutated_activity.actor, + mutated_activity.recipients, + mutated_activity.data, + ^visibility, a.actor, a.recipients, a.data, @@ -105,17 +123,7 @@ defmodule Pleroma.Notification do defp exclude_visibility(query, %{exclude_visibilities: visibility}) when visibility in @valid_visibilities do - query - |> where( - [n, a], - not fragment( - "activity_visibility(?, ?, ?) = (?)", - a.actor, - a.recipients, - a.data, - ^visibility - ) - ) + exclude_visibility(query, [visibility]) end defp exclude_visibility(query, %{exclude_visibilities: visibility}) -- cgit v1.2.3 From e6d7e27bd603806e96dfc2774f90cadb3cf73a8c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 12 Nov 2019 18:36:50 +0700 Subject: Add `allow_following_move` setting to User --- lib/pleroma/following_relationship.ex | 34 +++++++++------------- lib/pleroma/user.ex | 3 ++ .../mastodon_api/controllers/account_controller.ex | 1 + lib/pleroma/web/mastodon_api/views/account_view.ex | 7 +++++ 4 files changed, 25 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 2f89eb4cf..40538f7bf 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -109,26 +109,20 @@ defmodule Pleroma.FollowingRelationship do end def move_following(origin, target) do - following_relationships = - __MODULE__ - |> where(following_id: ^origin.id) - |> preload([:follower]) - |> limit(50) - |> Repo.all() - - case following_relationships do - [] -> - :ok - - following_relationships -> - Enum.each(following_relationships, fn following_relationship -> - Repo.transaction(fn -> - Repo.delete(following_relationship) - User.follow(following_relationship.follower, target) - end) - end) - - move_following(origin, target) + __MODULE__ + |> join(:inner, [r], f in assoc(r, :follower)) + |> where(following_id: ^origin.id) + |> where([r, f], f.allow_following_move == true) + |> limit(50) + |> preload([:follower]) + |> Repo.all() + |> Enum.map(fn following_relationship -> + Repo.delete(following_relationship) + Pleroma.Web.CommonAPI.follow(following_relationship.follower, target) + end) + |> case do + [] -> :ok + _ -> move_following(origin, target) end end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 8715b37de..d40f6ed08 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -104,6 +104,7 @@ defmodule Pleroma.User do field(:raw_fields, {:array, :map}, default: []) field(:discoverable, :boolean, default: false) field(:invisible, :boolean, default: false) + field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) field(:also_known_as, {:array, :string}, default: []) @@ -314,6 +315,7 @@ defmodule Pleroma.User do :hide_followers_count, :hide_follows_count, :hide_favorites, + :allow_following_move, :background, :show_role, :skip_thread_containment, @@ -359,6 +361,7 @@ defmodule Pleroma.User do :hide_follows, :fields, :hide_followers, + :allow_following_move, :discoverable, :hide_followers_count, :hide_follows_count, diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 73fad519e..7df7dc097 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -152,6 +152,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do :hide_favorites, :show_role, :skip_thread_containment, + :allow_following_move, :discoverable ] |> Enum.reduce(%{}, fn key, acc -> diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index e30fed610..7aae7d188 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -163,6 +163,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do |> maybe_put_chat_token(user, opts[:for], opts) |> maybe_put_activation_status(user, opts[:for]) |> maybe_put_follow_requests_count(user, opts[:for]) + |> maybe_put_allow_following_move(user, opts[:for]) |> maybe_put_unread_conversation_count(user, opts[:for]) end @@ -239,6 +240,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp maybe_put_notification_settings(data, _, _), do: data + defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: user_id}) do + Kernel.put_in(data, [:pleroma, :allow_following_move], user.allow_following_move) + end + + defp maybe_put_allow_following_move(data, _, _), do: data + defp maybe_put_activation_status(data, user, %User{is_admin: true}) do Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated) end -- cgit v1.2.3 From 27cd1374e3c1768a1a0b8abb40d5b56e6e021d2d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 12 Nov 2019 18:48:14 +0700 Subject: Add a notification for Move activities --- lib/pleroma/notification.ex | 33 ++++++++++++++++----------------- lib/pleroma/web/common_api/utils.ex | 21 +++++++++++++++------ 2 files changed, 31 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7ecf51e4..f37e7ec67 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -251,10 +251,13 @@ defmodule Pleroma.Notification do end end - def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) - when type in ["Like", "Announce", "Follow"] do - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + def create_notifications(%Activity{data: %{"type" => type}} = activity) + when type in ["Like", "Announce", "Follow", "Move"] do + notifications = + activity + |> get_notified_from_activity() + |> Enum.map(&create_notification(activity, &1)) + {:ok, notifications} end @@ -276,19 +279,15 @@ defmodule Pleroma.Notification do def get_notified_from_activity(activity, local_only \\ true) - def get_notified_from_activity( - %Activity{data: %{"to" => _, "type" => type} = _data} = activity, - local_only - ) - when type in ["Create", "Like", "Announce", "Follow"] do - recipients = - [] - |> Utils.maybe_notify_to_recipients(activity) - |> Utils.maybe_notify_mentioned_recipients(activity) - |> Utils.maybe_notify_subscribers(activity) - |> Enum.uniq() - - User.get_users_from_set(recipients, local_only) + def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only) + when type in ["Create", "Like", "Announce", "Follow", "Move"] do + [] + |> Utils.maybe_notify_to_recipients(activity) + |> Utils.maybe_notify_mentioned_recipients(activity) + |> Utils.maybe_notify_subscribers(activity) + |> Utils.maybe_notify_followers(activity) + |> Enum.uniq() + |> User.get_users_from_set(local_only) end def get_notified_from_activity(_, _local_only), do: [] diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 88a5f434a..43b67d0f0 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -451,6 +451,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do recipients ++ to end + def maybe_notify_to_recipients(recipients, _), do: recipients + def maybe_notify_mentioned_recipients( recipients, %Activity{data: %{"to" => _to, "type" => type} = data} = activity @@ -484,12 +486,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do recipients end - def maybe_notify_subscribers( - recipients, - %Activity{data: %{"actor" => actor, "type" => type}} = activity - ) - when type == "Create" do - with %User{} = user <- User.get_cached_by_ap_id(actor) do + def maybe_notify_subscribers(recipients, %Activity{data: %{"type" => "Create"}} = activity) do + with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do subscriber_ids = user |> User.subscribers() @@ -502,6 +500,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do def maybe_notify_subscribers(recipients, _), do: recipients + def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do + with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do + user + |> User.get_followers() + |> Enum.map(& &1.ap_id) + |> Enum.concat(recipients) + end + end + + def maybe_notify_followers(recipients, _), do: recipients + def maybe_extract_mentions(%{"tag" => tag}) do tag |> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end) -- cgit v1.2.3 From 768c1a5de172151beb34e6dda13d4fb05e05ed87 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 12 Nov 2019 19:13:19 +0700 Subject: Fix tests --- lib/pleroma/web/common_api/utils.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 43b67d0f0..cbb64f8d2 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -486,8 +486,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do recipients end - def maybe_notify_subscribers(recipients, %Activity{data: %{"type" => "Create"}} = activity) do - with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do + def maybe_notify_subscribers( + recipients, + %Activity{data: %{"actor" => actor, "type" => type}} = activity + ) + when type == "Create" do + with %User{} = user <- User.get_cached_by_ap_id(actor) do subscriber_ids = user |> User.subscribers() -- cgit v1.2.3 From c31ddce51ea18f052c1c3ad30a221b77c7a94e71 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 15 Nov 2019 21:38:54 +0300 Subject: [#1335] Reorganized `users.mutes` as relation to UserMute entity. --- lib/pleroma/user.ex | 112 ++++++++++++++------- lib/pleroma/user_mute.ex | 71 +++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 6 +- .../mastodon_api/controllers/account_controller.ex | 8 +- lib/pleroma/web/mastodon_api/views/account_view.ex | 4 +- lib/pleroma/web/streamer/worker.ex | 3 +- 6 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 lib/pleroma/user_mute.ex (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9dd1ad8fc..383c45992 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -23,6 +23,7 @@ defmodule Pleroma.User do alias Pleroma.RepoStreamer alias Pleroma.User alias Pleroma.UserBlock + alias Pleroma.UserMute alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils @@ -119,11 +120,17 @@ defmodule Pleroma.User do has_many(:notifications, Notification) has_many(:registrations, Registration) has_many(:deliveries, Delivery) + has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id) has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id) has_many(:blocked_users, through: [:blocker_blocks, :blockee]) has_many(:blocker_users, through: [:blockee_blocks, :blocker]) + has_many(:muter_mutes, UserMute, foreign_key: :muter_id) + has_many(:mutee_mutes, UserMute, foreign_key: :mutee_id) + has_many(:muted_users, through: [:muter_mutes, :mutee]) + has_many(:muter_users, through: [:mutee_mutes, :muter]) + field(:info, :map, default: %{}) # `:blocks` is deprecated (replaced with `blocked_users` relation) @@ -968,12 +975,12 @@ defmodule Pleroma.User do end @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} - def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do - add_to_mutes(muter, ap_id, notifications?) + def mute(muter, %User{} = mutee, notifications? \\ true) do + add_to_mutes(muter, mutee, notifications?) end - def unmute(muter, %{ap_id: ap_id}) do - remove_from_mutes(muter, ap_id) + def unmute(muter, %User{} = mutee) do + remove_from_mutes(muter, mutee) end def subscribe(subscriber, %{ap_id: ap_id}) do @@ -1041,7 +1048,11 @@ defmodule Pleroma.User do end def mutes?(nil, _), do: false - def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id) + def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target) + + def mutes_user?(%User{} = user, %User{} = target) do + UserMute.exists?(user, target) + end @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() def muted_notifications?(nil, _), do: false @@ -1049,17 +1060,17 @@ defmodule Pleroma.User do def muted_notifications?(user, %{ap_id: ap_id}), do: Enum.member?(user.muted_notifications, ap_id) + def blocks?(nil, _), do: false + def blocks?(%User{} = user, %User{} = target) do - blocks_ap_id?(user, target) || blocks_domain?(user, target) + blocks_user?(user, target) || blocks_domain?(user, target) end - def blocks?(nil, _), do: false - - def blocks_ap_id?(%User{} = user, %User{} = target) do + def blocks_user?(%User{} = user, %User{} = target) do UserBlock.exists?(user, target) end - def blocks_ap_id?(_, _), do: false + def blocks_user?(_, _), do: false def blocks_domain?(%User{} = user, %User{} = target) do domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) @@ -1077,7 +1088,16 @@ defmodule Pleroma.User do @spec muted_users(User.t()) :: [User.t()] def muted_users(user) do - User.Query.build(%{ap_id: user.mutes, deactivated: false}) + user + |> assoc(:muted_users) + |> restrict_deactivated() + |> Repo.all() + end + + def muted_ap_ids(user) do + user + |> assoc(:muted_users) + |> select([u], u.ap_id) |> Repo.all() end @@ -1096,6 +1116,35 @@ defmodule Pleroma.User do |> Repo.all() end + defp related_ap_ids_sql(join_table, source_column, target_column) do + "(SELECT array_agg(u.ap_id) FROM users as u " <> + "INNER JOIN #{join_table} AS join_table " <> + "ON join_table.#{source_column} = $1 " <> + "WHERE u.id = join_table.#{target_column})" + end + + @related_ap_ids_sql_params %{ + blocked_users: ["user_blocks", "blocker_id", "blockee_id"], + muted_users: ["user_mutes", "muter_id", "mutee_id"] + } + + def related_ap_ids(user, relations) when is_list(relations) do + query = + relations + |> Enum.map(fn r -> @related_ap_ids_sql_params[r] end) + |> Enum.filter(& &1) + |> Enum.map(fn [join_table, source_column, target_column] -> + related_ap_ids_sql(join_table, source_column, target_column) + end) + |> Enum.join(", ") + + with {:ok, %{rows: [ap_ids_arrays]}} <- + Repo.query("SELECT #{query}", [FlakeId.from_string(user.id)]) do + ap_ids_arrays = Enum.map(ap_ids_arrays, &(&1 || [])) + {:ok, ap_ids_arrays} + end + end + @spec subscribers(User.t()) :: [User.t()] def subscribers(user) do User.Query.build(%{ap_id: user.subscribers, deactivated: false}) @@ -1877,32 +1926,27 @@ defmodule Pleroma.User do UserBlock.delete(user, blocked) end - defp set_mutes(user, mutes) do - params = %{mutes: mutes} - - user - |> cast(params, [:mutes]) - |> validate_required([:mutes]) - |> update_and_set_cache() - end - - def add_to_mutes(user, muted, notifications?) do - with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do - set_notification_mutes( - user, - Enum.uniq([muted | user.muted_notifications]), - notifications? - ) + defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do + with {:ok, user_mute} <- UserMute.create(user, muted_user), + {:ok, _user} <- + set_notification_mutes( + user, + Enum.uniq([ap_id | user.muted_notifications]), + notifications? + ) do + {:ok, user_mute} end end - def remove_from_mutes(user, muted) do - with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do - set_notification_mutes( - user, - List.delete(user.muted_notifications, muted), - true - ) + defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do + with {:ok, user_mute} <- UserMute.delete(user, muted_user), + {:ok, _user} <- + set_notification_mutes( + user, + List.delete(user.muted_notifications, ap_id), + true + ) do + {:ok, user_mute} end end diff --git a/lib/pleroma/user_mute.ex b/lib/pleroma/user_mute.ex new file mode 100644 index 000000000..417a5ff84 --- /dev/null +++ b/lib/pleroma/user_mute.ex @@ -0,0 +1,71 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.UserMute do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.UserMute + + schema "user_mutes" do + belongs_to(:muter, User, type: FlakeId.Ecto.CompatType) + belongs_to(:mutee, User, type: FlakeId.Ecto.CompatType) + + timestamps(updated_at: false) + end + + def changeset(%UserMute{} = user_mute, params \\ %{}) do + user_mute + |> cast(params, [:muter_id, :mutee_id]) + |> validate_required([:muter_id, :mutee_id]) + |> unique_constraint(:mutee_id, name: :user_mutes_muter_id_mutee_id_index) + |> validate_not_self_mute() + end + + def exists?(%User{} = muter, %User{} = mutee) do + UserMute + |> where(muter_id: ^muter.id, mutee_id: ^mutee.id) + |> Repo.exists?() + end + + def create(%User{} = muter, %User{} = mutee) do + %UserMute{} + |> changeset(%{muter_id: muter.id, mutee_id: mutee.id}) + |> Repo.insert( + on_conflict: :replace_all_except_primary_key, + conflict_target: [:muter_id, :mutee_id] + ) + end + + def delete(%User{} = muter, %User{} = mutee) do + attrs = %{muter_id: muter.id, mutee_id: mutee.id} + + case Repo.get_by(UserMute, attrs) do + %UserMute{} = existing_record -> Repo.delete(existing_record) + nil -> {:ok, nil} + end + end + + defp validate_not_self_mute(%Ecto.Changeset{} = changeset) do + changeset + |> validate_change(:mutee_id, fn _, mutee_id -> + if mutee_id == get_field(changeset, :muter_id) do + [mutee_id: "can't be equal to muter_id"] + else + [] + end + end) + |> validate_change(:muter_id, fn _, muter_id -> + if muter_id == get_field(changeset, :mutee_id) do + [muter_id: "can't be equal to mutee_id"] + else + [] + end + end) + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 82203ef5c..cba2abfc3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -858,7 +858,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do - mutes = user.mutes + mutes = opts["muted_ap_ids"] || User.muted_ap_ids(user) query = from([activity] in query, @@ -875,8 +875,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, _), do: query - defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do - blocked_ap_ids = User.blocked_ap_ids(user) + defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do + blocked_ap_ids = opts["blocked_ap_ids"] || User.blocked_ap_ids(user) domain_blocks = user.domain_blocks || [] query = diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7c94a35d9..e37d3bb07 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -323,7 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do notifications? = params |> Map.get("notifications", true) |> truthy_param?() - with {:ok, muter} <- User.mute(muter, muted, notifications?) do + with {:ok, _user_mute} <- User.mute(muter, muted, notifications?) do + # TODO: remove `muter` refresh once `muted_notifications` field is deprecated + muter = User.get_cached_by_id(muter.id) render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -332,7 +334,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/unmute" def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do - with {:ok, muter} <- User.unmute(muter, muted) do + with {:ok, _user_mute} <- User.unmute(muter, muted) do + # TODO: remove `muter` refresh once `muted_notifications` field is deprecated + muter = User.get_cached_by_id(muter.id) render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index e30fed610..37dc8e194 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -50,8 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do id: to_string(target.id), following: User.following?(user, target), followed_by: User.following?(target, user), - blocking: User.blocks_ap_id?(user, target), - blocked_by: User.blocks_ap_id?(target, user), + blocking: User.blocks_user?(user, target), + blocked_by: User.blocks_user?(target, user), muting: User.mutes?(user, target), muting_notifications: User.muted_notifications?(user, target), subscribing: User.subscribed_to?(user, target), diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 672dd52ee..45f9c7c53 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -129,8 +129,7 @@ defmodule Pleroma.Web.Streamer.Worker do end defp should_send?(%User{} = user, %Activity{} = item) do - blocks = User.blocked_ap_ids(user) - mutes = user.mutes || [] + {:ok, [blocks, mutes]} = User.related_ap_ids(user, [:blocked_users, :muted_users]) reblog_mutes = user.muted_reblogs || [] recipient_blocks = MapSet.new(blocks ++ mutes) recipients = MapSet.new(item.recipients) -- cgit v1.2.3 From aad6576130c3e47a5fcc102c736ce6414c0efd7a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 18 Nov 2019 20:38:56 +0300 Subject: [#1335] Refactored UserMute and UserBlock into UserRelationship, introduced EctoEnum. --- lib/pleroma/ecto_enums.ex | 7 +++ lib/pleroma/user.ex | 99 ++++++++++++++++++++------------------ lib/pleroma/user/search.ex | 6 +-- lib/pleroma/user_block.ex | 71 --------------------------- lib/pleroma/user_mute.ex | 71 --------------------------- lib/pleroma/user_relationship.ex | 90 ++++++++++++++++++++++++++++++++++ lib/pleroma/web/streamer/worker.ex | 4 +- 7 files changed, 156 insertions(+), 192 deletions(-) create mode 100644 lib/pleroma/ecto_enums.ex delete mode 100644 lib/pleroma/user_block.ex delete mode 100644 lib/pleroma/user_mute.ex create mode 100644 lib/pleroma/user_relationship.ex (limited to 'lib') diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex new file mode 100644 index 000000000..bad5ec523 --- /dev/null +++ b/lib/pleroma/ecto_enums.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +import EctoEnum + +defenum(UserRelationshipTypeEnum, block: 1, mute: 2, reblog_mute: 3, notification_mute: 4) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d6bc02d04..4dd03c6c6 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -22,8 +22,7 @@ defmodule Pleroma.User do alias Pleroma.Repo alias Pleroma.RepoStreamer alias Pleroma.User - alias Pleroma.UserBlock - alias Pleroma.UserMute + alias Pleroma.UserRelationship alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils @@ -78,7 +77,6 @@ defmodule Pleroma.User do field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) - field(:mutes, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: []) field(:muted_notifications, {:array, :string}, default: []) field(:subscribers, {:array, :string}, default: []) @@ -121,20 +119,41 @@ defmodule Pleroma.User do has_many(:registrations, Registration) has_many(:deliveries, Delivery) - has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id) - has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id) - has_many(:blocked_users, through: [:blocker_blocks, :blockee]) - has_many(:blocker_users, through: [:blockee_blocks, :blocker]) + has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id) + has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id) + + has_many(:blocker_blocks, UserRelationship, + foreign_key: :source_id, + where: [relationship_type: :block] + ) + + has_many(:blockee_blocks, UserRelationship, + foreign_key: :target_id, + where: [relationship_type: :block] + ) + + has_many(:blocked_users, through: [:blocker_blocks, :target]) + has_many(:blocker_users, through: [:blockee_blocks, :source]) - has_many(:muter_mutes, UserMute, foreign_key: :muter_id) - has_many(:mutee_mutes, UserMute, foreign_key: :mutee_id) - has_many(:muted_users, through: [:muter_mutes, :mutee]) - has_many(:muter_users, through: [:mutee_mutes, :muter]) + has_many(:muter_mutes, UserRelationship, + foreign_key: :source_id, + where: [relationship_type: :mute] + ) + + has_many(:mutee_mutes, UserRelationship, + foreign_key: :target_id, + where: [relationship_type: :mute] + ) + + has_many(:muted_users, through: [:muter_mutes, :target]) + has_many(:muter_users, through: [:mutee_mutes, :source]) field(:info, :map, default: %{}) # `:blocks` is deprecated (replaced with `blocked_users` relation) field(:blocks, {:array, :string}, default: []) + # `:mutes` is deprecated (replaced with `muted_users` relation) + field(:mutes, {:array, :string}, default: []) timestamps() end @@ -1054,7 +1073,7 @@ defmodule Pleroma.User do def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target) def mutes_user?(%User{} = user, %User{} = target) do - UserMute.exists?(user, target) + UserRelationship.mute_exists?(user, target) end @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() @@ -1070,7 +1089,7 @@ defmodule Pleroma.User do end def blocks_user?(%User{} = user, %User{} = target) do - UserBlock.exists?(user, target) + UserRelationship.block_exists?(user, target) end def blocks_user?(_, _), do: false @@ -1119,33 +1138,20 @@ defmodule Pleroma.User do |> Repo.all() end - defp related_ap_ids_sql(join_table, source_column, target_column) do - "(SELECT array_agg(u.ap_id) FROM users as u " <> - "INNER JOIN #{join_table} AS join_table " <> - "ON join_table.#{source_column} = $1 " <> - "WHERE u.id = join_table.#{target_column})" - end - - @related_ap_ids_sql_params %{ - blocked_users: ["user_blocks", "blocker_id", "blockee_id"], - muted_users: ["user_mutes", "muter_id", "mutee_id"] - } - - def related_ap_ids(user, relations) when is_list(relations) do - query = - relations - |> Enum.map(fn r -> @related_ap_ids_sql_params[r] end) - |> Enum.filter(& &1) - |> Enum.map(fn [join_table, source_column, target_column] -> - related_ap_ids_sql(join_table, source_column, target_column) - end) - |> Enum.join(", ") - - with {:ok, %{rows: [ap_ids_arrays]}} <- - Repo.query("SELECT #{query}", [FlakeId.from_string(user.id)]) do - ap_ids_arrays = Enum.map(ap_ids_arrays, &(&1 || [])) - {:ok, ap_ids_arrays} - end + @doc """ + Returns map of related AP IDs list by relation type. + E.g. `related_ap_ids(user, [:blocks])` -> `%{blocks: ["https://some.site/users/userapid"]}` + """ + @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())} + def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do + user + |> assoc(:outgoing_relationships) + |> join(:inner, [user_rel], u in assoc(user_rel, :target)) + |> where([user_rel, u], user_rel.relationship_type in ^relationship_types) + |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)]) + |> group_by([user_rel, u], user_rel.relationship_type) + |> Repo.all() + |> Enum.into(%{}, fn [k, v] -> {k, v} end) end @spec subscribers(User.t()) :: [User.t()] @@ -1918,19 +1924,20 @@ defmodule Pleroma.User do set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked)) end - @spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()} + @spec add_to_block(User.t(), User.t()) :: + {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()} defp add_to_block(%User{} = user, %User{} = blocked) do - UserBlock.create(user, blocked) + UserRelationship.create_block(user, blocked) end @spec add_to_block(User.t(), User.t()) :: - {:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} + {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} defp remove_from_block(%User{} = user, %User{} = blocked) do - UserBlock.delete(user, blocked) + UserRelationship.delete_block(user, blocked) end defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do - with {:ok, user_mute} <- UserMute.create(user, muted_user), + with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user), {:ok, _user} <- set_notification_mutes( user, @@ -1942,7 +1949,7 @@ defmodule Pleroma.User do end defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do - with {:ok, user_mute} <- UserMute.delete(user, muted_user), + with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user), {:ok, _user} <- set_notification_mutes( user, diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 049e1a634..2b3c7b5b4 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -100,11 +100,11 @@ defmodule Pleroma.User.Search do defp filter_blocked_user(query, %User{} = blocker) do query - |> join(:left, [u], b in Pleroma.UserBlock, + |> join(:left, [u], b in Pleroma.UserRelationship, as: :blocks, - on: b.blocker_id == ^blocker.id and u.id == b.blockee_id + on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id ) - |> where([blocks: b], is_nil(b.blockee_id)) + |> where([blocks: b], is_nil(b.target_id)) end defp filter_blocked_user(query, _), do: query diff --git a/lib/pleroma/user_block.ex b/lib/pleroma/user_block.ex deleted file mode 100644 index bcf4b64d9..000000000 --- a/lib/pleroma/user_block.ex +++ /dev/null @@ -1,71 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.UserBlock do - use Ecto.Schema - - import Ecto.Changeset - import Ecto.Query - - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.UserBlock - - schema "user_blocks" do - belongs_to(:blocker, User, type: FlakeId.Ecto.CompatType) - belongs_to(:blockee, User, type: FlakeId.Ecto.CompatType) - - timestamps(updated_at: false) - end - - def changeset(%UserBlock{} = user_block, params \\ %{}) do - user_block - |> cast(params, [:blocker_id, :blockee_id]) - |> validate_required([:blocker_id, :blockee_id]) - |> unique_constraint(:blockee_id, name: :user_blocks_blocker_id_blockee_id_index) - |> validate_not_self_block() - end - - def exists?(%User{} = blocker, %User{} = blockee) do - UserBlock - |> where(blocker_id: ^blocker.id, blockee_id: ^blockee.id) - |> Repo.exists?() - end - - def create(%User{} = blocker, %User{} = blockee) do - %UserBlock{} - |> changeset(%{blocker_id: blocker.id, blockee_id: blockee.id}) - |> Repo.insert( - on_conflict: :replace_all_except_primary_key, - conflict_target: [:blocker_id, :blockee_id] - ) - end - - def delete(%User{} = blocker, %User{} = blockee) do - attrs = %{blocker_id: blocker.id, blockee_id: blockee.id} - - case Repo.get_by(UserBlock, attrs) do - %UserBlock{} = existing_record -> Repo.delete(existing_record) - nil -> {:ok, nil} - end - end - - defp validate_not_self_block(%Ecto.Changeset{} = changeset) do - changeset - |> validate_change(:blockee_id, fn _, blockee_id -> - if blockee_id == get_field(changeset, :blocker_id) do - [blockee_id: "can't be equal to blocker_id"] - else - [] - end - end) - |> validate_change(:blocker_id, fn _, blocker_id -> - if blocker_id == get_field(changeset, :blockee_id) do - [blocker_id: "can't be equal to blockee_id"] - else - [] - end - end) - end -end diff --git a/lib/pleroma/user_mute.ex b/lib/pleroma/user_mute.ex deleted file mode 100644 index 417a5ff84..000000000 --- a/lib/pleroma/user_mute.ex +++ /dev/null @@ -1,71 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.UserMute do - use Ecto.Schema - - import Ecto.Changeset - import Ecto.Query - - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.UserMute - - schema "user_mutes" do - belongs_to(:muter, User, type: FlakeId.Ecto.CompatType) - belongs_to(:mutee, User, type: FlakeId.Ecto.CompatType) - - timestamps(updated_at: false) - end - - def changeset(%UserMute{} = user_mute, params \\ %{}) do - user_mute - |> cast(params, [:muter_id, :mutee_id]) - |> validate_required([:muter_id, :mutee_id]) - |> unique_constraint(:mutee_id, name: :user_mutes_muter_id_mutee_id_index) - |> validate_not_self_mute() - end - - def exists?(%User{} = muter, %User{} = mutee) do - UserMute - |> where(muter_id: ^muter.id, mutee_id: ^mutee.id) - |> Repo.exists?() - end - - def create(%User{} = muter, %User{} = mutee) do - %UserMute{} - |> changeset(%{muter_id: muter.id, mutee_id: mutee.id}) - |> Repo.insert( - on_conflict: :replace_all_except_primary_key, - conflict_target: [:muter_id, :mutee_id] - ) - end - - def delete(%User{} = muter, %User{} = mutee) do - attrs = %{muter_id: muter.id, mutee_id: mutee.id} - - case Repo.get_by(UserMute, attrs) do - %UserMute{} = existing_record -> Repo.delete(existing_record) - nil -> {:ok, nil} - end - end - - defp validate_not_self_mute(%Ecto.Changeset{} = changeset) do - changeset - |> validate_change(:mutee_id, fn _, mutee_id -> - if mutee_id == get_field(changeset, :muter_id) do - [mutee_id: "can't be equal to muter_id"] - else - [] - end - end) - |> validate_change(:muter_id, fn _, muter_id -> - if muter_id == get_field(changeset, :mutee_id) do - [muter_id: "can't be equal to mutee_id"] - else - [] - end - end) - end -end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex new file mode 100644 index 000000000..5cb99ae50 --- /dev/null +++ b/lib/pleroma/user_relationship.ex @@ -0,0 +1,90 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.UserRelationship do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.UserRelationship + + schema "user_relationships" do + belongs_to(:source, User, type: FlakeId.Ecto.CompatType) + belongs_to(:target, User, type: FlakeId.Ecto.CompatType) + field(:relationship_type, UserRelationshipTypeEnum) + + timestamps(updated_at: false) + end + + def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do + user_relationship + |> cast(params, [:relationship_type, :source_id, :target_id]) + |> validate_required([:relationship_type, :source_id, :target_id]) + |> unique_constraint(:relationship_type, + name: :user_relationships_source_id_relationship_type_target_id_index + ) + |> validate_not_self_relationship() + end + + def exists?(relationship_type, %User{} = source, %User{} = target) do + UserRelationship + |> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id) + |> Repo.exists?() + end + + def block_exists?(%User{} = blocker, %User{} = blockee), do: exists?(:block, blocker, blockee) + + def mute_exists?(%User{} = muter, %User{} = mutee), do: exists?(:mute, muter, mutee) + + def create(relationship_type, %User{} = source, %User{} = target) do + %UserRelationship{} + |> changeset(%{ + relationship_type: relationship_type, + source_id: source.id, + target_id: target.id + }) + |> Repo.insert( + on_conflict: :replace_all_except_primary_key, + conflict_target: [:source_id, :relationship_type, :target_id] + ) + end + + def create_block(%User{} = blocker, %User{} = blockee), do: create(:block, blocker, blockee) + + def create_mute(%User{} = muter, %User{} = mutee), do: create(:mute, muter, mutee) + + def delete(relationship_type, %User{} = source, %User{} = target) do + attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id} + + case Repo.get_by(UserRelationship, attrs) do + %UserRelationship{} = existing_record -> Repo.delete(existing_record) + nil -> {:ok, nil} + end + end + + def delete_block(%User{} = blocker, %User{} = blockee), do: delete(:block, blocker, blockee) + + def delete_mute(%User{} = muter, %User{} = mutee), do: delete(:mute, muter, mutee) + + defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do + changeset + |> validate_change(:target_id, fn _, target_id -> + if target_id == get_field(changeset, :source_id) do + [target_id: "can't be equal to source_id"] + else + [] + end + end) + |> validate_change(:source_id, fn _, source_id -> + if source_id == get_field(changeset, :target_id) do + [source_id: "can't be equal to target_id"] + else + [] + end + end) + end +end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 45f9c7c53..020112949 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -129,7 +129,9 @@ defmodule Pleroma.Web.Streamer.Worker do end defp should_send?(%User{} = user, %Activity{} = item) do - {:ok, [blocks, mutes]} = User.related_ap_ids(user, [:blocked_users, :muted_users]) + related_ap_ids = User.related_ap_ids(user, [:block, :mute]) + blocks = related_ap_ids[:block] || [] + mutes = related_ap_ids[:mute] || [] reblog_mutes = user.muted_reblogs || [] recipient_blocks = MapSet.new(blocks ++ mutes) recipients = MapSet.new(item.recipients) -- cgit v1.2.3 From ba5cc3016514080b4bf7eaefd3e25936c0e222ba Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 19 Nov 2019 23:22:10 +0300 Subject: [#1335] Implemented notification mutes and reblog mutes as UserRelationships. User to UserRelationship relations and functions refactoring. --- lib/pleroma/notification.ex | 6 +- lib/pleroma/user.ex | 216 ++++++++++----------- lib/pleroma/user_relationship.ex | 26 +-- lib/pleroma/web/activity_pub/activity_pub.ex | 6 +- lib/pleroma/web/common_api/common_api.ex | 13 +- .../mastodon_api/controllers/account_controller.ex | 14 +- lib/pleroma/web/mastodon_api/mastodon_api.ex | 11 +- lib/pleroma/web/streamer/worker.ex | 14 +- 8 files changed, 146 insertions(+), 160 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 82faef85e..15b3337f4 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -60,7 +60,7 @@ defmodule Pleroma.Notification do end defp exclude_blocked(query, user) do - blocked_ap_ids = User.blocked_ap_ids(user) + blocked_ap_ids = User.blocked_users_ap_ids(user) query |> where([n, a], a.actor not in ^blocked_ap_ids) @@ -75,8 +75,10 @@ defmodule Pleroma.Notification do end defp exclude_muted(query, user, _opts) do + notification_muted_ap_ids = User.notification_muted_users_ap_ids(user) + query - |> where([n, a], a.actor not in ^user.muted_notifications) + |> where([n, a], a.actor not in ^notification_muted_ap_ids) |> join(:left, [n, a], tm in Pleroma.ThreadMute, on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) ) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4dd03c6c6..615f1b725 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -44,6 +44,20 @@ defmodule Pleroma.User do @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ + # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]] + @user_relationships_config [ + block: [blocker_blocks: :blocked_users, blockee_blocks: :blocker_users], + mute: [muter_mutes: :muted_users, mutee_mutes: :muter_users], + reblog_mute: [ + reblog_muter_mutes: :reblog_muted_users, + reblog_mutee_mutes: :reblog_muter_users + ], + notification_mute: [ + notification_muter_mutes: :notification_muted_users, + notification_mutee_mutes: :notification_muter_users + ] + ] + schema "users" do field(:bio, :string) field(:email, :string) @@ -63,7 +77,6 @@ defmodule Pleroma.User do field(:tags, {:array, :string}, default: []) field(:last_refreshed_at, :naive_datetime_usec) field(:last_digest_emailed_at, :naive_datetime) - field(:banner, :map, default: %{}) field(:background, :map, default: %{}) field(:source_data, :map, default: %{}) @@ -77,8 +90,6 @@ defmodule Pleroma.User do field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) - field(:muted_reblogs, {:array, :string}, default: []) - field(:muted_notifications, {:array, :string}, default: []) field(:subscribers, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) @@ -122,31 +133,29 @@ defmodule Pleroma.User do has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id) has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id) - has_many(:blocker_blocks, UserRelationship, - foreign_key: :source_id, - where: [relationship_type: :block] - ) - - has_many(:blockee_blocks, UserRelationship, - foreign_key: :target_id, - where: [relationship_type: :block] - ) - - has_many(:blocked_users, through: [:blocker_blocks, :target]) - has_many(:blocker_users, through: [:blockee_blocks, :source]) + for {relationship_type, + [ + {outgoing_relation, outgoing_relation_target}, + {incoming_relation, incoming_relation_source} + ]} <- @user_relationships_config do + # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc. + has_many(outgoing_relation, UserRelationship, + foreign_key: :source_id, + where: [relationship_type: relationship_type] + ) - has_many(:muter_mutes, UserRelationship, - foreign_key: :source_id, - where: [relationship_type: :mute] - ) + # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc. + has_many(incoming_relation, UserRelationship, + foreign_key: :target_id, + where: [relationship_type: relationship_type] + ) - has_many(:mutee_mutes, UserRelationship, - foreign_key: :target_id, - where: [relationship_type: :mute] - ) + # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc. + has_many(outgoing_relation_target, through: [outgoing_relation, :target]) - has_many(:muted_users, through: [:muter_mutes, :target]) - has_many(:muter_users, through: [:mutee_mutes, :source]) + # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc. + has_many(incoming_relation_source, through: [incoming_relation, :source]) + end field(:info, :map, default: %{}) @@ -154,10 +163,49 @@ defmodule Pleroma.User do field(:blocks, {:array, :string}, default: []) # `:mutes` is deprecated (replaced with `muted_users` relation) field(:mutes, {:array, :string}, default: []) + # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation) + field(:muted_reblogs, {:array, :string}, default: []) + # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation) + field(:muted_notifications, {:array, :string}, default: []) timestamps() end + for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <- + @user_relationships_config do + # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc. + def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do + target_users_query = assoc(user, unquote(outgoing_relation_target)) + + if restrict_deactivated? do + restrict_deactivated(target_users_query) + else + target_users_query + end + end + + # Definitions of `blocked_users/1`, `muted_users/1`, etc. + def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do + __MODULE__ + |> apply(unquote(:"#{outgoing_relation_target}_relation"), [ + user, + restrict_deactivated? + ]) + |> Repo.all() + end + + # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc. + def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do + __MODULE__ + |> apply(unquote(:"#{outgoing_relation_target}_relation"), [ + user, + restrict_deactivated? + ]) + |> select([u], u.ap_id) + |> Repo.all() + end + end + @doc "Returns if the user should be allowed to authenticate" def auth_active?(%User{deactivated: true}), do: false @@ -996,7 +1044,8 @@ defmodule Pleroma.User do |> Repo.all() end - @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} + @spec mute(User.t(), User.t(), boolean()) :: + {:ok, list(UserRelationship.t())} | {:error, String.t()} def mute(muter, %User{} = mutee, notifications? \\ true) do add_to_mutes(muter, mutee, notifications?) end @@ -1079,8 +1128,8 @@ defmodule Pleroma.User do @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() def muted_notifications?(nil, _), do: false - def muted_notifications?(user, %{ap_id: ap_id}), - do: Enum.member?(user.muted_notifications, ap_id) + def muted_notifications?(user, %User{} = target), + do: UserRelationship.notification_mute_exists?(user, target) def blocks?(nil, _), do: false @@ -1108,50 +1157,27 @@ defmodule Pleroma.User do end end - @spec muted_users(User.t()) :: [User.t()] - def muted_users(user) do - user - |> assoc(:muted_users) - |> restrict_deactivated() - |> Repo.all() - end - - def muted_ap_ids(user) do - user - |> assoc(:muted_users) - |> select([u], u.ap_id) - |> Repo.all() - end - - @spec blocked_users(User.t()) :: [User.t()] - def blocked_users(user) do - user - |> assoc(:blocked_users) - |> restrict_deactivated() - |> Repo.all() - end - - def blocked_ap_ids(user) do - user - |> assoc(:blocked_users) - |> select([u], u.ap_id) - |> Repo.all() - end - @doc """ Returns map of related AP IDs list by relation type. - E.g. `related_ap_ids(user, [:blocks])` -> `%{blocks: ["https://some.site/users/userapid"]}` + E.g. `related_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}` """ @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())} def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do - user - |> assoc(:outgoing_relationships) - |> join(:inner, [user_rel], u in assoc(user_rel, :target)) - |> where([user_rel, u], user_rel.relationship_type in ^relationship_types) - |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)]) - |> group_by([user_rel, u], user_rel.relationship_type) - |> Repo.all() - |> Enum.into(%{}, fn [k, v] -> {k, v} end) + db_result = + user + |> assoc(:outgoing_relationships) + |> join(:inner, [user_rel], u in assoc(user_rel, :target)) + |> where([user_rel, u], user_rel.relationship_type in ^relationship_types) + |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)]) + |> group_by([user_rel, u], user_rel.relationship_type) + |> Repo.all() + |> Enum.into(%{}, fn [k, v] -> {k, v} end) + + Enum.into( + relationship_types, + %{}, + fn rel_type -> {rel_type, db_result[rel_type] || []} end + ) end @spec subscribers(User.t()) :: [User.t()] @@ -1572,7 +1598,7 @@ defmodule Pleroma.User do end def showing_reblogs?(%User{} = user, %User{} = target) do - target.ap_id not in user.muted_reblogs + not UserRelationship.reblog_mute_exists?(user, target) end @doc """ @@ -1936,59 +1962,23 @@ defmodule Pleroma.User do UserRelationship.delete_block(user, blocked) end - defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do + defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user), - {:ok, _user} <- - set_notification_mutes( - user, - Enum.uniq([ap_id | user.muted_notifications]), - notifications? - ) do - {:ok, user_mute} + {:ok, user_notification_mute} <- + (notifications? && UserRelationship.create_notification_mute(user, muted_user)) || + {:ok, nil} do + {:ok, Enum.filter([user_mute, user_notification_mute], & &1)} end end - defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do + defp remove_from_mutes(user, %User{} = muted_user) do with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user), - {:ok, _user} <- - set_notification_mutes( - user, - List.delete(user.muted_notifications, ap_id), - true - ) do - {:ok, user_mute} + {:ok, user_notification_mute} <- + UserRelationship.delete_notification_mute(user, muted_user) do + {:ok, [user_mute, user_notification_mute]} end end - defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do - {:ok, user} - end - - defp set_notification_mutes(user, muted_notifications, true = _notifications?) do - params = %{muted_notifications: muted_notifications} - - user - |> cast(params, [:muted_notifications]) - |> validate_required([:muted_notifications]) - |> update_and_set_cache() - end - - def add_reblog_mute(user, ap_id) do - params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]} - - user - |> cast(params, [:muted_reblogs]) - |> update_and_set_cache() - end - - def remove_reblog_mute(user, ap_id) do - params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)} - - user - |> cast(params, [:muted_reblogs]) - |> update_and_set_cache() - end - def set_invisible(user, invisible) do params = %{invisible: invisible} diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 5cb99ae50..24c724549 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -20,6 +20,20 @@ defmodule Pleroma.UserRelationship do timestamps(updated_at: false) end + for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do + # Definitions of `create_block/2`, `create_mute/2` etc. + def unquote(:"create_#{relationship_type}")(source, target), + do: create(unquote(relationship_type), source, target) + + # Definitions of `delete_block/2`, `delete_mute/2` etc. + def unquote(:"delete_#{relationship_type}")(source, target), + do: delete(unquote(relationship_type), source, target) + + # Definitions of `block_exists?/2`, `mute_exists?/2` etc. + def unquote(:"#{relationship_type}_exists?")(source, target), + do: exists?(unquote(relationship_type), source, target) + end + def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do user_relationship |> cast(params, [:relationship_type, :source_id, :target_id]) @@ -36,10 +50,6 @@ defmodule Pleroma.UserRelationship do |> Repo.exists?() end - def block_exists?(%User{} = blocker, %User{} = blockee), do: exists?(:block, blocker, blockee) - - def mute_exists?(%User{} = muter, %User{} = mutee), do: exists?(:mute, muter, mutee) - def create(relationship_type, %User{} = source, %User{} = target) do %UserRelationship{} |> changeset(%{ @@ -53,10 +63,6 @@ defmodule Pleroma.UserRelationship do ) end - def create_block(%User{} = blocker, %User{} = blockee), do: create(:block, blocker, blockee) - - def create_mute(%User{} = muter, %User{} = mutee), do: create(:mute, muter, mutee) - def delete(relationship_type, %User{} = source, %User{} = target) do attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id} @@ -66,10 +72,6 @@ defmodule Pleroma.UserRelationship do end end - def delete_block(%User{} = blocker, %User{} = blockee), do: delete(:block, blocker, blockee) - - def delete_mute(%User{} = muter, %User{} = mutee), do: delete(:mute, muter, mutee) - defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do changeset |> validate_change(:target_id, fn _, target_id -> diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7a4a4791e..61b7f9280 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -884,7 +884,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do - mutes = opts["muted_ap_ids"] || User.muted_ap_ids(user) + mutes = opts["muted_ap_ids"] || User.muted_users_ap_ids(user) query = from([activity] in query, @@ -902,7 +902,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, _), do: query defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do - blocked_ap_ids = opts["blocked_ap_ids"] || User.blocked_ap_ids(user) + blocked_ap_ids = opts["blocked_ap_ids"] || User.blocked_users_ap_ids(user) domain_blocks = user.domain_blocks || [] query = @@ -945,7 +945,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_pinned(query, _), do: query defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do - muted_reblogs = user.muted_reblogs || [] + muted_reblogs = User.reblog_muted_users_ap_ids(user) from( activity in query, diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index fe6e26a90..afda9ffce 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User + alias Pleroma.UserRelationship alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -420,15 +421,11 @@ defmodule Pleroma.Web.CommonAPI do defp set_visibility(activity, _), do: {:ok, activity} - def hide_reblogs(user, %{ap_id: ap_id} = _muted) do - if ap_id not in user.muted_reblogs do - User.add_reblog_mute(user, ap_id) - end + def hide_reblogs(%User{} = user, %User{} = target) do + UserRelationship.create_reblog_mute(user, target) end - def show_reblogs(user, %{ap_id: ap_id} = _muted) do - if ap_id in user.muted_reblogs do - User.remove_reblog_mute(user, ap_id) - end + def show_reblogs(%User{} = user, %User{} = target) do + UserRelationship.delete_reblog_mute(user, target) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 750f5c690..5b6158b93 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -323,9 +323,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do notifications? = params |> Map.get("notifications", true) |> truthy_param?() - with {:ok, _user_mute} <- User.mute(muter, muted, notifications?) do - # TODO: remove `muter` refresh once `muted_notifications` field is deprecated - muter = User.get_cached_by_id(muter.id) + with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -334,9 +332,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/unmute" def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do - with {:ok, _user_mute} <- User.unmute(muter, muted) do - # TODO: remove `muter` refresh once `muted_notifications` field is deprecated - muter = User.get_cached_by_id(muter.id) + with {:ok, _user_relationships} <- User.unmute(muter, muted) do render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -377,12 +373,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/mutes" def mutes(%{assigns: %{user: user}} = conn, _) do - render(conn, "index.json", users: User.muted_users(user), for: user, as: :user) + users = User.muted_users(user, _restrict_deactivated = true) + render(conn, "index.json", users: users, for: user, as: :user) end @doc "GET /api/v1/blocks" def blocks(%{assigns: %{user: user}} = conn, _) do - render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user) + users = User.blocked_users(user, _restrict_deactivated = true) + render(conn, "index.json", users: users, for: user, as: :user) end @doc "GET /api/v1/endorsements" diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index d875a5788..ee253a342 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -24,19 +24,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do with {:ok, follower, _followed, _} <- result do options = cast_params(params) - - case reblogs_visibility(options[:reblogs], result) do - {:ok, follower} -> {:ok, follower} - _ -> {:ok, follower} - end + set_reblogs_visibility(options[:reblogs], result) + {:ok, follower} end end - defp reblogs_visibility(false, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do CommonAPI.hide_reblogs(follower, followed) end - defp reblogs_visibility(_, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do CommonAPI.show_reblogs(follower, followed) end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 020112949..07e677d29 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -129,17 +129,17 @@ defmodule Pleroma.Web.Streamer.Worker do end defp should_send?(%User{} = user, %Activity{} = item) do - related_ap_ids = User.related_ap_ids(user, [:block, :mute]) - blocks = related_ap_ids[:block] || [] - mutes = related_ap_ids[:mute] || [] - reblog_mutes = user.muted_reblogs || [] - recipient_blocks = MapSet.new(blocks ++ mutes) + %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} = + User.related_ap_ids(user, [:block, :mute, :reblog_mute]) + + recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids) recipients = MapSet.new(item.recipients) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) with parent <- Object.normalize(item) || item, - true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), - true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), + true <- + Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)), + true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)), true <- MapSet.disjoint?(recipients, recipient_blocks), %{host: item_host} <- URI.parse(item.actor), %{host: parent_host} <- URI.parse(parent.data["actor"]), -- cgit v1.2.3 From 555edd01abf62b74c14c30caf82d77a80a93e72c Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 20 Nov 2019 12:19:07 +0300 Subject: [#1335] User AP ID relations fetching performance optimizations. --- lib/pleroma/notification.ex | 37 +++++++++++++++++++------ lib/pleroma/user.ex | 11 +++++--- lib/pleroma/web/activity_pub/activity_pub.ex | 40 +++++++++++++++++++++++----- lib/pleroma/web/streamer/worker.ex | 2 +- 4 files changed, 70 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 15b3337f4..d4b6eb3b8 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -21,6 +21,8 @@ defmodule Pleroma.Notification do @type t :: %__MODULE__{} + @include_muted_option :with_muted + schema "notifications" do field(:seen, :boolean, default: false) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) @@ -34,7 +36,25 @@ defmodule Pleroma.Notification do |> cast(attrs, [:seen]) end - def for_user_query(user, opts \\ []) do + defp for_user_query_ap_id_opts(user, opts) do + ap_id_relations = + [:block] ++ + if opts[@include_muted_option], do: [], else: [:notification_mute] + + preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations) + + exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts) + + exclude_notification_muted_opts = + Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts) + + {exclude_blocked_opts, exclude_notification_muted_opts} + end + + def for_user_query(user, opts \\ %{}) do + {exclude_blocked_opts, exclude_notification_muted_opts} = + for_user_query_ap_id_opts(user, opts) + Notification |> where(user_id: ^user.id) |> where( @@ -54,13 +74,13 @@ defmodule Pleroma.Notification do ) ) |> preload([n, a, o], activity: {a, object: o}) - |> exclude_muted(user, opts) - |> exclude_blocked(user) + |> exclude_notification_muted(user, exclude_notification_muted_opts) + |> exclude_blocked(user, exclude_blocked_opts) |> exclude_visibility(opts) end - defp exclude_blocked(query, user) do - blocked_ap_ids = User.blocked_users_ap_ids(user) + defp exclude_blocked(query, user, opts) do + blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user) query |> where([n, a], a.actor not in ^blocked_ap_ids) @@ -70,12 +90,13 @@ defmodule Pleroma.Notification do ) end - defp exclude_muted(query, _, %{with_muted: true}) do + defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do query end - defp exclude_muted(query, user, _opts) do - notification_muted_ap_ids = User.notification_muted_users_ap_ids(user) + defp exclude_notification_muted(query, user, opts) do + notification_muted_ap_ids = + opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user) query |> where([n, a], a.actor not in ^notification_muted_ap_ids) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 615f1b725..4b53dce13 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1158,11 +1158,14 @@ defmodule Pleroma.User do end @doc """ - Returns map of related AP IDs list by relation type. - E.g. `related_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}` + Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type. + E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}` """ - @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())} - def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do + @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())} + def outgoing_relations_ap_ids(_, []), do: %{} + + def outgoing_relations_ap_ids(%User{} = user, relationship_types) + when is_list(relationship_types) do db_result = user |> assoc(:outgoing_relationships) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 61b7f9280..b24139117 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -884,7 +884,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do - mutes = opts["muted_ap_ids"] || User.muted_users_ap_ids(user) + mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user) query = from([activity] in query, @@ -902,7 +902,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, _), do: query defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do - blocked_ap_ids = opts["blocked_ap_ids"] || User.blocked_users_ap_ids(user) + blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user) domain_blocks = user.domain_blocks || [] query = @@ -944,8 +944,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_pinned(query, _), do: query - defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do - muted_reblogs = User.reblog_muted_users_ap_ids(user) + defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do + muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user) from( activity in query, @@ -1012,7 +1012,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_order(query, _), do: query + defp fetch_activities_query_ap_ids_ops(opts) do + source_user = opts["muting_user"] + ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: [] + + ap_id_relations = + ap_id_relations ++ + if opts["blocking_user"] && opts["blocking_user"] == source_user do + [:block] + else + [] + end + + preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations) + + restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts) + restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts) + + restrict_muted_reblogs_opts = + Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts) + + {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} + end + def fetch_activities_query(recipients, opts \\ %{}) do + {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} = + fetch_activities_query_ap_ids_ops(opts) + config = %{ skip_thread_containment: Config.get([:instance, :skip_thread_containment]) } @@ -1032,15 +1058,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_type(opts) |> restrict_state(opts) |> restrict_favorited_by(opts) - |> restrict_blocked(opts) - |> restrict_muted(opts) + |> restrict_blocked(restrict_blocked_opts) + |> restrict_muted(restrict_muted_opts) |> restrict_media(opts) |> restrict_visibility(opts) |> restrict_thread_visibility(opts, config) |> restrict_replies(opts) |> restrict_reblogs(opts) |> restrict_pinned(opts) - |> restrict_muted_reblogs(opts) + |> restrict_muted_reblogs(restrict_muted_reblogs_opts) |> Activity.restrict_deactivated_users() |> exclude_poll_votes(opts) |> exclude_visibility(opts) diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 07e677d29..a1b445f2f 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -130,7 +130,7 @@ defmodule Pleroma.Web.Streamer.Worker do defp should_send?(%User{} = user, %Activity{} = item) do %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} = - User.related_ap_ids(user, [:block, :mute, :reblog_mute]) + User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute]) recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids) recipients = MapSet.new(item.recipients) -- cgit v1.2.3 From de892d2fe1e70054aaf946b4cd11fb39111fe937 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 20 Nov 2019 15:46:11 +0300 Subject: [#1335] Reorganized users.subscribers as UserRelationship. Added tests for UserRelationship-related functionality. --- lib/pleroma/ecto_enums.ex | 8 +- lib/pleroma/user.ex | 103 ++++++++++----------- lib/pleroma/web/common_api/common_api.ex | 2 +- lib/pleroma/web/common_api/utils.ex | 2 +- .../pleroma_api/controllers/account_controller.ex | 4 +- 5 files changed, 62 insertions(+), 57 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex index bad5ec523..b86229312 100644 --- a/lib/pleroma/ecto_enums.ex +++ b/lib/pleroma/ecto_enums.ex @@ -4,4 +4,10 @@ import EctoEnum -defenum(UserRelationshipTypeEnum, block: 1, mute: 2, reblog_mute: 3, notification_mute: 4) +defenum(UserRelationshipTypeEnum, + block: 1, + mute: 2, + reblog_mute: 3, + notification_mute: 4, + inverse_subscription: 5 +) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4b53dce13..d97c19d38 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -44,10 +44,17 @@ defmodule Pleroma.User do @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ + # AP ID user relationships (blocks, mutes etc.) # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]] @user_relationships_config [ - block: [blocker_blocks: :blocked_users, blockee_blocks: :blocker_users], - mute: [muter_mutes: :muted_users, mutee_mutes: :muter_users], + block: [ + blocker_blocks: :blocked_users, + blockee_blocks: :blocker_users + ], + mute: [ + muter_mutes: :muted_users, + mutee_mutes: :muter_users + ], reblog_mute: [ reblog_muter_mutes: :reblog_muted_users, reblog_mutee_mutes: :reblog_muter_users @@ -55,6 +62,11 @@ defmodule Pleroma.User do notification_mute: [ notification_muter_mutes: :notification_muted_users, notification_mutee_mutes: :notification_muter_users + ], + # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target + inverse_subscription: [ + subscribee_subscriptions: :subscriber_users, + subscriber_subscriptions: :subscribee_users ] ] @@ -90,7 +102,6 @@ defmodule Pleroma.User do field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) - field(:subscribers, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) @@ -167,6 +178,8 @@ defmodule Pleroma.User do field(:muted_reblogs, {:array, :string}, default: []) # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation) field(:muted_notifications, {:array, :string}, default: []) + # `:subscribers` is deprecated (replaced with `subscriber_users` relation) + field(:subscribers, {:array, :string}, default: []) timestamps() end @@ -1046,33 +1059,43 @@ defmodule Pleroma.User do @spec mute(User.t(), User.t(), boolean()) :: {:ok, list(UserRelationship.t())} | {:error, String.t()} - def mute(muter, %User{} = mutee, notifications? \\ true) do + def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do add_to_mutes(muter, mutee, notifications?) end - def unmute(muter, %User{} = mutee) do + def unmute(%User{} = muter, %User{} = mutee) do remove_from_mutes(muter, mutee) end - def subscribe(subscriber, %{ap_id: ap_id}) do - with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do - deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) + def subscribe(%User{} = subscriber, %User{} = target) do + deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) - if blocks?(subscribed, subscriber) and deny_follow_blocked do - {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} - else - User.add_to_subscribers(subscribed, subscriber.ap_id) - end + if blocks?(target, subscriber) and deny_follow_blocked do + {:error, "Could not subscribe: #{target.nickname} is blocking you"} + else + # Note: the relationship is inverse: subscriber acts as relationship target + UserRelationship.create_inverse_subscription(target, subscriber) end end - def unsubscribe(unsubscriber, %{ap_id: ap_id}) do + def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do + with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do + subscribe(subscriber, subscribee) + end + end + + def unsubscribe(%User{} = unsubscriber, %User{} = target) do + # Note: the relationship is inverse: subscriber acts as relationship target + UserRelationship.delete_inverse_subscription(target, unsubscriber) + end + + def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do with %User{} = user <- get_cached_by_ap_id(ap_id) do - User.remove_from_subscribers(user, unsubscriber.ap_id) + unsubscribe(unsubscriber, user) end end - def block(blocker, %User{} = blocked) do + def block(%User{} = blocker, %User{} = blocked) do # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) blocker = if following?(blocker, blocked) do @@ -1089,13 +1112,7 @@ defmodule Pleroma.User do nil -> blocked end - blocker = - if subscribed_to?(blocked, blocker) do - {:ok, blocker} = unsubscribe(blocked, blocker) - blocker - else - blocker - end + unsubscribe(blocked, blocker) if following?(blocked, blocker), do: unfollow(blocked, blocker) @@ -1105,16 +1122,16 @@ defmodule Pleroma.User do end # helper to handle the block given only an actor's AP id - def block(blocker, %{ap_id: ap_id}) do + def block(%User{} = blocker, %{ap_id: ap_id}) do block(blocker, get_cached_by_ap_id(ap_id)) end - def unblock(blocker, %User{} = blocked) do + def unblock(%User{} = blocker, %User{} = blocked) do remove_from_block(blocker, blocked) end # helper to handle the block given only an actor's AP id - def unblock(blocker, %{ap_id: ap_id}) do + def unblock(%User{} = blocker, %{ap_id: ap_id}) do unblock(blocker, get_cached_by_ap_id(ap_id)) end @@ -1128,7 +1145,7 @@ defmodule Pleroma.User do @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() def muted_notifications?(nil, _), do: false - def muted_notifications?(user, %User{} = target), + def muted_notifications?(%User{} = user, %User{} = target), do: UserRelationship.notification_mute_exists?(user, target) def blocks?(nil, _), do: false @@ -1151,9 +1168,14 @@ defmodule Pleroma.User do def blocks_domain?(_, _), do: false - def subscribed_to?(user, %{ap_id: ap_id}) do + def subscribed_to?(%User{} = user, %User{} = target) do + # Note: the relationship is inverse: subscriber acts as relationship target + UserRelationship.inverse_subscription_exists?(target, user) + end + + def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do with %User{} = target <- get_cached_by_ap_id(ap_id) do - Enum.member?(target.subscribers, user.ap_id) + subscribed_to?(user, target) end end @@ -1183,12 +1205,6 @@ defmodule Pleroma.User do ) end - @spec subscribers(User.t()) :: [User.t()] - def subscribers(user) do - User.Query.build(%{ap_id: user.subscribers, deactivated: false}) - |> Repo.all() - end - def deactivate_async(user, status \\ true) do BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) end @@ -1919,23 +1935,6 @@ defmodule Pleroma.User do |> update_and_set_cache() end - defp set_subscribers(user, subscribers) do - params = %{subscribers: subscribers} - - user - |> cast(params, [:subscribers]) - |> validate_required([:subscribers]) - |> update_and_set_cache() - end - - def add_to_subscribers(user, subscribed) do - set_subscribers(user, Enum.uniq([subscribed | user.subscribers])) - end - - def remove_from_subscribers(user, subscribed) do - set_subscribers(user, List.delete(user.subscribers, subscribed)) - end - defp set_domain_blocks(user, domain_blocks) do params = %{domain_blocks: domain_blocks} diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index afda9ffce..2f3bcfc3c 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -33,7 +33,7 @@ defmodule Pleroma.Web.CommonAPI do def unfollow(follower, unfollowed) do with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed), {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed), - {:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do + {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do {:ok, follower} end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 88a5f434a..f77076906 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -492,7 +492,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do with %User{} = user <- User.get_cached_by_ap_id(actor) do subscriber_ids = user - |> User.subscribers() + |> User.subscriber_users() |> Enum.filter(&Visibility.visible_for_user?(activity, &1)) |> Enum.map(& &1.ap_id) diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index bc2f1017c..773cd9a97 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -144,7 +144,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do @doc "POST /api/v1/pleroma/accounts/:id/subscribe" def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do + with {:ok, _subscription} <- User.subscribe(user, subscription_target) do render(conn, "relationship.json", user: user, target: subscription_target) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -153,7 +153,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do + with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do render(conn, "relationship.json", user: user, target: subscription_target) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) -- cgit v1.2.3 From 1636cc5b7e2ad324c828c993d5fb39ac9cdb40cc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 23 Nov 2019 14:06:19 +0300 Subject: Removed users.info and remaining usages. --- lib/pleroma/user.ex | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index fcb1d5143..e4656af3d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -119,8 +119,6 @@ defmodule Pleroma.User do has_many(:registrations, Registration) has_many(:deliveries, Delivery) - field(:info, :map, default: %{}) - timestamps() end @@ -244,7 +242,6 @@ defmodule Pleroma.User do params = params - |> Map.put(:info, params[:info] || %{}) |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) |> truncate_fields_param() @@ -1226,7 +1223,7 @@ defmodule Pleroma.User do def external_users(opts \\ []) do query = external_users_query() - |> select([u], struct(u, [:id, :ap_id, :info])) + |> select([u], struct(u, [:id, :ap_id])) query = if opts[:max_id], -- cgit v1.2.3 From 2b341627da5d592bdedc66a331409f5228ab28cf Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 25 Nov 2019 00:04:29 +0900 Subject: Admin API: Render whole status in grouped reports --- lib/pleroma/web/activity_pub/utils.ex | 23 ++++++++++++++++++----- lib/pleroma/web/admin_api/views/report_view.ex | 8 +++++++- 2 files changed, 25 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c45662359..277ca3c7c 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -822,20 +822,33 @@ defmodule Pleroma.Web.ActivityPub.Utils do reports = get_reports_by_status_id(activity["id"]) max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"])) actors = Enum.map(reports, & &1.user_actor) + {deleted, status} = get_status_data(activity) %{ date: max_date.data["published"], account: activity["actor"], - status: %{ - id: activity["id"], - content: activity["content"], - published: activity["published"] - }, + status: status, + status_deleted: deleted, actors: Enum.uniq(actors), reports: reports } end + defp get_status_data(activity) do + case Activity.get_by_ap_id(activity["id"]) do + %Activity{} = act -> + {false, act} + + _ -> + {true, + %{ + id: activity["id"], + content: activity["content"], + published: activity["published"] + }} + end + end + def get_reports_by_status_id(ap_id) do from(a in Activity, where: fragment("(?)->>'type' = 'Flag'", a.data), diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca88595c7..0ba94def9 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -45,10 +45,16 @@ defmodule Pleroma.Web.AdminAPI.ReportView do def render("index_grouped.json", %{groups: groups}) do reports = Enum.map(groups, fn group -> + status = + if group[:status_deleted], + do: group[:status], + else: StatusView.render("show.json", %{activity: group[:status]}) + %{ date: group[:date], account: group[:account], - status: group[:status], + status: status, + status_deleted: status_deleted, actors: Enum.map(group[:actors], &merge_account_views/1), reports: group[:reports] -- cgit v1.2.3 From 40059c9b1e404412da103aaf74333bec364d3099 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 25 Nov 2019 00:05:52 +0900 Subject: Typo --- lib/pleroma/web/admin_api/views/report_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 0ba94def9..45ce75272 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -54,7 +54,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do date: group[:date], account: group[:account], status: status, - status_deleted: status_deleted, + status_deleted: group[:status_deleted], actors: Enum.map(group[:actors], &merge_account_views/1), reports: group[:reports] -- cgit v1.2.3 From 1364d303f8e4dcd4d9f7913d4755c58b0f4b87ef Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 25 Nov 2019 01:39:35 +0900 Subject: AdminAPI: Fix grouped reports for closed/resolved reports --- lib/pleroma/web/activity_pub/utils.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 277ca3c7c..9e460b604 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -852,7 +852,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do def get_reports_by_status_id(ap_id) do from(a in Activity, where: fragment("(?)->>'type' = 'Flag'", a.data), - where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]) + where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]), + or_where: fragment("(?)->'object' @> ?", a.data, ^[ap_id]) ) |> Activity.with_preloaded_user_actor() |> Repo.all() -- cgit v1.2.3 From b3b4e5ca805780f278b6239973d6d497b1697fbd Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Fri, 22 Nov 2019 13:35:21 +0900 Subject: AdminAPI: Grouped reports old/new fix If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports) --- lib/pleroma/web/activity_pub/utils.ex | 78 ++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 38 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9e460b604..718e3328d 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -788,36 +788,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do ActivityPub.fetch_activities([], params, :offset) end - @spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{ - required(:groups) => [ - %{ - required(:date) => String.t(), - required(:account) => %{}, - required(:status) => %{}, - required(:actors) => [%User{}], - required(:reports) => [%Activity{}] - } - ], - required(:total) => integer - } - def get_reports_grouped_by_status(groups) do - parsed_groups = - groups - |> Enum.map(fn entry -> - activity = - case Jason.decode(entry.activity) do - {:ok, activity} -> activity - _ -> build_flag_object(entry.activity) - end - - parse_report_group(activity) - end) - - %{ - groups: parsed_groups - } - end - def parse_report_group(activity) do reports = get_reports_by_status_id(activity["id"]) max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"])) @@ -859,6 +829,32 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Repo.all() end + @spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{ + required(:groups) => [ + %{ + required(:date) => String.t(), + required(:account) => %{}, + required(:status) => %{}, + required(:actors) => [%User{}], + required(:reports) => [%Activity{}] + } + ], + required(:total) => integer + } + def get_reports_grouped_by_status(activity_ids) do + parsed_groups = + activity_ids + |> Enum.map(fn id -> + id + |> build_flag_object() + |> parse_report_group() + end) + + %{ + groups: parsed_groups + } + end + @spec get_reported_activities() :: [ %{ required(:activity) => String.t(), @@ -866,17 +862,23 @@ defmodule Pleroma.Web.ActivityPub.Utils do } ] def get_reported_activities do - from(a in Activity, - where: fragment("(?)->>'type' = 'Flag'", a.data), + reported_activities_query = + from(a in Activity, + where: fragment("(?)->>'type' = 'Flag'", a.data), + select: %{ + activity: fragment("jsonb_array_elements((? #- '{object,0}')->'object')", a.data) + }, + group_by: fragment("activity") + ) + + from(a in subquery(reported_activities_query), + distinct: true, select: %{ - date: fragment("max(?->>'published') date", a.data), - activity: - fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity", a.data) - }, - group_by: fragment("activity"), - order_by: fragment("date DESC") + id: fragment("COALESCE(?->>'id'::text, ? #>> '{}')", a.activity, a.activity) + } ) |> Repo.all() + |> Enum.map(& &1.id) end def update_report_state(%Activity{} = activity, state) -- cgit v1.2.3 From 1fc28a4b441fdc0de8b2b43e2b045da38ee50b0a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 26 Nov 2019 18:48:56 +0700 Subject: Add a view for the move notification --- lib/pleroma/activity.ex | 3 +- .../web/mastodon_api/views/notification_view.ex | 38 +++++++++------------- lib/pleroma/web/push/impl.ex | 2 +- 3 files changed, 18 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c1065611b..992298c2d 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -28,7 +28,8 @@ defmodule Pleroma.Activity do "Create" => "mention", "Follow" => "follow", "Announce" => "reblog", - "Like" => "favourite" + "Like" => "favourite", + "Move" => "move" } @mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types, diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 5e3dbe728..ddd7f5318 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -37,32 +37,24 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do } case mastodon_type do - "mention" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: activity, for: user}) - }) - - "favourite" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: parent_activity, for: user}) - }) - - "reblog" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: parent_activity, for: user}) - }) - - "follow" -> - response - - _ -> - nil + "mention" -> put_status(response, activity, user) + "favourite" -> put_status(response, parent_activity, user) + "reblog" -> put_status(response, parent_activity, user) + "move" -> put_target(response, activity, user) + "follow" -> response + _ -> nil end else _ -> nil end end + + defp put_status(response, activity, user) do + Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user})) + end + + defp put_target(response, activity, user) do + target = User.get_cached_by_ap_id(activity.data["target"]) + Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user})) + end end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index dd445e8bf..8c131409d 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do require Logger import Ecto.Query - @types ["Create", "Follow", "Announce", "Like"] + @types ["Create", "Follow", "Announce", "Like", "Move"] @doc "Performs sending notifications for user subscriptions" @spec perform(Notification.t()) :: list(any) | :error -- cgit v1.2.3 From 5135656f579954cf786011b539934c7150e0d0bc Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Wed, 27 Nov 2019 22:54:12 +0900 Subject: Handle reopened reports with deleted statuses --- lib/pleroma/web/activity_pub/utils.ex | 50 +++++++++++++------------- lib/pleroma/web/admin_api/views/report_view.ex | 11 +++--- 2 files changed, 32 insertions(+), 29 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 962f02a05..d91abf7b3 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -722,16 +722,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do act when is_binary(act) -> act end - activity = Activity.get_by_ap_id_with_object(id) - actor = User.get_by_ap_id(activity.object.data["actor"]) + case Activity.get_by_ap_id_with_object(id) do + %Activity{} = activity -> + %{ + "type" => "Note", + "id" => activity.data["id"], + "content" => activity.object.data["content"], + "published" => activity.object.data["published"], + "actor" => + AccountView.render("show.json", %{ + user: User.get_by_ap_id(activity.object.data["actor"]) + }) + } - %{ - "type" => "Note", - "id" => activity.data["id"], - "content" => activity.object.data["content"], - "published" => activity.object.data["published"], - "actor" => AccountView.render("show.json", %{user: actor}) - } + _ -> + %{"id" => id, "deleted" => true} + end end defp build_flag_object(_), do: [] @@ -792,30 +798,27 @@ defmodule Pleroma.Web.ActivityPub.Utils do reports = get_reports_by_status_id(activity["id"]) max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"])) actors = Enum.map(reports, & &1.user_actor) - {deleted, status} = get_status_data(activity) + status = get_status_data(activity) %{ date: max_date.data["published"], account: activity["actor"], status: status, - status_deleted: deleted, actors: Enum.uniq(actors), reports: reports } end - defp get_status_data(activity) do - case Activity.get_by_ap_id(activity["id"]) do - %Activity{} = act -> - {false, act} + defp get_status_data(status) do + case status["deleted"] do + true -> + %{ + "id" => status["id"], + "deleted" => true + } _ -> - {true, - %{ - id: activity["id"], - content: activity["content"], - published: activity["published"] - }} + Activity.get_by_ap_id(status["id"]) end end @@ -829,7 +832,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Repo.all() end - @spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{ + @spec get_reports_grouped_by_status([String.t()]) :: %{ required(:groups) => [ %{ required(:date) => String.t(), @@ -838,8 +841,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do required(:actors) => [%User{}], required(:reports) => [%Activity{}] } - ], - required(:total) => integer + ] } def get_reports_grouped_by_status(activity_ids) do parsed_groups = diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 45ce75272..13602efd9 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do use Pleroma.Web, :view + alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.AdminAPI.Report @@ -46,15 +47,15 @@ defmodule Pleroma.Web.AdminAPI.ReportView do reports = Enum.map(groups, fn group -> status = - if group[:status_deleted], - do: group[:status], - else: StatusView.render("show.json", %{activity: group[:status]}) + case group.status do + %Activity{} = activity -> StatusView.render("show.json", %{activity: activity}) + _ -> group.status + end %{ date: group[:date], account: group[:account], - status: status, - status_deleted: group[:status_deleted], + status: Map.put_new(status, "deleted", false), actors: Enum.map(group[:actors], &merge_account_views/1), reports: group[:reports] -- cgit v1.2.3 From fcabcab4430b0aa075243bf98630d67c79f3ef9b Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Thu, 28 Nov 2019 00:09:00 +0900 Subject: Fetch account from report, not from status (it might be deleted) --- lib/pleroma/web/activity_pub/utils.ex | 9 ++++++++- lib/pleroma/web/admin_api/admin_api_controller.ex | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index d91abf7b3..2ca805c09 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -798,11 +798,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do reports = get_reports_by_status_id(activity["id"]) max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"])) actors = Enum.map(reports, & &1.user_actor) + [%{data: %{"object" => [account_id | _]}} | _] = reports + + account = + AccountView.render("show.json", %{ + user: User.get_by_ap_id(account_id) + }) + status = get_status_data(activity) %{ date: max_date.data["published"], - account: activity["actor"], + account: account, status: status, actors: Enum.uniq(actors), reports: reports diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 24fdc3c82..b003d1f35 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -647,11 +647,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def list_grouped_reports(conn, _params) do - reports = Utils.get_reported_activities() + statuses = Utils.get_reported_activities() conn |> put_view(ReportView) - |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports)) + |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses)) end def report_show(conn, %{"id" => id}) do -- cgit v1.2.3 From a52da55eb9c6bbf8a08bf1d90d59a48dc25f5907 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 28 Oct 2019 12:47:23 +0300 Subject: added User.NotificationSetting struct --- lib/pleroma/notification.ex | 8 +++--- lib/pleroma/user.ex | 26 ++++------------- lib/pleroma/user/notification_setting.ex | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 lib/pleroma/user/notification_setting.ex (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7ecf51e4..acb635fdc 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -314,7 +314,7 @@ defmodule Pleroma.Notification do def skip?( :followers, activity, - %{notification_settings: %{"followers" => false}} = user + %{notification_settings: %{followers: false}} = user ) do actor = activity.data["actor"] follower = User.get_cached_by_ap_id(actor) @@ -324,14 +324,14 @@ defmodule Pleroma.Notification do def skip?( :non_followers, activity, - %{notification_settings: %{"non_followers" => false}} = user + %{notification_settings: %{non_followers: false}} = user ) do actor = activity.data["actor"] follower = User.get_cached_by_ap_id(actor) !User.following?(follower, user) end - def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do + def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do actor = activity.data["actor"] followed = User.get_cached_by_ap_id(actor) User.following?(user, followed) @@ -340,7 +340,7 @@ defmodule Pleroma.Notification do def skip?( :non_follows, activity, - %{notification_settings: %{"non_follows" => false}} = user + %{notification_settings: %{non_follows: false}} = user ) do actor = activity.data["actor"] followed = User.get_cached_by_ap_id(actor) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b18a4c6a5..94fca2a9f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -105,13 +105,10 @@ defmodule Pleroma.User do field(:invisible, :boolean, default: false) field(:skip_thread_containment, :boolean, default: false) - field(:notification_settings, :map, - default: %{ - "followers" => true, - "follows" => true, - "non_follows" => true, - "non_followers" => true - } + embeds_one( + :notification_settings, + Pleroma.User.NotificationSetting, + on_replace: :update ) has_many(:notifications, Notification) @@ -1095,20 +1092,9 @@ defmodule Pleroma.User do end def update_notification_settings(%User{} = user, settings) do - settings = - settings - |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end) - |> Map.new() - - notification_settings = - user.notification_settings - |> Map.merge(settings) - |> Map.take(["followers", "follows", "non_follows", "non_followers"]) - - params = %{notification_settings: notification_settings} - user - |> cast(params, [:notification_settings]) + |> cast(%{notification_settings: settings}, []) + |> cast_embed(:notification_settings) |> validate_required([:notification_settings]) |> update_and_set_cache() end diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex new file mode 100644 index 000000000..64100c0e6 --- /dev/null +++ b/lib/pleroma/user/notification_setting.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.NotificationSetting do + use Ecto.Schema + import Ecto.Changeset + + @derive Jason.Encoder + @primary_key false + + @privacy_options %{ + name_and_message: "name_and_message", + name_only: "name_only", + no_name_or_message: "no_name_or_message" + } + + embedded_schema do + field(:followers, :boolean, default: true) + field(:follows, :boolean, default: true) + field(:non_follows, :boolean, default: true) + field(:non_followers, :boolean, default: true) + field(:privacy_option, :string, default: @privacy_options.name_and_message) + end + + def changeset(schema, params) do + schema + |> cast(prepare_attrs(params), [ + :followers, + :follows, + :non_follows, + :non_followers, + :privacy_option + ]) + |> validate_inclusion(:privacy_option, Map.values(@privacy_options)) + end + + defp prepare_attrs(params) do + Enum.reduce(params, %{}, fn + {k, v}, acc + when k in ["followers", "follows", "non_follows", "non_followers"] and + is_binary(v) -> + Map.put(acc, k, String.downcase(v)) + + {k, v}, acc -> + Map.put(acc, k, v) + end) + end +end -- cgit v1.2.3 From 04a8ffbe84c6d40709860e75fffa0330a2db690f Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 29 Oct 2019 21:33:17 +0300 Subject: added privacy option to push notifications --- lib/pleroma/user/notification_setting.ex | 13 ++----------- lib/pleroma/web/push/impl.ex | 27 ++++++++++++++++++++++----- lib/pleroma/workers/web_pusher_worker.ex | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex index 64100c0e6..f0899613e 100644 --- a/lib/pleroma/user/notification_setting.ex +++ b/lib/pleroma/user/notification_setting.ex @@ -9,18 +9,12 @@ defmodule Pleroma.User.NotificationSetting do @derive Jason.Encoder @primary_key false - @privacy_options %{ - name_and_message: "name_and_message", - name_only: "name_only", - no_name_or_message: "no_name_or_message" - } - embedded_schema do field(:followers, :boolean, default: true) field(:follows, :boolean, default: true) field(:non_follows, :boolean, default: true) field(:non_followers, :boolean, default: true) - field(:privacy_option, :string, default: @privacy_options.name_and_message) + field(:privacy_option, :boolean, default: false) end def changeset(schema, params) do @@ -32,14 +26,11 @@ defmodule Pleroma.User.NotificationSetting do :non_followers, :privacy_option ]) - |> validate_inclusion(:privacy_option, Map.values(@privacy_options)) end defp prepare_attrs(params) do Enum.reduce(params, %{}, fn - {k, v}, acc - when k in ["followers", "follows", "non_follows", "non_followers"] and - is_binary(v) -> + {k, v}, acc when is_binary(v) -> Map.put(acc, k, String.downcase(v)) {k, v}, acc -> diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 3de7af708..53f93c1ed 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do @spec perform(Notification.t()) :: list(any) | :error def perform( %{ - activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, - user_id: user_id + activity: %{data: %{"type" => activity_type}} = activity, + user: %User{id: user_id} } = notif ) when activity_type in @types do @@ -39,18 +39,17 @@ defmodule Pleroma.Web.Push.Impl do for subscription <- fetch_subsriptions(user_id), get_in(subscription.data, ["alerts", type]) do %{ - title: format_title(notif), access_token: subscription.token.token, - body: format_body(notif, actor, object), notification_id: notif.id, notification_type: type, icon: avatar_url, preferred_locale: "en", pleroma: %{ - activity_id: activity_id, + activity_id: notif.activity.id, direct_conversation_id: direct_conversation_id } } + |> Map.merge(build_content(notif, actor, object)) |> Jason.encode!() |> push_message(build_sub(subscription), gcm_api_key, subscription) end @@ -100,6 +99,24 @@ defmodule Pleroma.Web.Push.Impl do } end + def build_content( + %{ + activity: %{data: %{"directMessage" => true}}, + user: %{notification_settings: %{privacy_option: true}} + }, + actor, + _ + ) do + %{title: "New Direct Message", body: "@#{actor.nickname}"} + end + + def build_content(notif, actor, object) do + %{ + title: format_title(notif), + body: format_body(notif, actor, object) + } + end + def format_body( %{activity: %{data: %{"type" => "Create"}}}, actor, diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index 61b451e3e..a978c4013 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Workers.WebPusherWorker do notification = Notification |> Repo.get(notification_id) - |> Repo.preload([:activity]) + |> Repo.preload([:activity, :user]) Pleroma.Web.Push.Impl.perform(notification) end -- cgit v1.2.3 From a36607c27e6a50aeca450570f7b8e4c9c0233bb1 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 30 Oct 2019 22:59:04 +0300 Subject: add mix task to set\unset privacy option of notification --- lib/mix/tasks/pleroma/notification_settings.ex | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 lib/mix/tasks/pleroma/notification_settings.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex new file mode 100644 index 000000000..7d65f0587 --- /dev/null +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -0,0 +1,83 @@ +defmodule Mix.Tasks.Pleroma.NotificationSettings do + @shortdoc "Enable&Disable privacy option for push notifications" + @moduledoc """ + Example: + + > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user + > mix pleroma.notification_settings --privacy-option=true # set true for all users + + """ + + use Mix.Task + import Mix.Pleroma + import Ecto.Query + + def run(args) do + start_pleroma() + + {options, _, _} = + OptionParser.parse( + args, + strict: [ + privacy_option: :boolean, + email_users: :string, + nickname_users: :string + ] + ) + + privacy_option = Keyword.get(options, :privacy_option) + + if not is_nil(privacy_option) do + privacy_option + |> build_query(options) + |> Pleroma.Repo.update_all([]) + end + + shell_info("Done") + end + + defp build_query(privacy_option, options) do + query = + from(u in Pleroma.User, + update: [ + set: [ + notification_settings: + fragment( + "jsonb_set(notification_settings, '{privacy_option}', ?)", + ^privacy_option + ) + ] + ] + ) + + user_emails = + options + |> Keyword.get(:email_users, "") + |> String.split(",") + |> Enum.map(&String.trim(&1)) + |> Enum.reject(&(&1 == "")) + + query = + if length(user_emails) > 0 do + where(query, [u], u.email in ^user_emails) + else + query + end + + user_nicknames = + options + |> Keyword.get(:nickname_users, "") + |> String.split(",") + |> Enum.map(&String.trim(&1)) + |> Enum.reject(&(&1 == "")) + + query = + if length(user_nicknames) > 0 do + where(query, [u], u.nickname in ^user_nicknames) + else + query + end + + query + end +end -- cgit v1.2.3 From a98cda77580ead154dd2cb020c6d4ebc1e719d86 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 29 Nov 2019 15:49:35 +0700 Subject: Fix Pleroma.HTML.extract_first_external_url/2 --- lib/pleroma/html.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 997e965f0..4acd46253 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -91,6 +91,7 @@ defmodule Pleroma.HTML do Cachex.fetch!(:scrubber_cache, key, fn _key -> result = content + |> HtmlEntities.decode() |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]") |> Floki.attribute("a", "href") |> Enum.at(0) -- cgit v1.2.3 From 51111e286b316340b45b4e6a378646bed0cb0a6a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 29 Nov 2019 18:57:19 +0300 Subject: [#1427] Initial support for OAuth admin scopes. --- lib/pleroma/web/admin_api/admin_api_controller.ex | 16 +++++----- lib/pleroma/web/oauth/oauth_controller.ex | 10 +++---- lib/pleroma/web/oauth/scopes.ex | 34 ++++++++++++++++++---- .../controllers/emoji_api_controller.ex | 2 +- 4 files changed, 43 insertions(+), 19 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 8c1318d1b..01cd12c96 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -30,13 +30,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: ["read:accounts"]} + %{scopes: ["admin:read:accounts", "read:accounts"]} when action in [:list_users, :user_show, :right_get, :invites] ) plug( OAuthScopesPlug, - %{scopes: ["write:accounts"]} + %{scopes: ["admin:write:accounts", "write:accounts"]} when action in [ :get_invite_token, :revoke_invite, @@ -58,35 +58,35 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: ["read:reports"]} when action in [:list_reports, :report_show] + %{scopes: ["admin:read:reports", "read:reports"]} when action in [:list_reports, :report_show] ) plug( OAuthScopesPlug, - %{scopes: ["write:reports"]} + %{scopes: ["admin:reports", "write:reports"]} when action in [:report_update_state, :report_respond] ) plug( OAuthScopesPlug, - %{scopes: ["read:statuses"]} when action == :list_user_statuses + %{scopes: ["admin:read:statuses", "read:statuses"]} when action == :list_user_statuses ) plug( OAuthScopesPlug, - %{scopes: ["write:statuses"]} + %{scopes: ["admin:write:statuses", "write:statuses"]} when action in [:status_update, :status_delete] ) plug( OAuthScopesPlug, - %{scopes: ["read"]} + %{scopes: ["admin:read", "read"]} when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log] ) plug( OAuthScopesPlug, - %{scopes: ["write"]} + %{scopes: ["admin:write", "write"]} when action in [:relay_follow, :relay_unfollow, :config_update] ) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 2aee8cab2..87acdec97 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -222,7 +222,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:user_active, true} <- {:user_active, !user.deactivated}, {:password_reset_pending, false} <- {:password_reset_pending, user.password_reset_pending}, - {:ok, scopes} <- validate_scopes(app, params), + {:ok, scopes} <- validate_scopes(app, params, user), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build(user, token)) @@ -471,7 +471,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), - {:ok, scopes} <- validate_scopes(app, auth_attrs), + {:ok, scopes} <- validate_scopes(app, auth_attrs, user), {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do Authorization.create_authorization(app, user, scopes) end @@ -487,12 +487,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) - @spec validate_scopes(App.t(), map()) :: + @spec validate_scopes(App.t(), map(), User.t()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(app, params) do + defp validate_scopes(%App{} = app, params, %User{} = user) do params |> Scopes.fetch_scopes(app.scopes) - |> Scopes.validate(app.scopes) + |> Scopes.validate(app.scopes, user) end def default_redirect_uri(%App{} = app) do diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 48bd14407..0c8796ecb 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -7,6 +7,9 @@ defmodule Pleroma.Web.OAuth.Scopes do Functions for dealing with scopes. """ + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + @doc """ Fetch scopes from request params. @@ -53,15 +56,36 @@ defmodule Pleroma.Web.OAuth.Scopes do @doc """ Validates scopes. """ - @spec validate(list() | nil, list()) :: + @spec validate(list() | nil, list(), User.t()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - def validate([], _app_scopes), do: {:error, :missing_scopes} - def validate(nil, _app_scopes), do: {:error, :missing_scopes} + def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []], + do: {:error, :missing_scopes} - def validate(scopes, app_scopes) do - case Pleroma.Plugs.OAuthScopesPlug.filter_descendants(scopes, app_scopes) do + def validate(scopes, app_scopes, %User{} = user) do + with {:ok, _} <- ensure_scopes_support(scopes, app_scopes), + {:ok, scopes} <- authorize_admin_scopes(scopes, app_scopes, user) do + {:ok, scopes} + end + end + + defp ensure_scopes_support(scopes, app_scopes) do + case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do ^scopes -> {:ok, scopes} _ -> {:error, :unsupported_scopes} end end + + defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do + if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do + {:ok, scopes} + else + {:error, :unsupported_scopes} + end + end + + defp contains_admin_scopes?(scopes) do + scopes + |> OAuthScopesPlug.filter_descendants(["admin"]) + |> Enum.any?() + end end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index a474d41d4..1e8c62e3a 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( OAuthScopesPlug, - %{scopes: ["write"]} + %{scopes: ["admin:write", "write"]} when action in [ :create, :delete, -- cgit v1.2.3 From 0326683932800c20dcec2b7d3500bff149cf091b Mon Sep 17 00:00:00 2001 From: kPherox Date: Sat, 30 Nov 2019 04:26:45 +0900 Subject: hide follower/following for friendica --- lib/pleroma/web/activity_pub/activity_pub.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f25314ff6..f32d04175 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1233,13 +1233,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - defp collection_private(data) do - if is_map(data["first"]) and - data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do + defp collection_private(%{"first" => first}) do + if is_map(first) and + first["type"] in ["CollectionPage", "OrderedCollectionPage"] do {:ok, false} else with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <- - Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do + Fetcher.fetch_and_contain_remote_object_from_id(first) do {:ok, false} else {:error, {:ok, %{status: code}}} when code in [401, 403] -> @@ -1254,6 +1254,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + defp collection_private(_data), do: {:ok, true} + def user_data_from_user_object(data) do with {:ok, data} <- MRF.filter(data), {:ok, data} <- object_to_user_data(data) do -- cgit v1.2.3 From cf52106e059560afb50de5ae8044d0a51278fae1 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 2 Dec 2019 13:38:35 +0700 Subject: Update Floki dependency --- lib/pleroma/html.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 4acd46253..997e965f0 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -91,7 +91,6 @@ defmodule Pleroma.HTML do Cachex.fetch!(:scrubber_cache, key, fn _key -> result = content - |> HtmlEntities.decode() |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]") |> Floki.attribute("a", "href") |> Enum.at(0) -- cgit v1.2.3 From 356a936ecbddf6bbdfada2ef70264b99e9d9723c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 2 Dec 2019 18:11:45 +0700 Subject: Fix `mix pleroma.user list` task --- lib/mix/tasks/pleroma/user.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index bc8eacda8..0adb78fe3 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -373,9 +373,9 @@ defmodule Mix.Tasks.Pleroma.User do users |> Enum.each(fn user -> shell_info( - "#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{ - user.info.locked - }, deactivated: #{user.info.deactivated}" + "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{ + user.locked + }, deactivated: #{user.deactivated}" ) end) end) -- cgit v1.2.3 From 624e720aa45fd54575d17a2b5e77c67deef6eb86 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 3 Dec 2019 22:13:38 +0700 Subject: Add `with_move` query param to the notifications API --- lib/pleroma/notification.ex | 9 +++++++++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index f37e7ec67..6b6e1c2e1 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -57,6 +57,7 @@ defmodule Pleroma.Notification do |> exclude_muted(user, opts) |> exclude_blocked(user) |> exclude_visibility(opts) + |> exclude_move(opts) end defp exclude_blocked(query, user) do @@ -81,6 +82,14 @@ defmodule Pleroma.Notification do |> where([n, a, o, tm], is_nil(tm.user_id)) end + defp exclude_move(query, %{with_move: true}) do + query + end + + defp exclude_move(query, _opts) do + where(query, [n, a], fragment("?->>'type' != 'Move'", a.data)) + end + @valid_visibilities ~w[direct unlisted public private] defp exclude_visibility(query, %{exclude_visibilities: visibility}) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index d875a5788..3e7dc4617 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -73,7 +73,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do exclude_types: {:array, :string}, exclude_visibilities: {:array, :string}, reblogs: :boolean, - with_muted: :boolean + with_muted: :boolean, + with_move: :boolean } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) -- cgit v1.2.3 From 05fb8d0084553a3ed7d1f10212f49771ce05570f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 3 Dec 2019 22:31:12 +0700 Subject: Fix Pleroma.FollowingRelationship.move_following/2 --- lib/pleroma/following_relationship.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a03c9bd30..0b0219b82 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -121,8 +121,12 @@ defmodule Pleroma.FollowingRelationship do Pleroma.Web.CommonAPI.follow(following_relationship.follower, target) end) |> case do - [] -> :ok - _ -> move_following(origin, target) + [] -> + User.update_follower_count(origin) + :ok + + _ -> + move_following(origin, target) end end end -- cgit v1.2.3 From aa97fe8f175c3618a04bb45e6aa7c37e26259577 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 4 Dec 2019 12:48:34 +0100 Subject: ChatChannel: Ignore messages that are too long. --- lib/pleroma/application.ex | 2 -- lib/pleroma/web/chat_channel.ex | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 9dbd1e26b..57462740c 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -147,8 +147,6 @@ defmodule Pleroma.Application do defp oauth_cleanup_child(_), do: [] - defp chat_child(:test, _), do: [] - defp chat_child(_env, true) do [Pleroma.Web.ChatChannel.ChatChannelState] end diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index 08841a3e8..840414933 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ChatChannel do def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do text = String.trim(text) - if String.length(text) > 0 do + if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do author = User.get_cached_by_nickname(user_name) author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) message = ChatChannelState.add_message(%{text: text, author: author}) -- cgit v1.2.3 From d3bce89edddf72e2eb78e7c84a93c5bd4b6c8ff8 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 4 Dec 2019 19:41:14 +0300 Subject: [#1335] Merge issue fix. --- lib/pleroma/user.ex | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a68524377..b7f50e5ac 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -169,8 +169,6 @@ defmodule Pleroma.User do has_many(incoming_relation_source, through: [incoming_relation, :source]) end - field(:info, :map, default: %{}) - # `:blocks` is deprecated (replaced with `blocked_users` relation) field(:blocks, {:array, :string}, default: []) # `:mutes` is deprecated (replaced with `muted_users` relation) -- cgit v1.2.3 From 9f99640cfc81f28c0b6cdb5f6ef065b3e46f5f23 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Dec 2019 02:50:38 +0300 Subject: ActivityPub: Fix deletes being exempt from MRF Closes #1461 --- lib/pleroma/activity.ex | 5 +++-- lib/pleroma/activity/queries.ex | 8 ++++++++ lib/pleroma/object.ex | 2 +- lib/pleroma/web/activity_pub/activity_pub.ex | 5 +++-- 4 files changed, 15 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index f180c1e33..480b261cf 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -241,9 +241,10 @@ defmodule Pleroma.Activity do def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) def normalize(_), do: nil - def delete_by_ap_id(id) when is_binary(id) do + def delete_all_by_object_ap_id(id) when is_binary(id) do id |> Queries.by_object_id() + |> Queries.exclude_type("Delete") |> select([u], u) |> Repo.delete_all() |> elem(1) @@ -255,7 +256,7 @@ defmodule Pleroma.Activity do |> purge_web_resp_cache() end - def delete_by_ap_id(_), do: nil + def delete_all_by_object_ap_id(_), do: nil defp purge_web_resp_cache(%Activity{} = activity) do %{path: path} = URI.parse(activity.data["id"]) diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index 949f010a8..26bc1099d 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -64,4 +64,12 @@ defmodule Pleroma.Activity.Queries do where: fragment("(?)->>'type' = ?", activity.data, ^activity_type) ) end + + @spec exclude_type(query, String.t()) :: query + def exclude_type(query \\ Activity, activity_type) do + from( + activity in query, + where: fragment("(?)->>'type' != ?", activity.data, ^activity_type) + ) + end end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index b4ed3a9b2..ff0e59241 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -147,7 +147,7 @@ defmodule Pleroma.Object do def delete(%Object{data: %{"id" => id}} = object) do with {:ok, _obj} = swap_object_with_tombstone(object), - deleted_activity = Activity.delete_by_ap_id(id), + deleted_activity = Activity.delete_all_by_object_ap_id(id), {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do {:ok, object, deleted_activity} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d6a425d8b..c8160b0cf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -456,17 +456,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do user = User.get_cached_by_ap_id(actor) to = (object.data["to"] || []) ++ (object.data["cc"] || []) - with {:ok, object, activity} <- Object.delete(object), + with create_activity <- Activity.get_create_by_object_ap_id(id), data <- %{ "type" => "Delete", "actor" => actor, "object" => id, "to" => to, - "deleted_activity_id" => activity && activity.id + "deleted_activity_id" => create_activity && create_activity.id } |> maybe_put("id", activity_id), {:ok, activity} <- insert(data, local, false), + {:ok, object, _create_activity} <- Object.delete(object), stream_out_participations(object, user), _ <- decrease_replies_count_if_reply(object), {:ok, _actor} <- decrease_note_count_if_public(user, object), -- cgit v1.2.3 From af42c00cfffb2cd8e93857cd1cf2901113c45bd2 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 6 Dec 2019 00:25:44 +0300 Subject: [#1427] Reworked admin scopes support. Requalified users.is_admin flag as legacy accessor to admin actions in case token lacks admin scope(s). --- lib/mix/tasks/pleroma/user.ex | 9 ++----- lib/pleroma/config.ex | 7 ++++++ lib/pleroma/plugs/user_is_admin_plug.ex | 28 ++++++++++++++++++---- lib/pleroma/user.ex | 28 ++++++++++++++++------ lib/pleroma/web/admin_api/admin_api_controller.ex | 18 +++++++------- lib/pleroma/web/oauth/scopes.ex | 2 +- .../controllers/emoji_api_controller.ex | 2 +- 7 files changed, 66 insertions(+), 28 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 4e3b80db3..8c4739221 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -8,7 +8,6 @@ defmodule Mix.Tasks.Pleroma.User do alias Ecto.Changeset alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.OAuth @shortdoc "Manages Pleroma users" @moduledoc File.read!("docs/administration/CLI_tasks/user.md") @@ -354,8 +353,7 @@ defmodule Mix.Tasks.Pleroma.User do start_pleroma() with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do - OAuth.Token.delete_user_tokens(user) - OAuth.Authorization.delete_user_authorizations(user) + User.global_sign_out(user) shell_info("#{nickname} signed out from all apps.") else @@ -375,10 +373,7 @@ defmodule Mix.Tasks.Pleroma.User do end defp set_admin(user, value) do - {:ok, user} = - user - |> Changeset.change(%{is_admin: value}) - |> User.update_and_set_cache() + {:ok, user} = User.admin_api_update(user, %{is_admin: value}) shell_info("Admin status of #{user.nickname}: #{user.is_admin}") user diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex index fcc039710..cadab2f15 100644 --- a/lib/pleroma/config.ex +++ b/lib/pleroma/config.ex @@ -65,4 +65,11 @@ defmodule Pleroma.Config do def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], []) def oauth_consumer_enabled?, do: oauth_consumer_strategies() != [] + + def enforce_oauth_admin_scope_usage?, do: !!get([:auth, :enforce_oauth_admin_scope_usage]) + + def oauth_admin_scopes(scope) do + ["admin:#{scope}"] ++ + if enforce_oauth_admin_scope_usage?(), do: [], else: [scope] + end end diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index ee808f31f..8814556f1 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -5,19 +5,39 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn + alias Pleroma.User + alias Pleroma.Web.OAuth def init(options) do options end - def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do - conn + def call(%Plug.Conn{assigns: %{token: %OAuth.Token{scopes: oauth_scopes} = _token}} = conn, _) do + if OAuth.Scopes.contains_admin_scopes?(oauth_scopes) do + # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. + # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. + conn + else + fail(conn) + end + end + + unless Pleroma.Config.enforce_oauth_admin_scope_usage?() do + # To do: once AdminFE makes use of "admin" scope, disable the following func definition + # (fail on no admin scope(s) in token even if `is_admin` is true) + def call(%Plug.Conn{assigns: %{user: %User{is_admin: true}}} = conn, _) do + conn + end end def call(conn, _) do + fail(conn) + end + + defp fail(conn) do conn - |> render_error(:forbidden, "User is not admin.") - |> halt + |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") + |> halt() end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index fcb1d5143..d05dccd3d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1732,13 +1732,27 @@ defmodule Pleroma.User do end def admin_api_update(user, params) do - user - |> cast(params, [ - :is_moderator, - :is_admin, - :show_role - ]) - |> update_and_set_cache() + changeset = + cast(user, params, [ + :is_moderator, + :is_admin, + :show_role + ]) + + with {:ok, updated_user} <- update_and_set_cache(changeset) do + if user.is_admin && !updated_user.is_admin do + # Tokens & authorizations containing any admin scopes must be revoked (revoking all) + global_sign_out(user) + end + + {:ok, updated_user} + end + end + + @doc "Signs user out of all applications" + def global_sign_out(user) do + OAuth.Authorization.delete_user_authorizations(user) + OAuth.Token.delete_user_tokens(user) end def mascot_update(user, url) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 01cd12c96..f9ace00d7 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -30,13 +30,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: ["admin:read:accounts", "read:accounts"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("read:accounts")} when action in [:list_users, :user_show, :right_get, :invites] ) plug( OAuthScopesPlug, - %{scopes: ["admin:write:accounts", "write:accounts"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("write:accounts")} when action in [ :get_invite_token, :revoke_invite, @@ -58,35 +58,37 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: ["admin:read:reports", "read:reports"]} when action in [:list_reports, :report_show] + %{scopes: Pleroma.Config.oauth_admin_scopes("read:reports")} + when action in [:list_reports, :report_show] ) plug( OAuthScopesPlug, - %{scopes: ["admin:reports", "write:reports"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("write:reports")} when action in [:report_update_state, :report_respond] ) plug( OAuthScopesPlug, - %{scopes: ["admin:read:statuses", "read:statuses"]} when action == :list_user_statuses + %{scopes: Pleroma.Config.oauth_admin_scopes("read:statuses")} + when action == :list_user_statuses ) plug( OAuthScopesPlug, - %{scopes: ["admin:write:statuses", "write:statuses"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("write:statuses")} when action in [:status_update, :status_delete] ) plug( OAuthScopesPlug, - %{scopes: ["admin:read", "read"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("read")} when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log] ) plug( OAuthScopesPlug, - %{scopes: ["admin:write", "write"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("write")} when action in [:relay_follow, :relay_unfollow, :config_update] ) diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 0c8796ecb..5e04652c2 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -83,7 +83,7 @@ defmodule Pleroma.Web.OAuth.Scopes do end end - defp contains_admin_scopes?(scopes) do + def contains_admin_scopes?(scopes) do scopes |> OAuthScopesPlug.filter_descendants(["admin"]) |> Enum.any?() diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 1e8c62e3a..6f286032e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( OAuthScopesPlug, - %{scopes: ["admin:write", "write"]} + %{scopes: Pleroma.Config.oauth_admin_scopes("write")} when action in [ :create, :delete, -- cgit v1.2.3 From e8cee4d9a0ea13db8d087e42eb17939bb8b11f0b Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 6 Dec 2019 14:25:13 +0100 Subject: ActivityPub: For user timelines, respects blocks. Unless the timeline belongs to a blocked user. --- lib/pleroma/web/activity_pub/activity_pub.ex | 9 +++++++++ lib/pleroma/web/mastodon_api/controllers/account_controller.ex | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index b07a94701..c5bc1ef0d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -748,6 +748,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Map.put("whole_db", true) |> Map.put("pinned_activity_ids", user.pinned_activities) + params = + if User.blocks?(reading_user, user) do + params + else + params + |> Map.put("blocking_user", reading_user) + |> Map.put("muting_user", reading_user) + end + recipients = user_activities_recipients(%{ "godmode" => params["godmode"], diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index d71a14434..d19029cb5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -249,7 +249,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/accounts/:id/statuses" def statuses(%{assigns: %{user: reading_user}} = conn, params) do with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do - params = Map.put(params, "tag", params["tagged"]) + params = + params + |> Map.put("tag", params["tagged"]) + |> Map.delete("godmode") + activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn -- cgit v1.2.3 From 93a80ee9155bf5257aa92afaca2fe017f28aabfa Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 6 Dec 2019 16:56:23 +0300 Subject: [#1427] Bugfix for `enforce_oauth_admin_scope_usage`. Admin API documentation entry. --- lib/pleroma/plugs/user_is_admin_plug.ex | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 8814556f1..814029dce 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -6,13 +6,20 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn - alias Pleroma.User alias Pleroma.Web.OAuth def init(options) do options end + unless Pleroma.Config.enforce_oauth_admin_scope_usage?() do + # To do: once AdminFE makes use of "admin" scope, disable the following func definition + # (fail on no admin scope(s) in token even if `is_admin` is true) + def call(%Plug.Conn{assigns: %{user: %Pleroma.User{is_admin: true}}} = conn, _) do + conn + end + end + def call(%Plug.Conn{assigns: %{token: %OAuth.Token{scopes: oauth_scopes} = _token}} = conn, _) do if OAuth.Scopes.contains_admin_scopes?(oauth_scopes) do # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. @@ -23,14 +30,6 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do end end - unless Pleroma.Config.enforce_oauth_admin_scope_usage?() do - # To do: once AdminFE makes use of "admin" scope, disable the following func definition - # (fail on no admin scope(s) in token even if `is_admin` is true) - def call(%Plug.Conn{assigns: %{user: %User{is_admin: true}}} = conn, _) do - conn - end - end - def call(conn, _) do fail(conn) end -- cgit v1.2.3 From 40e1817f707c3c2ef253009c7363cd81b11322a6 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 6 Dec 2019 20:33:47 +0300 Subject: [#1427] Fixes / improvements of admin scopes support. Added tests. --- lib/pleroma/plugs/oauth_scopes_plug.ex | 9 +++++ lib/pleroma/plugs/user_is_admin_plug.ex | 38 +++++++++------------- lib/pleroma/web/admin_api/admin_api_controller.ex | 16 ++++----- .../controllers/emoji_api_controller.ex | 2 +- 4 files changed, 33 insertions(+), 32 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index a3278dbef..3201fb399 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do import Plug.Conn import Pleroma.Web.Gettext + alias Pleroma.Config alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug @behaviour Plug @@ -15,6 +16,14 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do op = options[:op] || :| token = assigns[:token] + + scopes = + if options[:admin] do + Config.oauth_admin_scopes(scopes) + else + scopes + end + matched_scopes = token && filter_descendants(scopes, token.scopes) cond do diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 814029dce..4a0e43b00 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -12,31 +12,23 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do options end - unless Pleroma.Config.enforce_oauth_admin_scope_usage?() do - # To do: once AdminFE makes use of "admin" scope, disable the following func definition - # (fail on no admin scope(s) in token even if `is_admin` is true) - def call(%Plug.Conn{assigns: %{user: %Pleroma.User{is_admin: true}}} = conn, _) do - conn - end - end + def call(%Plug.Conn{assigns: assigns} = conn, _) do + token = assigns[:token] + user = assigns[:user] - def call(%Plug.Conn{assigns: %{token: %OAuth.Token{scopes: oauth_scopes} = _token}} = conn, _) do - if OAuth.Scopes.contains_admin_scopes?(oauth_scopes) do - # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. - # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. - conn - else - fail(conn) - end - end + cond do + token && OAuth.Scopes.contains_admin_scopes?(token.scopes) -> + # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. + # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. + conn - def call(conn, _) do - fail(conn) - end + user && user.is_admin && !Pleroma.Config.enforce_oauth_admin_scope_usage?() -> + conn - defp fail(conn) do - conn - |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") - |> halt() + true -> + conn + |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") + |> halt() + end end end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 34d147107..0a63f3fe6 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -30,13 +30,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("read:accounts")} + %{scopes: ["read:accounts"], admin: true} when action in [:list_users, :user_show, :right_get, :invites] ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("write:accounts")} + %{scopes: ["write:accounts"], admin: true} when action in [ :get_invite_token, :revoke_invite, @@ -58,37 +58,37 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("read:reports")} + %{scopes: ["read:reports"], admin: true} when action in [:list_reports, :report_show] ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("write:reports")} + %{scopes: ["write:reports"], admin: true} when action in [:report_update_state, :report_respond] ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("read:statuses")} + %{scopes: ["read:statuses"], admin: true} when action == :list_user_statuses ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("write:statuses")} + %{scopes: ["write:statuses"], admin: true} when action in [:status_update, :status_delete] ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("read")} + %{scopes: ["read"], admin: true} when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log] ) plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("write")} + %{scopes: ["write"], admin: true} when action in [:relay_follow, :relay_unfollow, :config_update] ) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 6f286032e..69dfa92e3 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( OAuthScopesPlug, - %{scopes: Pleroma.Config.oauth_admin_scopes("write")} + %{scopes: ["write"], admin: true} when action in [ :create, :delete, -- cgit v1.2.3 From 1770602747ae95d95d12c5601f99ced8699e8947 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 7 Dec 2019 17:49:53 +0300 Subject: [#1427] Extra check that admin OAuth scope is used by admin. Adjusted tests. --- lib/pleroma/plugs/user_is_admin_plug.ex | 24 ++++++++++++++++-------- lib/pleroma/user.ex | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 4a0e43b00..582fb1f92 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -6,29 +6,37 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn + alias Pleroma.User alias Pleroma.Web.OAuth def init(options) do options end - def call(%Plug.Conn{assigns: assigns} = conn, _) do + def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do token = assigns[:token] - user = assigns[:user] cond do + not Pleroma.Config.enforce_oauth_admin_scope_usage?() -> + conn + token && OAuth.Scopes.contains_admin_scopes?(token.scopes) -> # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. # Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. conn - user && user.is_admin && !Pleroma.Config.enforce_oauth_admin_scope_usage?() -> - conn - true -> - conn - |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") - |> halt() + fail(conn) end end + + def call(conn, _) do + fail(conn) + end + + defp fail(conn) do + conn + |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") + |> halt() + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7b8222ce1..1006b5bf9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1736,7 +1736,8 @@ defmodule Pleroma.User do with {:ok, updated_user} <- update_and_set_cache(changeset) do if user.is_admin && !updated_user.is_admin do - # Tokens & authorizations containing any admin scopes must be revoked (revoking all) + # Tokens & authorizations containing any admin scopes must be revoked (revoking all). + # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins). global_sign_out(user) end -- cgit v1.2.3 From a21340caa192b41051abe3b5905fbcb2fde281d7 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 8 Dec 2019 16:36:22 +0300 Subject: Fix never matching clause `length/1` is only used with lists. --- lib/pleroma/clippy.ex | 1 + lib/pleroma/html.ex | 1 + lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/clippy.ex b/lib/pleroma/clippy.ex index bd20952a6..6e6121d4e 100644 --- a/lib/pleroma/clippy.ex +++ b/lib/pleroma/clippy.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Clippy do @moduledoc false + # No software is complete until they have a Clippy implementation. # A ballmer peak _may_ be required to change this module. diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 997e965f0..71c53ce0e 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -163,6 +163,7 @@ defmodule Pleroma.HTML.Scrubber.Default do require FastSanitize.Sanitizer.Meta alias FastSanitize.Sanitizer.Meta + # credo:disable-for-previous-line # No idea how to fix this one… diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ce95fb6ba..ecba27bef 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -387,7 +387,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming(%{"id" => nil}, _options), do: :error def handle_incoming(%{"id" => ""}, _options), do: :error # length of https:// = 8, should validate better, but good enough for now. - def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8), + def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8, do: :error # TODO: validate those with a Ecto scheme -- cgit v1.2.3 From bd3aa8500c64b769a36a75fefa445668dc8da4a6 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 8 Dec 2019 17:36:44 +0300 Subject: OAuth token cleanup: Get rid of compile-time configuration --- lib/pleroma/web/oauth/token/clean_worker.ex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index f639f9c6f..3c9c580d5 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -11,11 +11,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @ten_seconds 10_000 @one_day 86_400_000 - @interval Pleroma.Config.get( - [:oauth2, :clean_expired_tokens_interval], - @one_day - ) - alias Pleroma.Web.OAuth.Token alias Pleroma.Workers.BackgroundWorker @@ -29,8 +24,9 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @doc false def handle_info(:perform, state) do BackgroundWorker.enqueue("clean_expired_tokens", %{}) + interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) - Process.send_after(self(), :perform, @interval) + Process.send_after(self(), :perform, interval) {:noreply, state} end -- cgit v1.2.3 From d6c89068f3c6765b7a3ef63199725b7833c34c3a Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 8 Dec 2019 19:42:40 +0300 Subject: HTML: Compile Scrubbers on boot This makes it possible to configure their behavior on OTP releases. --- lib/pleroma/application.ex | 1 + lib/pleroma/html.ex | 232 ++++----------------------------------------- 2 files changed, 20 insertions(+), 213 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 57462740c..5b844aa41 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -30,6 +30,7 @@ defmodule Pleroma.Application do # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications def start(_type, _args) do + Pleroma.HTML.compile_scrubbers() Pleroma.Config.DeprecationWarnings.warn() setup_instrumenters() diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 71c53ce0e..2cae29f35 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -3,6 +3,25 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.HTML do + # Scrubbers are compiled on boot so they can be configured in OTP releases + # @on_load :compile_scrubbers + + def compile_scrubbers do + dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") + + dir + |> File.ls!() + |> Enum.map(&Path.join(dir, &1)) + |> Kernel.ParallelCompiler.compile() + |> case do + {:error, _errors, _warnings} -> + raise "Compiling scrubbers failed" + + {:ok, _modules, _warnings} -> + :ok + end + end + defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber] defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default] @@ -99,216 +118,3 @@ defmodule Pleroma.HTML do end) end end - -defmodule Pleroma.HTML.Scrubber.TwitterText do - @moduledoc """ - An HTML scrubbing policy which limits to twitter-style text. Only - paragraphs, breaks and links are allowed through the filter. - """ - - @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - - require FastSanitize.Sanitizer.Meta - alias FastSanitize.Sanitizer.Meta - - Meta.strip_comments() - - # links - Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes) - - Meta.allow_tag_with_this_attribute_values(:a, "class", [ - "hashtag", - "u-url", - "mention", - "u-url mention", - "mention u-url" - ]) - - Meta.allow_tag_with_this_attribute_values(:a, "rel", [ - "tag", - "nofollow", - "noopener", - "noreferrer" - ]) - - Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) - - # paragraphs and linebreaks - Meta.allow_tag_with_these_attributes(:br, []) - Meta.allow_tag_with_these_attributes(:p, []) - - # microformats - Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"]) - Meta.allow_tag_with_these_attributes(:span, []) - - # allow inline images for custom emoji - if Pleroma.Config.get([:markup, :allow_inline_images]) do - # restrict img tags to http/https only, because of MediaProxy. - Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"]) - - Meta.allow_tag_with_these_attributes(:img, [ - "width", - "height", - "class", - "title", - "alt" - ]) - end - - Meta.strip_everything_not_covered() -end - -defmodule Pleroma.HTML.Scrubber.Default do - @doc "The default HTML scrubbing policy: no " - - require FastSanitize.Sanitizer.Meta - alias FastSanitize.Sanitizer.Meta - - # credo:disable-for-previous-line - # No idea how to fix this one… - - @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - - Meta.strip_comments() - - Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes) - - Meta.allow_tag_with_this_attribute_values(:a, "class", [ - "hashtag", - "u-url", - "mention", - "u-url mention", - "mention u-url" - ]) - - Meta.allow_tag_with_this_attribute_values(:a, "rel", [ - "tag", - "nofollow", - "noopener", - "noreferrer", - "ugc" - ]) - - Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) - - Meta.allow_tag_with_these_attributes(:abbr, ["title"]) - - Meta.allow_tag_with_these_attributes(:b, []) - Meta.allow_tag_with_these_attributes(:blockquote, []) - Meta.allow_tag_with_these_attributes(:br, []) - Meta.allow_tag_with_these_attributes(:code, []) - Meta.allow_tag_with_these_attributes(:del, []) - Meta.allow_tag_with_these_attributes(:em, []) - Meta.allow_tag_with_these_attributes(:i, []) - Meta.allow_tag_with_these_attributes(:li, []) - Meta.allow_tag_with_these_attributes(:ol, []) - Meta.allow_tag_with_these_attributes(:p, []) - Meta.allow_tag_with_these_attributes(:pre, []) - Meta.allow_tag_with_these_attributes(:strong, []) - Meta.allow_tag_with_these_attributes(:sub, []) - Meta.allow_tag_with_these_attributes(:sup, []) - Meta.allow_tag_with_these_attributes(:u, []) - Meta.allow_tag_with_these_attributes(:ul, []) - - Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"]) - Meta.allow_tag_with_these_attributes(:span, []) - - @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images]) - - if @allow_inline_images do - # restrict img tags to http/https only, because of MediaProxy. - Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"]) - - Meta.allow_tag_with_these_attributes(:img, [ - "width", - "height", - "class", - "title", - "alt" - ]) - end - - if Pleroma.Config.get([:markup, :allow_tables]) do - Meta.allow_tag_with_these_attributes(:table, []) - Meta.allow_tag_with_these_attributes(:tbody, []) - Meta.allow_tag_with_these_attributes(:td, []) - Meta.allow_tag_with_these_attributes(:th, []) - Meta.allow_tag_with_these_attributes(:thead, []) - Meta.allow_tag_with_these_attributes(:tr, []) - end - - if Pleroma.Config.get([:markup, :allow_headings]) do - Meta.allow_tag_with_these_attributes(:h1, []) - Meta.allow_tag_with_these_attributes(:h2, []) - Meta.allow_tag_with_these_attributes(:h3, []) - Meta.allow_tag_with_these_attributes(:h4, []) - Meta.allow_tag_with_these_attributes(:h5, []) - end - - if Pleroma.Config.get([:markup, :allow_fonts]) do - Meta.allow_tag_with_these_attributes(:font, ["face"]) - end - - Meta.strip_everything_not_covered() -end - -defmodule Pleroma.HTML.Transform.MediaProxy do - @moduledoc "Transforms inline image URIs to use MediaProxy." - - alias Pleroma.Web.MediaProxy - - def before_scrub(html), do: html - - def scrub_attribute(:img, {"src", "http" <> target}) do - media_url = - ("http" <> target) - |> MediaProxy.url() - - {"src", media_url} - end - - def scrub_attribute(_tag, attribute), do: attribute - - def scrub({:img, attributes, children}) do - attributes = - attributes - |> Enum.map(fn attr -> scrub_attribute(:img, attr) end) - |> Enum.reject(&is_nil(&1)) - - {:img, attributes, children} - end - - def scrub({:comment, _text, _children}), do: "" - - def scrub({tag, attributes, children}), do: {tag, attributes, children} - def scrub({_tag, children}), do: children - def scrub(text), do: text -end - -defmodule Pleroma.HTML.Scrubber.LinksOnly do - @moduledoc """ - An HTML scrubbing policy which limits to links only. - """ - - @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - - require FastSanitize.Sanitizer.Meta - alias FastSanitize.Sanitizer.Meta - - Meta.strip_comments() - - # links - Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes) - - Meta.allow_tag_with_this_attribute_values(:a, "rel", [ - "tag", - "nofollow", - "noopener", - "noreferrer", - "me", - "ugc" - ]) - - Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) - Meta.strip_everything_not_covered() -end -- cgit v1.2.3 From 3c45ed4f4752bb2dce3ca2e9a153cb278df685fb Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 8 Dec 2019 21:08:25 +0300 Subject: OTP: Fix runtime upload limit config being ignored Closes #1109 --- lib/pleroma/plugs/parsers_plug.ex | 21 +++++++++++++++++++++ lib/pleroma/web/endpoint.ex | 9 +-------- 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 lib/pleroma/plugs/parsers_plug.ex (limited to 'lib') diff --git a/lib/pleroma/plugs/parsers_plug.ex b/lib/pleroma/plugs/parsers_plug.ex new file mode 100644 index 000000000..2e493ce0e --- /dev/null +++ b/lib/pleroma/plugs/parsers_plug.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.Parsers do + @moduledoc "Initializes Plug.Parsers with upload limit set at boot time" + + @behaviour Plug + + def init(_opts) do + Plug.Parsers.init( + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Jason, + length: Pleroma.Config.get([:instance, :upload_limit]), + body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} + ) + end + + defdelegate call(conn, opts), to: Plug.Parsers +end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 49735b5c2..bbea31682 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -61,14 +61,7 @@ defmodule Pleroma.Web.Endpoint do plug(Plug.RequestId) plug(Plug.Logger) - plug( - Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], - pass: ["*/*"], - json_decoder: Jason, - length: Pleroma.Config.get([:instance, :upload_limit]), - body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} - ) + plug(Pleroma.Plugs.Parsers) plug(Plug.MethodOverride) plug(Plug.Head) -- cgit v1.2.3 From f4b7f32d51f9d0bd721befdd33b49d2c52a6e231 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 9 Dec 2019 20:45:04 +0300 Subject: status search: prefer the status fetched by url over other results --- lib/pleroma/activity/search.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f847ac238..d30a5a6a5 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Activity.Search do {:ok, object} <- Fetcher.fetch_object_from_id(search_query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do - activities ++ [activity] + [activity | activities] else _ -> activities end -- cgit v1.2.3 From 9dfaa0b832ddb09f0937c96e5e30b83957f8185f Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 9 Dec 2019 22:29:44 +0300 Subject: fix loads config variable with large value from db --- lib/mix/tasks/pleroma/config.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 0e21408b2..590c7a914 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -52,7 +52,9 @@ defmodule Mix.Tasks.Pleroma.Config do |> Enum.each(fn config -> IO.write( file, - "config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n" + "config :#{config.group}, #{config.key}, #{ + inspect(Config.from_binary(config.value), limit: :infinity) + }\r\n\r\n" ) if delete? do -- cgit v1.2.3 From 701815e64c35160d29e418724c29cbe2d8b4024d Mon Sep 17 00:00:00 2001 From: Hakaba Hitoyo Date: Tue, 10 Dec 2019 13:19:26 +0000 Subject: [ActivityPub] Configurable ActivityPub actor type --- lib/pleroma/user.ex | 4 ++++ lib/pleroma/web/activity_pub/activity_pub.ex | 2 ++ lib/pleroma/web/activity_pub/views/user_view.ex | 2 +- lib/pleroma/web/mastodon_api/controllers/account_controller.ex | 1 + lib/pleroma/web/mastodon_api/views/account_view.ex | 5 +++-- 5 files changed, 11 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e2afc6de8..694f1f110 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -127,6 +127,7 @@ defmodule Pleroma.User do field(:invisible, :boolean, default: false) field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) + field(:actor_type, :string, default: "Person") field(:also_known_as, {:array, :string}, default: []) embeds_one( @@ -346,6 +347,7 @@ defmodule Pleroma.User do :following_count, :discoverable, :invisible, + :actor_type, :also_known_as ] ) @@ -396,6 +398,7 @@ defmodule Pleroma.User do :raw_fields, :pleroma_settings_store, :discoverable, + :actor_type, :also_known_as ] ) @@ -438,6 +441,7 @@ defmodule Pleroma.User do :discoverable, :hide_followers_count, :hide_follows_count, + :actor_type, :also_known_as ] ) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1e2cc2e2b..6571102a9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1217,6 +1217,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do data = Transmogrifier.maybe_fix_user_object(data) discoverable = data["discoverable"] || false invisible = data["invisible"] || false + actor_type = data["type"] || "Person" user_data = %{ ap_id: data["id"], @@ -1232,6 +1233,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do follower_address: data["followers"], following_address: data["following"], bio: data["summary"], + actor_type: actor_type, also_known_as: Map.get(data, "alsoKnownAs", []) } diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index cf08045c9..9059aa634 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -91,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do %{ "id" => user.ap_id, - "type" => "Person", + "type" => user.actor_type, "following" => "#{user.ap_id}/following", "followers" => "#{user.ap_id}/followers", "inbox" => "#{user.ap_id}/inbox", diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index d19029cb5..38d14256f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -188,6 +188,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, Map.merge(user.pleroma_settings_store, value)} end) |> add_if_present(params, "default_scope", :default_scope) + |> add_if_present(params, "actor_type", :actor_type) emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 546cc0ed5..a5420f480 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do 0 end - bot = (user.source_data["type"] || "Person") in ["Application", "Service"] + bot = user.actor_type in ["Application", "Service"] emojis = (user.source_data["tag"] || []) @@ -137,7 +137,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do sensitive: false, fields: user.raw_fields, pleroma: %{ - discoverable: user.discoverable + discoverable: user.discoverable, + actor_type: user.actor_type } }, -- cgit v1.2.3