Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2019-10-05 18:22:05 +01:00
commit f1c77f9682
31 changed files with 385 additions and 214 deletions

View file

@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### Added
- Refreshing poll results for remote polls - Refreshing poll results for remote polls
- Job queue stats to the healthcheck page
- Admin API: Add ability to require password reset - Admin API: Add ability to require password reset
- Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition) - Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition)
- Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items - Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items
@ -23,6 +24,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed ### Fixed
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- Added `:instance, extended_nickname_format` setting to the default config
## [1.1.0] - 2019-??-?? ## [1.1.0] - 2019-??-??
### Security ### Security
@ -74,6 +77,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ActivityPub: Deactivated user deletion - ActivityPub: Deactivated user deletion
- ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user - ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user
- MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled - MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled
- Mastodon API: Blocks are now treated consistently between the Streaming API and the Timeline APIs
### Added ### Added
- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically. - Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically.

View file

@ -279,7 +279,8 @@
max_remote_account_fields: 20, max_remote_account_fields: 20,
account_field_name_length: 512, account_field_name_length: 512,
account_field_value_length: 2048, account_field_value_length: 2048,
external_user_synchronization: true external_user_synchronization: true,
extended_nickname_format: false
config :pleroma, :markup, config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because # XXX - unfortunately, inline images must be enabled by default right now, because

View file

@ -317,7 +317,8 @@ See [Admin-API](admin_api.md)
"active": 0, # active processes "active": 0, # active processes
"idle": 0, # idle processes "idle": 0, # idle processes
"memory_used": 0.00, # Memory used "memory_used": 0.00, # Memory used
"healthy": true # Instance state "healthy": true, # Instance state
"job_queue_stats": {} # Job queue stats
} }
``` ```
@ -391,7 +392,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
### Update a file in a custom emoji pack ### Update a file in a custom emoji pack
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params:
* if the `action` is `add`, adds an emoji named `shortcode` to the pack `pack_name`, * if the `action` is `add`, adds an emoji named `shortcode` to the pack `pack_name`,
that means that the emoji file needs to be uploaded with the request that means that the emoji file needs to be uploaded with the request
(thus requiring it to be a multipart request) and be named `file`. (thus requiring it to be a multipart request) and be named `file`.
@ -408,7 +409,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
### Updates (replaces) pack metadata ### Updates (replaces) pack metadata
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params:
* `new_data`: new metadata to replace the old one * `new_data`: new metadata to replace the old one
* Response: JSON, updated "metadata" section of the pack and 200 status or 400 if there was a * Response: JSON, updated "metadata" section of the pack and 200 status or 400 if there was a
problem with the new metadata (the error is specified in the "error" part of the response JSON) problem with the new metadata (the error is specified in the "error" part of the response JSON)
@ -417,7 +418,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
### Requests the instance to download the pack from another instance ### Requests the instance to download the pack from another instance
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params:
* `instance_address`: the address of the instance to download from * `instance_address`: the address of the instance to download from
* `pack_name`: the pack to download from that instance * `pack_name`: the pack to download from that instance
* Response: JSON, "ok" and 200 status if the pack was downloaded, or 500 if there were * Response: JSON, "ok" and 200 status if the pack was downloaded, or 500 if there were

View file

@ -42,6 +42,7 @@ def start(_type, _args) do
hackney_pool_children() ++ hackney_pool_children() ++
[ [
Pleroma.Stats, Pleroma.Stats,
Pleroma.JobQueueMonitor,
{Oban, Pleroma.Config.get(Oban)} {Oban, Pleroma.Config.get(Oban)}
] ++ ] ++
task_children(@env) ++ task_children(@env) ++

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Healthcheck do
active: 0, active: 0,
idle: 0, idle: 0,
memory_used: 0, memory_used: 0,
job_queue_stats: nil,
healthy: true healthy: true
@type t :: %__MODULE__{ @type t :: %__MODULE__{
@ -21,6 +22,7 @@ defmodule Pleroma.Healthcheck do
active: non_neg_integer(), active: non_neg_integer(),
idle: non_neg_integer(), idle: non_neg_integer(),
memory_used: number(), memory_used: number(),
job_queue_stats: map(),
healthy: boolean() healthy: boolean()
} }
@ -30,6 +32,7 @@ def system_info do
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
} }
|> assign_db_info() |> assign_db_info()
|> assign_job_queue_stats()
|> check_health() |> check_health()
end end
@ -55,6 +58,11 @@ defp assign_db_info(healthcheck) do
Map.merge(healthcheck, db_info) Map.merge(healthcheck, db_info)
end end
defp assign_job_queue_stats(healthcheck) do
stats = Pleroma.JobQueueMonitor.stats()
Map.put(healthcheck, :job_queue_stats, stats)
end
@spec check_health(Healthcheck.t()) :: Healthcheck.t() @spec check_health(Healthcheck.t()) :: Healthcheck.t()
def check_health(%{pool_size: pool_size, active: active} = check) def check_health(%{pool_size: pool_size, active: active} = check)
when active >= pool_size do when active >= pool_size do

View file

@ -0,0 +1,78 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.JobQueueMonitor do
use GenServer
@initial_state %{workers: %{}, queues: %{}, processed_jobs: 0}
@queue %{processed_jobs: 0, success: 0, failure: 0}
@operation %{processed_jobs: 0, success: 0, failure: 0}
def start_link(_) do
GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
end
@impl true
def init(state) do
:telemetry.attach("oban-monitor-failure", [:oban, :failure], &handle_event/4, nil)
:telemetry.attach("oban-monitor-success", [:oban, :success], &handle_event/4, nil)
{:ok, state}
end
def stats do
GenServer.call(__MODULE__, :stats)
end
def handle_event([:oban, status], %{duration: duration}, meta, _) do
GenServer.cast(__MODULE__, {:process_event, status, duration, meta})
end
@impl true
def handle_call(:stats, _from, state) do
{:reply, state, state}
end
@impl true
def handle_cast({:process_event, status, duration, meta}, state) do
state =
state
|> Map.update!(:workers, fn workers ->
workers
|> Map.put_new(meta.worker, %{})
|> Map.update!(meta.worker, &update_worker(&1, status, meta, duration))
end)
|> Map.update!(:queues, fn workers ->
workers
|> Map.put_new(meta.queue, @queue)
|> Map.update!(meta.queue, &update_queue(&1, status, meta, duration))
end)
|> Map.update!(:processed_jobs, &(&1 + 1))
{:noreply, state}
end
defp update_worker(worker, status, meta, duration) do
worker
|> Map.put_new(meta.args["op"], @operation)
|> Map.update!(meta.args["op"], &update_op(&1, status, meta, duration))
end
defp update_op(op, :enqueue, _meta, _duration) do
op
|> Map.update!(:enqueued, &(&1 + 1))
end
defp update_op(op, status, _meta, _duration) do
op
|> Map.update!(:processed_jobs, &(&1 + 1))
|> Map.update!(status, &(&1 + 1))
end
defp update_queue(queue, status, _meta, _duration) do
queue
|> Map.update!(:processed_jobs, &(&1 + 1))
|> Map.update!(status, &(&1 + 1))
end
end

View file

@ -583,7 +583,7 @@ def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) -> is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
restrict_to_local == false -> restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
get_cached_by_nickname(nickname_or_id) get_cached_by_nickname(nickname_or_id)
restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) -> restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->

View file

@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Streamer alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker alias Pleroma.Workers.BackgroundWorker
@ -291,8 +292,8 @@ def reject(%{to: to, actor: actor, object: object} = params) do
end end
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
# only accept false as false value
local = !(params[:local] == false) local = !(params[:local] == false)
activity_id = params[:activity_id]
with data <- %{ with data <- %{
"to" => to, "to" => to,
@ -301,6 +302,7 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
"actor" => actor, "actor" => actor,
"object" => object "object" => object
}, },
data <- Utils.maybe_put(data, "id", activity_id),
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}

View file

@ -82,38 +82,6 @@ def track_object_fetch(conn, object_id) do
conn conn
end end
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
{page, _} = Integer.parse(page)
conn
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes, page: page})
else
{:public?, false} ->
{:error, :not_found}
end
end
def object_likes(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
{_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes})
else
{:public?, false} ->
{:error, :not_found}
end
end
def activity(conn, %{"uuid" => uuid}) do def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid), with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id), %Activity{} = activity <- Activity.normalize(ap_id),

View file

@ -621,7 +621,8 @@ def handle_incoming(
to: data["to"] || [], to: data["to"] || [],
cc: data["cc"] || [], cc: data["cc"] || [],
object: object, object: object,
actor: actor_id actor: actor_id,
activity_id: data["id"]
}) })
else else
e -> e ->

View file

@ -251,16 +251,6 @@ def get_existing_like(actor, %{data: %{"id" => id}}) do
|> Repo.one() |> Repo.one()
end end
@doc """
Returns like activities targeting an object
"""
def get_object_likes(%{data: %{"id" => id}}) do
id
|> Activity.Queries.by_object_id()
|> Activity.Queries.by_type("Like")
|> Repo.all()
end
@spec make_like_data(User.t(), map(), String.t()) :: map() @spec make_like_data(User.t(), map(), String.t()) :: map()
def make_like_data( def make_like_data(
%User{ap_id: ap_id} = actor, %User{ap_id: ap_id} = actor,
@ -461,14 +451,16 @@ def make_announce_data(
""" """
def make_unannounce_data( def make_unannounce_data(
%User{ap_id: ap_id} = user, %User{ap_id: ap_id} = user,
%Activity{data: %{"context" => context}} = activity, %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id activity_id
) do ) do
object = Object.normalize(object)
%{ %{
"type" => "Undo", "type" => "Undo",
"actor" => ap_id, "actor" => ap_id,
"object" => activity.data, "object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]], "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()], "cc" => [Pleroma.Constants.as_public()],
"context" => context "context" => context
} }
@ -477,14 +469,16 @@ def make_unannounce_data(
def make_unlike_data( def make_unlike_data(
%User{ap_id: ap_id} = user, %User{ap_id: ap_id} = user,
%Activity{data: %{"context" => context}} = activity, %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id activity_id
) do ) do
object = Object.normalize(object)
%{ %{
"type" => "Undo", "type" => "Undo",
"actor" => ap_id, "actor" => ap_id,
"object" => activity.data, "object" => activity.data,
"to" => [user.follower_address, activity.data["actor"]], "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()], "cc" => [Pleroma.Constants.as_public()],
"context" => context "context" => context
} }
@ -745,6 +739,6 @@ def get_existing_votes(actor, %{data: %{"id" => id}}) do
|> Repo.all() |> Repo.all()
end end
defp maybe_put(map, _key, nil), do: map def maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value) def maybe_put(map, key, value), do: Map.put(map, key, value)
end end

View file

@ -37,40 +37,4 @@ def render("object.json", %{object: %Activity{} = activity}) do
Map.merge(base, additional) Map.merge(base, additional)
end end
def render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) do
collection(likes, "#{ap_id}/likes", page)
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
end
def render("likes.json", %{ap_id: ap_id, likes: likes}) do
%{
"id" => "#{ap_id}/likes",
"type" => "OrderedCollection",
"totalItems" => length(likes),
"first" => collection(likes, "#{ap_id}/likes", 1)
}
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
end
def collection(collection, iri, page) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
items = Enum.map(items, fn object -> Transmogrifier.prepare_object(object.data) end)
total = length(collection)
map = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => total,
"orderedItems" => items
}
if offset + length(items) < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
else
map
end
end
end end

View file

@ -16,6 +16,8 @@ defmodule Pleroma.Web.CommonAPI do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils import Pleroma.Web.CommonAPI.Utils
require Pleroma.Constants
def follow(follower, followed) do def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@ -271,7 +273,7 @@ def update(user) do
ActivityPub.update(%{ ActivityPub.update(%{
local: true, local: true,
to: [user.follower_address], to: [Pleroma.Constants.as_public(), user.follower_address],
cc: [], cc: [],
actor: user.ap_id, actor: user.ap_id,
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})

View file

@ -25,40 +25,44 @@ def render("show.json", %{
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity) mastodon_type = Activity.mastodon_notification_type(activity)
response = %{ with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do
id: to_string(notification.id), response = %{
type: mastodon_type, id: to_string(notification.id),
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), type: mastodon_type,
account: AccountView.render("show.json", %{user: actor, for: user}), created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
pleroma: %{ account: account,
is_seen: notification.seen pleroma: %{
is_seen: notification.seen
}
} }
}
case mastodon_type do case mastodon_type do
"mention" -> "mention" ->
response response
|> Map.merge(%{ |> Map.merge(%{
status: StatusView.render("show.json", %{activity: activity, for: user}) status: StatusView.render("show.json", %{activity: activity, for: user})
}) })
"favourite" -> "favourite" ->
response response
|> Map.merge(%{ |> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user}) status: StatusView.render("show.json", %{activity: parent_activity, for: user})
}) })
"reblog" -> "reblog" ->
response response
|> Map.merge(%{ |> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user}) status: StatusView.render("show.json", %{activity: parent_activity, for: user})
}) })
"follow" -> "follow" ->
response response
_ -> _ ->
nil nil
end
else
_ -> nil
end end
end end
end end

View file

@ -460,7 +460,7 @@ defp do_create_authorization(
end end
# Special case: Local MastodonFE # Special case: Local MastodonFE
defp redirect_uri(%Plug.Conn{} = conn, "."), do: mastodon_api_url(conn, :login) defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri

View file

@ -580,7 +580,6 @@ defmodule Pleroma.Web.Router do
pipe_through(:ostatus) pipe_through(:ostatus)
get("/users/:nickname/outbox", ActivityPubController, :outbox) get("/users/:nickname/outbox", ActivityPubController, :outbox)
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
end end
pipeline :activitypub_client do pipeline :activitypub_client do

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Ping do defmodule Pleroma.Web.Streamer.Ping do
use GenServer use GenServer
require Logger require Logger

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.State do defmodule Pleroma.Web.Streamer.State do
use GenServer use GenServer
require Logger require Logger

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.StreamerSocket do defmodule Pleroma.Web.Streamer.StreamerSocket do
defstruct transport_pid: nil, user: nil defstruct transport_pid: nil, user: nil

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Supervisor do defmodule Pleroma.Web.Streamer.Supervisor do
use Supervisor use Supervisor

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Streamer.Worker do defmodule Pleroma.Web.Streamer.Worker do
use GenServer use GenServer
@ -128,11 +132,14 @@ defp should_send?(%User{} = user, %Activity{} = item) do
blocks = user.info.blocks || [] blocks = user.info.blocks || []
mutes = user.info.mutes || [] mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || [] reblog_mutes = user.info.muted_reblogs || []
recipient_blocks = MapSet.new(blocks ++ mutes)
recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
with parent when not is_nil(parent) <- Object.normalize(item), with parent when not is_nil(parent) <- Object.normalize(item),
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), 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?([blocks, mutes], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor), %{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]), %{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
@ -194,11 +201,8 @@ def push_to_socket(topics, topic, item) do
# Get the current user so we have up-to-date blocks etc. # Get the current user so we have up-to-date blocks etc.
if socket_user do if socket_user do
user = User.get_cached_by_ap_id(socket_user.ap_id) user = User.get_cached_by_ap_id(socket_user.ap_id)
blocks = user.info.blocks || []
mutes = user.info.mutes || []
with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), if should_send?(user, item) do
true <- thread_containment(item, user) do
send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) send(transport_pid, {:text, StreamerView.render("update.json", item, user)})
end end
else else

View file

@ -9,7 +9,14 @@ defmodule Pleroma.HealthcheckTest do
test "system_info/0" do test "system_info/0" do
result = Healthcheck.system_info() |> Map.from_struct() result = Healthcheck.system_info() |> Map.from_struct()
assert Map.keys(result) == [:active, :healthy, :idle, :memory_used, :pool_size] assert Map.keys(result) == [
:active,
:healthy,
:idle,
:job_queue_stats,
:memory_used,
:pool_size
]
end end
describe "check_health/1" do describe "check_health/1" do

View file

@ -0,0 +1,70 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.JobQueueMonitorTest do
use ExUnit.Case, async: true
alias Pleroma.JobQueueMonitor
@success {:process_event, :success, 1337,
%{
args: %{"op" => "refresh_subscriptions"},
attempt: 1,
id: 339,
max_attempts: 5,
queue: "federator_outgoing",
worker: "Pleroma.Workers.SubscriberWorker"
}}
@failure {:process_event, :failure, 22_521_134,
%{
args: %{"op" => "force_password_reset", "user_id" => "9nJG6n6Nbu7tj9GJX6"},
attempt: 1,
error: %RuntimeError{message: "oops"},
id: 345,
kind: :exception,
max_attempts: 1,
queue: "background",
stack: [
{Pleroma.Workers.BackgroundWorker, :perform, 2,
[file: 'lib/pleroma/workers/background_worker.ex', line: 31]},
{Oban.Queue.Executor, :safe_call, 1,
[file: 'lib/oban/queue/executor.ex', line: 42]},
{:timer, :tc, 3, [file: 'timer.erl', line: 197]},
{Oban.Queue.Executor, :call, 2, [file: 'lib/oban/queue/executor.ex', line: 23]},
{Task.Supervised, :invoke_mfa, 2, [file: 'lib/task/supervised.ex', line: 90]},
{:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}
],
worker: "Pleroma.Workers.BackgroundWorker"
}}
test "stats/0" do
assert %{processed_jobs: _, queues: _, workers: _} = JobQueueMonitor.stats()
end
test "handle_cast/2" do
state = %{workers: %{}, queues: %{}, processed_jobs: 0}
assert {:noreply, state} = JobQueueMonitor.handle_cast(@success, state)
assert {:noreply, state} = JobQueueMonitor.handle_cast(@failure, state)
assert {:noreply, state} = JobQueueMonitor.handle_cast(@success, state)
assert {:noreply, state} = JobQueueMonitor.handle_cast(@failure, state)
assert state == %{
processed_jobs: 4,
queues: %{
"background" => %{failure: 2, processed_jobs: 2, success: 0},
"federator_outgoing" => %{failure: 0, processed_jobs: 2, success: 2}
},
workers: %{
"Pleroma.Workers.BackgroundWorker" => %{
"force_password_reset" => %{failure: 2, processed_jobs: 2, success: 0}
},
"Pleroma.Workers.SubscriberWorker" => %{
"refresh_subscriptions" => %{failure: 0, processed_jobs: 2, success: 2}
}
}
}
end
end

View file

@ -1725,4 +1725,61 @@ test "update_info/2" do
assert %{info: %{hide_follows: true}} = Repo.get(User, user.id) assert %{info: %{hide_follows: true}} = Repo.get(User, user.id)
assert {:ok, %{info: %{hide_follows: true}}} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}") assert {:ok, %{info: %{hide_follows: true}}} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
end end
describe "get_cached_by_nickname_or_id" do
setup do
limit_to_local_content = Pleroma.Config.get([:instance, :limit_to_local_content])
local_user = insert(:user)
remote_user = insert(:user, nickname: "nickname@example.com", local: false)
on_exit(fn ->
Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local_content)
end)
[local_user: local_user, remote_user: remote_user]
end
test "allows getting remote users by id no matter what :limit_to_local_content is set to", %{
remote_user: remote_user
} do
Pleroma.Config.put([:instance, :limit_to_local_content], false)
assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id)
Pleroma.Config.put([:instance, :limit_to_local_content], true)
assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id)
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
assert %User{} = User.get_cached_by_nickname_or_id(remote_user.id)
end
test "disallows getting remote users by nickname without authentication when :limit_to_local_content is set to :unauthenticated",
%{remote_user: remote_user} do
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
assert nil == User.get_cached_by_nickname_or_id(remote_user.nickname)
end
test "allows getting remote users by nickname with authentication when :limit_to_local_content is set to :unauthenticated",
%{remote_user: remote_user, local_user: local_user} do
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
assert %User{} = User.get_cached_by_nickname_or_id(remote_user.nickname, for: local_user)
end
test "disallows getting remote users by nickname when :limit_to_local_content is set to true",
%{remote_user: remote_user} do
Pleroma.Config.put([:instance, :limit_to_local_content], true)
assert nil == User.get_cached_by_nickname_or_id(remote_user.nickname)
end
test "allows getting local users by nickname no matter what :limit_to_local_content is set to",
%{local_user: local_user} do
Pleroma.Config.put([:instance, :limit_to_local_content], false)
assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname)
Pleroma.Config.put([:instance, :limit_to_local_content], true)
assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname)
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname)
end
end
end end

View file

@ -225,69 +225,6 @@ test "cached purged after object deletion", %{conn: conn} do
end end
end end
describe "/object/:uuid/likes" do
setup do
like = insert(:like_activity)
like_object_ap_id = Object.normalize(like).data["id"]
uuid =
like_object_ap_id
|> String.split("/")
|> List.last()
[id: like.data["id"], uuid: uuid]
end
test "it returns the like activities in a collection", %{conn: conn, id: id, uuid: uuid} do
result =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/objects/#{uuid}/likes")
|> json_response(200)
assert List.first(result["first"]["orderedItems"])["id"] == id
assert result["type"] == "OrderedCollection"
assert result["totalItems"] == 1
refute result["first"]["next"]
end
test "it does not crash when page number is exceeded total pages", %{conn: conn, uuid: uuid} do
result =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/objects/#{uuid}/likes?page=2")
|> json_response(200)
assert result["type"] == "OrderedCollectionPage"
assert result["totalItems"] == 1
refute result["next"]
assert Enum.empty?(result["orderedItems"])
end
test "it contains the next key when likes count is more than 10", %{conn: conn} do
note = insert(:note_activity)
insert_list(11, :like_activity, note_activity: note)
uuid =
note
|> Object.normalize()
|> Map.get(:data)
|> Map.get("id")
|> String.split("/")
|> List.last()
result =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/objects/#{uuid}/likes?page=1")
|> json_response(200)
assert result["totalItems"] == 11
assert length(result["orderedItems"]) == 10
assert result["next"]
end
end
describe "/activities/:uuid" do describe "/activities/:uuid" do
test "it returns a json representation of the activity", %{conn: conn} do test "it returns a json representation of the activity", %{conn: conn} do
activity = insert(:note_activity) activity = insert(:note_activity)

View file

@ -811,10 +811,11 @@ test "unliking a previously liked object" do
{:ok, like_activity, object} = ActivityPub.like(user, object) {:ok, like_activity, object} = ActivityPub.like(user, object)
assert object.data["like_count"] == 1 assert object.data["like_count"] == 1
{:ok, _, _, object} = ActivityPub.unlike(user, object) {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
assert object.data["like_count"] == 0 assert object.data["like_count"] == 0
assert Activity.get_by_id(like_activity.id) == nil assert Activity.get_by_id(like_activity.id) == nil
assert note_activity.actor in unlike_activity.recipients
end end
end end
@ -890,7 +891,7 @@ test "unannouncing a previously announced object" do
assert unannounce_activity.data["to"] == [ assert unannounce_activity.data["to"] == [
User.ap_followers(user), User.ap_followers(user),
announce_activity.data["actor"] object.data["actor"]
] ]
assert unannounce_activity.data["type"] == "Undo" assert unannounce_activity.data["type"] == "Undo"

View file

@ -546,6 +546,8 @@ test "it works for incoming update activities" do
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
assert data["id"] == update_data["id"]
user = User.get_cached_by_ap_id(data["actor"]) user = User.get_cached_by_ap_id(data["actor"])
assert user.name == "gargle" assert user.name == "gargle"

View file

@ -106,11 +106,13 @@ test "returns data for unlike activity" do
user = insert(:user) user = insert(:user)
like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"}) like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"})
object = Object.normalize(like_activity.data["object"])
assert Utils.make_unlike_data(user, like_activity, nil) == %{ assert Utils.make_unlike_data(user, like_activity, nil) == %{
"type" => "Undo", "type" => "Undo",
"actor" => user.ap_id, "actor" => user.ap_id,
"object" => like_activity.data, "object" => like_activity.data,
"to" => [user.follower_address, like_activity.data["actor"]], "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()], "cc" => [Pleroma.Constants.as_public()],
"context" => like_activity.data["context"] "context" => like_activity.data["context"]
} }
@ -119,7 +121,7 @@ test "returns data for unlike activity" do
"type" => "Undo", "type" => "Undo",
"actor" => user.ap_id, "actor" => user.ap_id,
"object" => like_activity.data, "object" => like_activity.data,
"to" => [user.follower_address, like_activity.data["actor"]], "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()], "cc" => [Pleroma.Constants.as_public()],
"context" => like_activity.data["context"], "context" => like_activity.data["context"],
"id" => "9mJEZK0tky1w2xD2vY" "id" => "9mJEZK0tky1w2xD2vY"

View file

@ -14,6 +14,8 @@ defmodule Pleroma.Web.CommonAPITest do
import Pleroma.Factory import Pleroma.Factory
require Pleroma.Constants
clear_config([:instance, :safe_dm_mentions]) clear_config([:instance, :safe_dm_mentions])
clear_config([:instance, :limit]) clear_config([:instance, :limit])
clear_config([:instance, :max_pinned_statuses]) clear_config([:instance, :max_pinned_statuses])
@ -96,11 +98,13 @@ test "it adds emoji in the object" do
test "it adds emoji when updating profiles" do test "it adds emoji when updating profiles" do
user = insert(:user, %{name: ":firefox:"}) user = insert(:user, %{name: ":firefox:"})
CommonAPI.update(user) {:ok, activity} = CommonAPI.update(user)
user = User.get_cached_by_ap_id(user.ap_id) user = User.get_cached_by_ap_id(user.ap_id)
[firefox] = user.info.source_data["tag"] [firefox] = user.info.source_data["tag"]
assert firefox["name"] == ":firefox:" assert firefox["name"] == ":firefox:"
assert Pleroma.Constants.as_public() in activity.recipients
end end
describe "posting" do describe "posting" do

View file

@ -100,5 +100,11 @@ test "Follow notification" do
NotificationView.render("index.json", %{notifications: [notification], for: followed}) NotificationView.render("index.json", %{notifications: [notification], for: followed})
assert [expected] == result assert [expected] == result
User.perform(:delete, follower)
notification = Notification |> Repo.one() |> Repo.preload(:activity)
assert [] ==
NotificationView.render("index.json", %{notifications: [notification], for: followed})
end end
end end

View file

@ -233,30 +233,68 @@ test "it sends message if recipients invalid and thread containment is enabled b
end end
end end
test "it doesn't send to blocked users" do describe "blocks" do
user = insert(:user) test "it doesn't send messages involving blocked users" do
blocked_user = insert(:user) user = insert(:user)
{:ok, user} = User.block(user, blocked_user) blocked_user = insert(:user)
{:ok, user} = User.block(user, blocked_user)
task = task =
Task.async(fn -> Task.async(fn ->
refute_receive {:text, _}, 1_000 refute_receive {:text, _}, 1_000
end) end)
fake_socket = %StreamerSocket{ fake_socket = %StreamerSocket{
transport_pid: task.pid, transport_pid: task.pid,
user: user user: user
} }
{:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"})
topics = %{ topics = %{
"public" => [fake_socket] "public" => [fake_socket]
} }
Worker.push_to_socket(topics, "public", activity) Worker.push_to_socket(topics, "public", activity)
Task.await(task) Task.await(task)
end
test "it doesn't send messages transitively involving blocked users" do
blocker = insert(:user)
blockee = insert(:user)
friend = insert(:user)
task =
Task.async(fn ->
refute_receive {:text, _}, 1_000
end)
fake_socket = %StreamerSocket{
transport_pid: task.pid,
user: blocker
}
topics = %{
"public" => [fake_socket]
}
{:ok, blocker} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
Worker.push_to_socket(topics, "public", activity_one)
{:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
Worker.push_to_socket(topics, "public", activity_two)
{:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
Worker.push_to_socket(topics, "public", activity_three)
Task.await(task)
end
end end
test "it doesn't send unwanted DMs to list" do test "it doesn't send unwanted DMs to list" do