commit: a5dbf8deadaf14cec4b28e4a6a018619758fcaf9
parent 8428a1bed38d461e45dddde19758020ab98044e8
Author: Lain Soykaf <lain@lain.com>
Date: Wed, 27 Aug 2025 15:27:37 +0400
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into lazarus
Diffstat:
15 files changed, 122 insertions(+), 12 deletions(-)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
@@ -290,7 +290,7 @@ stop_review_app:
amd64:
stage: release
image:
- name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
+ name: hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
only: &release-only
- stable@pleroma/pleroma
- develop@pleroma/pleroma
@@ -317,7 +317,7 @@ amd64:
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
DEBIAN_FRONTEND: noninteractive
before_script: &before-release
- - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git
+ - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git build-essential
- echo "import Config" > config/prod.secret.exs
- mix local.hex --force
- mix local.rebar --force
@@ -333,7 +333,7 @@ amd64-musl:
artifacts: *release-artifacts
only: *release-only
image:
- name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
+ name: hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
tags:
- amd64
cache: *release-cache
@@ -377,7 +377,7 @@ arm64:
tags:
- arm
image:
- name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
+ name: hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
cache: *release-cache
variables: *release-variables
before_script: *before-release
@@ -390,7 +390,7 @@ arm64-musl:
tags:
- arm
image:
- name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
+ name: hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
diff --git a/Dockerfile b/Dockerfile
@@ -1,10 +1,10 @@
# https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir
-ARG ELIXIR_VER=1.14.5
-ARG ERLANG_VER=25.3.2.14
+ARG ELIXIR_VER=1.17.3
+ARG ERLANG_VER=26.2.5.6
ARG ALPINE_VER=3.17.9
-FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
+FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} AS build
COPY . .
@@ -15,6 +15,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
echo "import Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
+ mix deps.clean --all &&\
mix deps.get --only prod &&\
mkdir release &&\
mix release --path release
diff --git a/changelog.d/admin-self-revocation.security b/changelog.d/admin-self-revocation.security
@@ -0,0 +1 @@
+Admin API: Fixed self-revocation vulnerability where admins could accidentally revoke their own admin status via the single-user permission endpoint
+\ No newline at end of file
diff --git a/changelog.d/dockerfile-versions.change b/changelog.d/dockerfile-versions.change
@@ -0,0 +1 @@
+Update Dockerfile to use Elixir 1.17.3, Erlang 26.2.5.6, and Alpine 3.17.9 to match CI release builds
+\ No newline at end of file
diff --git a/changelog.d/moderation-log-unknown-actions.fix b/changelog.d/moderation-log-unknown-actions.fix
@@ -0,0 +1 @@
+Fix ModerationLog FunctionClauseError for unknown actions
+\ No newline at end of file
diff --git a/changelog.d/repost-repeat-filtering-3391.add b/changelog.d/repost-repeat-filtering-3391.add
@@ -0,0 +1 @@
+Add only_reblogs parameter to account statuses API for filtering to show only reblogs/reposts
+\ No newline at end of file
diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
@@ -88,6 +88,7 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
- `only_media`: include only statuses with media attached
- `with_muted`: include statuses/reactions from muted accounts
- `exclude_reblogs`: exclude reblogs
+- `only_reblogs`: include only reblogs
- `exclude_replies`: exclude replies
- `exclude_visibilities`: exclude visibilities
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
@@ -575,6 +575,12 @@ defmodule Pleroma.ModerationLog do
"@#{actor_nickname} requested account backup for @#{user_nickname}"
end
+ def get_log_entry_message(%ModerationLog{data: data}) do
+ actor_name = get_in(data, ["actor", "nickname"]) || "unknown"
+ action = data["action"] || "unknown"
+ "@#{actor_name} performed action #{action}"
+ end
+
defp nicknames_to_string(nicknames) do
nicknames
|> Enum.map(&"@#{&1}")
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1065,6 +1065,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
end
+ defp restrict_reblogs(query, %{only_reblogs: true}) do
+ from(activity in query, where: fragment("?->>'type' = 'Announce'", activity.data))
+ end
+
defp restrict_reblogs(query, _), do: query
defp restrict_muted(query, %{with_muted: true}), do: query
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -240,6 +240,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
render_error(conn, :not_found, "No such permission_group")
end
+ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
+ end
+
def right_delete(
%{assigns: %{user: admin}} = conn,
%{
@@ -265,10 +269,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
json(conn, fields)
end
- def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
- render_error(conn, :forbidden, "You can't revoke your own admin status.")
- end
-
@doc "Get a password reset token (base64 string) for given nickname"
def get_password_reset(conn, %{"nickname" => nickname}) do
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -143,6 +143,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
"Include statuses from muted accounts."
),
Operation.parameter(:exclude_reblogs, :query, BooleanLike.schema(), "Exclude reblogs"),
+ Operation.parameter(
+ :only_reblogs,
+ :query,
+ BooleanLike.schema(),
+ "Include only reblogs"
+ ),
Operation.parameter(:exclude_replies, :query, BooleanLike.schema(), "Exclude replies"),
Operation.parameter(
:exclude_visibilities,
diff --git a/test/pleroma/moderation_log_test.exs b/test/pleroma/moderation_log_test.exs
@@ -308,4 +308,37 @@ defmodule Pleroma.ModerationLogTest do
assert log.data["message"] == "@#{moderator.nickname} deleted status ##{note.id}"
end
end
+
+ describe "get_log_entry_message/1" do
+ setup do
+ moderator = insert(:user, is_moderator: true)
+ [moderator: moderator]
+ end
+
+ test "handles unknown action types gracefully", %{moderator: moderator} do
+ log_entry = %ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => moderator.nickname},
+ "action" => "unknown_action",
+ "some_data" => "test_value"
+ }
+ }
+
+ assert ModerationLog.get_log_entry_message(log_entry) =~ moderator.nickname
+ assert ModerationLog.get_log_entry_message(log_entry) =~ "unknown_action"
+ end
+
+ test "handles malformed log entries gracefully" do
+ log_entry = %ModerationLog{
+ data: %{
+ "action" => "force_password_reset"
+ # Missing "actor" and "subject" fields
+ }
+ }
+
+ message = ModerationLog.get_log_entry_message(log_entry)
+ assert is_binary(message)
+ assert message =~ "force_password_reset"
+ end
+ end
end
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -1270,6 +1270,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity == expected_activity
end
+ test "includes only reblogs on request" do
+ user = insert(:user)
+ {:ok, _} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
+ {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
+
+ [activity] = ActivityPub.fetch_user_activities(user, nil, %{only_reblogs: true})
+
+ assert activity == expected_activity
+ end
+
describe "irreversible filters" do
setup do
user = insert(:user)
diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
@@ -321,6 +321,36 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{user_two.nickname}"
end
+
+ test "/:right DELETE, admin cannot revoke their own admin status (single)", %{
+ admin: admin,
+ conn: conn
+ } do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/#{admin.nickname}/permission_group/admin")
+
+ assert json_response(conn, 403) == %{"error" => "You can't revoke your own admin status."}
+ end
+
+ test "/:right DELETE, admin cannot revoke their own admin status (multiple)", %{
+ admin: admin,
+ conn: conn
+ } do
+ user = insert(:user, is_admin: true)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/permission_group/admin", %{
+ nicknames: [admin.nickname, user.nickname]
+ })
+
+ assert json_response(conn, 403) == %{
+ "error" => "You can't revoke your own admin/moderator status."
+ }
+ end
end
describe "/api/pleroma/admin/users/:nickname/password_reset" do
diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -469,6 +469,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
end
+ test "gets only a user's reblogs", %{user: user, conn: conn} do
+ {:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"})
+ {:ok, %{id: reblog_id}} = CommonAPI.repeat(post_id, user)
+
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=true")
+ assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
+
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_reblogs=1")
+ assert [%{"id" => ^reblog_id}] = json_response_and_validate_schema(conn, 200)
+ end
+
test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
{:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "#hashtag"})
{:ok, _post} = CommonAPI.post(user, %{status: "hashtag"})