Merge remote-tracking branch 'pleroma/develop' into merge-pleroma
This commit is contained in:
commit
fa4f0ae328
16 changed files with 352 additions and 8 deletions
|
@ -18,6 +18,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Removed
|
||||
- BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact)
|
||||
|
||||
## 2.5.3
|
||||
|
||||
### Security
|
||||
- Emoji pack loader sanitizes pack names
|
||||
- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories
|
||||
|
||||
## 2.5.2
|
||||
|
||||
### Security
|
||||
|
|
1
changelog.d/3801.fix
Normal file
1
changelog.d/3801.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Filter context activities using Visibility.visible_for_user?
|
1
changelog.d/attachment-type-check.fix
Normal file
1
changelog.d/attachment-type-check.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Restrict attachments to only uploaded files only
|
1
changelog.d/emoji-pack-sanitization.security
Normal file
1
changelog.d/emoji-pack-sanitization.security
Normal file
|
@ -0,0 +1 @@
|
|||
Emoji pack loader sanitizes pack names
|
1
changelog.d/otp_perms.security
Normal file
1
changelog.d/otp_perms.security
Normal file
|
@ -0,0 +1 @@
|
|||
- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories
|
|
@ -209,12 +209,20 @@ def run(["gen" | rest]) do
|
|||
config_dir = Path.dirname(config_path)
|
||||
psql_dir = Path.dirname(psql_path)
|
||||
|
||||
# Note: Distros requiring group read (0o750) on those directories should
|
||||
# pre-create the directories.
|
||||
[config_dir, psql_dir, static_dir, uploads_dir]
|
||||
|> Enum.reject(&File.exists?/1)
|
||||
|> Enum.map(&File.mkdir_p!/1)
|
||||
|> Enum.each(fn dir ->
|
||||
File.mkdir_p!(dir)
|
||||
File.chmod!(dir, 0o700)
|
||||
end)
|
||||
|
||||
shell_info("Writing config to #{config_path}.")
|
||||
|
||||
# Sadly no fchmod(2) equivalent in Elixir…
|
||||
File.touch!(config_path)
|
||||
File.chmod!(config_path, 0o640)
|
||||
File.write(config_path, result_config)
|
||||
shell_info("Writing the postgres script to #{psql_path}.")
|
||||
File.write(psql_path, result_psql)
|
||||
|
@ -233,8 +241,7 @@ def run(["gen" | rest]) do
|
|||
else
|
||||
shell_error(
|
||||
"The task would have overwritten the following files:\n" <>
|
||||
(Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <>
|
||||
"Rerun with `--force` to overwrite them."
|
||||
Enum.map_join(will_overwrite, &"- #{&1}\n") <> "Rerun with `--force` to overwrite them."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,20 @@ def load(config, opts) do
|
|||
|
||||
with_runtime_config =
|
||||
if File.exists?(config_path) do
|
||||
# <https://git.pleroma.social/pleroma/pleroma/-/issues/3135>
|
||||
%File.Stat{mode: mode} = File.lstat!(config_path)
|
||||
|
||||
if Bitwise.band(mode, 0o007) > 0 do
|
||||
raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}"
|
||||
end
|
||||
|
||||
if Bitwise.band(mode, 0o020) > 0 do
|
||||
raise "Configuration at #{config_path} has group-wise write permissions, execute the following: chmod g-w #{config_path}"
|
||||
end
|
||||
|
||||
# Note: Elixir doesn't provides a getuid(2)
|
||||
# so cannot forbid group-read only when config is owned by us
|
||||
|
||||
runtime_config = Config.Reader.read!(config_path)
|
||||
|
||||
with_defaults
|
||||
|
|
|
@ -96,4 +96,6 @@ defmodule Pleroma.Constants do
|
|||
const(mime_regex,
|
||||
do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
|
||||
)
|
||||
|
||||
const(upload_object_types, do: ["Document", "Image"])
|
||||
end
|
||||
|
|
|
@ -285,6 +285,7 @@ def update_metadata(name, data) do
|
|||
|
||||
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()}
|
||||
def load_pack(name) do
|
||||
name = Path.basename(name)
|
||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
||||
|
||||
with {:ok, _} <- File.stat(pack_file),
|
||||
|
|
|
@ -491,6 +491,7 @@ def fetch_activities_for_context_query(context, opts) do
|
|||
|> maybe_preload_objects(opts)
|
||||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_unauthenticated(opts[:user])
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|
@ -1298,6 +1299,27 @@ defp restrict_join_state(query, %{state: state}) when is_binary(state) do
|
|||
|
||||
defp restrict_join_state(query, _), do: query
|
||||
|
||||
defp restrict_unauthenticated(query, nil) do
|
||||
local = Config.restrict_unauthenticated_access?(:activities, :local)
|
||||
remote = Config.restrict_unauthenticated_access?(:activities, :remote)
|
||||
|
||||
cond do
|
||||
local and remote ->
|
||||
from(activity in query, where: false)
|
||||
|
||||
local ->
|
||||
from(activity in query, where: activity.local == false)
|
||||
|
||||
remote ->
|
||||
from(activity in query, where: activity.local == true)
|
||||
|
||||
true ->
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
defp restrict_unauthenticated(query, _), do: query
|
||||
|
||||
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
|
||||
|
||||
defp exclude_poll_votes(query, _) do
|
||||
|
|
|
@ -68,7 +68,12 @@ def attachments_from_ids_descs(ids, descs_str) do
|
|||
end
|
||||
|
||||
defp get_attachment(media_id) do
|
||||
Repo.get(Object, media_id)
|
||||
with %Object{data: data} = object <- Repo.get(Object, media_id),
|
||||
%{"type" => type} when type in Pleroma.Constants.upload_object_types() <- data do
|
||||
object
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())}
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -8,7 +8,7 @@ def project do
|
|||
app: :pleroma,
|
||||
name: "Rebased",
|
||||
compat_name: "Pleroma",
|
||||
version: version("2.5.52"),
|
||||
version: version("2.5.53"),
|
||||
elixir: "~> 1.11",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:phoenix] ++ Mix.compilers(),
|
||||
|
|
|
@ -17,6 +17,8 @@ test "loads release defaults config and warns about non-existent runtime config"
|
|||
end
|
||||
|
||||
test "merged runtime config" do
|
||||
assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640)
|
||||
|
||||
merged =
|
||||
ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs")
|
||||
|
||||
|
@ -25,6 +27,8 @@ test "merged runtime config" do
|
|||
end
|
||||
|
||||
test "merged exported config" do
|
||||
assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640)
|
||||
|
||||
ExUnit.CaptureIO.capture_io(fn ->
|
||||
merged =
|
||||
ReleaseRuntimeProvider.load([],
|
||||
|
@ -37,6 +41,9 @@ test "merged exported config" do
|
|||
end
|
||||
|
||||
test "runtime config is merged with exported config" do
|
||||
assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640)
|
||||
assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640)
|
||||
|
||||
merged =
|
||||
ReleaseRuntimeProvider.load([],
|
||||
config_path: "test/fixtures/config/temp.secret.exs",
|
||||
|
|
|
@ -90,4 +90,8 @@ test "add emoji file", %{pack: pack} do
|
|||
|
||||
assert updated_pack.files_count == 1
|
||||
end
|
||||
|
||||
test "load_pack/1 ignores path traversal in a forged pack name", %{pack: pack} do
|
||||
assert {:ok, ^pack} = Pack.load_pack("../../../../../dump_pack")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -592,7 +592,7 @@ test "returns [] when attachment ids is empty" do
|
|||
end
|
||||
|
||||
test "returns list attachments with desc" do
|
||||
object = insert(:note)
|
||||
object = insert(:attachment)
|
||||
desc = Jason.encode!(%{object.id => "test-desc"})
|
||||
|
||||
assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [
|
||||
|
@ -603,7 +603,7 @@ test "returns list attachments with desc" do
|
|||
|
||||
describe "attachments_from_ids/1" do
|
||||
test "returns attachments with descs" do
|
||||
object = insert(:note)
|
||||
object = insert(:attachment)
|
||||
desc = Jason.encode!(%{object.id => "test-desc"})
|
||||
|
||||
assert Utils.attachments_from_ids(%{
|
||||
|
@ -615,13 +615,18 @@ test "returns attachments with descs" do
|
|||
end
|
||||
|
||||
test "returns attachments without descs" do
|
||||
object = insert(:note)
|
||||
object = insert(:attachment)
|
||||
assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data]
|
||||
end
|
||||
|
||||
test "returns [] when not pass media_ids" do
|
||||
assert Utils.attachments_from_ids(%{}) == []
|
||||
end
|
||||
|
||||
test "checks that the object is of upload type" do
|
||||
object = insert(:note)
|
||||
assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "maybe_add_list_data/3" do
|
||||
|
|
|
@ -793,6 +793,49 @@ defp local_and_remote_activities do
|
|||
{:ok, local: local, remote: remote}
|
||||
end
|
||||
|
||||
defp local_and_remote_context_activities do
|
||||
local_user_1 = insert(:user)
|
||||
local_user_2 = insert(:user)
|
||||
remote_user = insert(:user, local: false)
|
||||
|
||||
{:ok, %{id: id1, data: %{"context" => context}}} =
|
||||
CommonAPI.post(local_user_1, %{status: "post"})
|
||||
|
||||
{:ok, %{id: id2} = post} =
|
||||
CommonAPI.post(local_user_2, %{status: "local reply", in_reply_to_status_id: id1})
|
||||
|
||||
params = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"actor" => remote_user.ap_id,
|
||||
"type" => "Create",
|
||||
"context" => context,
|
||||
"id" => "#{remote_user.ap_id}/activities/1",
|
||||
"inReplyTo" => post.data["id"],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "remote reply",
|
||||
"context" => context,
|
||||
"id" => "#{remote_user.ap_id}/objects/1",
|
||||
"attributedTo" => remote_user.ap_id,
|
||||
"to" => [
|
||||
local_user_1.ap_id,
|
||||
local_user_2.ap_id,
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
},
|
||||
"to" => [
|
||||
local_user_1.ap_id,
|
||||
local_user_2.ap_id,
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
}
|
||||
|
||||
{:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
|
||||
{:ok, remote_activity} = ObanHelpers.perform(job)
|
||||
|
||||
%{locals: [id1, id2], remote: remote_activity.id, context: context}
|
||||
end
|
||||
|
||||
describe "status with restrict unauthenticated activities for local and remote" do
|
||||
setup do: local_and_remote_activities()
|
||||
|
||||
|
@ -979,6 +1022,230 @@ test "if user is authenticated", %{local: local, remote: remote} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "getting status contexts restricted unauthenticated for local and remote" do
|
||||
setup do: local_and_remote_context_activities()
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
||||
|
||||
test "if user is unauthenticated", %{conn: conn, locals: [post_id, _]} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
assert json_response_and_validate_schema(res_conn, 200) == %{
|
||||
"ancestors" => [],
|
||||
"descendants" => []
|
||||
}
|
||||
end
|
||||
|
||||
test "if user is unauthenticated reply", %{conn: conn, locals: [_, reply_id]} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
assert json_response_and_validate_schema(res_conn, 200) == %{
|
||||
"ancestors" => [],
|
||||
"descendants" => []
|
||||
}
|
||||
end
|
||||
|
||||
test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
%{"ancestors" => [], "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert reply_id in descendant_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
%{"ancestors" => ancestors, "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
ancestor_ids =
|
||||
ancestors
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert post_id in ancestor_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting status contexts restricted unauthenticated for local" do
|
||||
setup do: local_and_remote_context_activities()
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], false)
|
||||
|
||||
test "if user is unauthenticated", %{
|
||||
conn: conn,
|
||||
locals: [post_id, reply_id],
|
||||
remote: remote_reply_id
|
||||
} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
%{"ancestors" => [], "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert reply_id not in descendant_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is unauthenticated reply", %{
|
||||
conn: conn,
|
||||
locals: [post_id, reply_id],
|
||||
remote: remote_reply_id
|
||||
} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
%{"ancestors" => ancestors, "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
ancestor_ids =
|
||||
ancestors
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert post_id not in ancestor_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
%{"ancestors" => [], "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert reply_id in descendant_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
%{"ancestors" => ancestors, "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
ancestor_ids =
|
||||
ancestors
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert post_id in ancestor_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting status contexts restricted unauthenticated for remote" do
|
||||
setup do: local_and_remote_context_activities()
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :local], false)
|
||||
|
||||
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
||||
|
||||
test "if user is unauthenticated", %{
|
||||
conn: conn,
|
||||
locals: [post_id, reply_id],
|
||||
remote: remote_reply_id
|
||||
} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
%{"ancestors" => [], "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert reply_id in descendant_ids
|
||||
assert remote_reply_id not in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is unauthenticated reply", %{
|
||||
conn: conn,
|
||||
locals: [post_id, reply_id],
|
||||
remote: remote_reply_id
|
||||
} do
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
%{"ancestors" => ancestors, "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
ancestor_ids =
|
||||
ancestors
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert post_id in ancestor_ids
|
||||
assert remote_reply_id not in descendant_ids
|
||||
end
|
||||
|
||||
test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
|
||||
|
||||
%{"ancestors" => [], "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
reply_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert reply_id in reply_ids
|
||||
assert remote_reply_id in reply_ids
|
||||
end
|
||||
|
||||
test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
|
||||
%{conn: conn} = oauth_access(["read"])
|
||||
res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
|
||||
|
||||
%{"ancestors" => ancestors, "descendants" => descendants} =
|
||||
json_response_and_validate_schema(res_conn, 200)
|
||||
|
||||
ancestor_ids =
|
||||
ancestors
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
descendant_ids =
|
||||
descendants
|
||||
|> Enum.map(& &1["id"])
|
||||
|
||||
assert post_id in ancestor_ids
|
||||
assert remote_reply_id in descendant_ids
|
||||
end
|
||||
end
|
||||
|
||||
describe "deleting a status" do
|
||||
test "when you created it" do
|
||||
%{user: author, conn: conn} = oauth_access(["write:statuses"])
|
||||
|
|
Loading…
Reference in a new issue