Merge pull request 'Federate user profile background' (#682) from Oneric/akkoma:background-federation into develop
Reviewed-on: #682
This commit is contained in:
commit
34c213f02f
10 changed files with 97 additions and 4 deletions
|
@ -11,11 +11,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- handling of GET /api/v1/preferences
|
- handling of GET /api/v1/preferences
|
||||||
- Akkoma API is now documented
|
- Akkoma API is now documented
|
||||||
- ability to auto-approve follow requests from users you are already following
|
- ability to auto-approve follow requests from users you are already following
|
||||||
|
- The SimplePolicy MRF can now strip user backgrounds from selected remote hosts
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- OTP builds are now built on erlang OTP26
|
- OTP builds are now built on erlang OTP26
|
||||||
- The base Phoenix framework is now updated to 1.7
|
- The base Phoenix framework is now updated to 1.7
|
||||||
- An `outbox` field has been added to actor profiles to comply with AP spec
|
- An `outbox` field has been added to actor profiles to comply with AP spec
|
||||||
|
- User profile backgrounds do now federate with other Akkoma instances and Sharkey
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- Documentation issue in which a non-existing nginx file was referenced
|
- Documentation issue in which a non-existing nginx file was referenced
|
||||||
|
|
|
@ -144,6 +144,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `report_removal`: List of instances to reject reports from and the reason for doing so.
|
* `report_removal`: List of instances to reject reports from and the reason for doing so.
|
||||||
* `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
|
* `avatar_removal`: List of instances to strip avatars from and the reason for doing so.
|
||||||
* `banner_removal`: List of instances to strip banners from and the reason for doing so.
|
* `banner_removal`: List of instances to strip banners from and the reason for doing so.
|
||||||
|
* `background_removal`: List of instances to strip user backgrounds from and the reason for doing so.
|
||||||
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
|
* `reject_deletes`: List of instances to reject deletions from and the reason for doing so.
|
||||||
|
|
||||||
#### :mrf_subchain
|
#### :mrf_subchain
|
||||||
|
|
|
@ -35,6 +35,7 @@ Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_si
|
||||||
* `media_removal`: Servers in this group will have media stripped from incoming messages.
|
* `media_removal`: Servers in this group will have media stripped from incoming messages.
|
||||||
* `avatar_removal`: Avatars from these servers will be stripped from incoming messages.
|
* `avatar_removal`: Avatars from these servers will be stripped from incoming messages.
|
||||||
* `banner_removal`: Banner images from these servers will be stripped from incoming messages.
|
* `banner_removal`: Banner images from these servers will be stripped from incoming messages.
|
||||||
|
* `background_removal`: User background images from these servers will be stripped from incoming messages.
|
||||||
* `report_removal`: Servers in this group will have their reports (flags) rejected.
|
* `report_removal`: Servers in this group will have their reports (flags) rejected.
|
||||||
* `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
|
* `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields.
|
||||||
* `reject_deletes`: Deletion requests will be rejected from these servers.
|
* `reject_deletes`: Deletion requests will be rejected from these servers.
|
||||||
|
|
|
@ -382,6 +382,10 @@ def banner_url(user, options \\ []) do
|
||||||
do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options)
|
do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def background_url(user) do
|
||||||
|
do_optional_url(user.background, nil, no_default: true)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_optional_url(field, default, options) do
|
defp do_optional_url(field, default, options) do
|
||||||
case field do
|
case field do
|
||||||
%{"url" => [%{"href" => href} | _]} when is_binary(href) ->
|
%{"url" => [%{"href" => href} | _]} when is_binary(href) ->
|
||||||
|
@ -466,6 +470,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
:avatar,
|
:avatar,
|
||||||
:ap_enabled,
|
:ap_enabled,
|
||||||
:banner,
|
:banner,
|
||||||
|
:background,
|
||||||
:is_locked,
|
:is_locked,
|
||||||
:last_refreshed_at,
|
:last_refreshed_at,
|
||||||
:uri,
|
:uri,
|
||||||
|
|
|
@ -1603,6 +1603,7 @@ defp object_to_user_data(data, additional) do
|
||||||
uri: get_actor_url(data["url"]),
|
uri: get_actor_url(data["url"]),
|
||||||
ap_enabled: true,
|
ap_enabled: true,
|
||||||
banner: normalize_image(data["image"]),
|
banner: normalize_image(data["image"]),
|
||||||
|
background: normalize_image(data["backgroundUrl"]),
|
||||||
fields: fields,
|
fields: fields,
|
||||||
emoji: emojis,
|
emoji: emojis,
|
||||||
is_locked: is_locked,
|
is_locked: is_locked,
|
||||||
|
|
|
@ -178,6 +178,23 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image
|
||||||
|
|
||||||
defp check_banner_removal(_actor_info, object), do: {:ok, object}
|
defp check_banner_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
|
defp check_background_removal(
|
||||||
|
%{host: actor_host} = _actor_info,
|
||||||
|
%{"backgroundUrl" => _bg} = object
|
||||||
|
) do
|
||||||
|
background_removal =
|
||||||
|
instance_list(:background_removal)
|
||||||
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
|
if MRF.subdomain_match?(background_removal, actor_host) do
|
||||||
|
{:ok, Map.delete(object, "backgroundUrl")}
|
||||||
|
else
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_background_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do
|
defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do
|
||||||
rest
|
rest
|
||||||
|> String.split(",", parts: 2, trim: true)
|
|> String.split(",", parts: 2, trim: true)
|
||||||
|
@ -283,7 +300,8 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
|
||||||
with {:ok, _} <- check_accept(actor_info),
|
with {:ok, _} <- check_accept(actor_info),
|
||||||
{:ok, _} <- check_reject(actor_info),
|
{:ok, _} <- check_reject(actor_info),
|
||||||
{:ok, object} <- check_avatar_removal(actor_info, object),
|
{:ok, object} <- check_avatar_removal(actor_info, object),
|
||||||
{:ok, object} <- check_banner_removal(actor_info, object) do
|
{:ok, object} <- check_banner_removal(actor_info, object),
|
||||||
|
{:ok, object} <- check_background_removal(actor_info, object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
{:reject, nil} -> {:reject, "[SimplePolicy]"}
|
||||||
|
@ -447,6 +465,11 @@ def config_description do
|
||||||
key: :banner_removal,
|
key: :banner_removal,
|
||||||
description: "List of instances to strip banners from and the reason for doing so"
|
description: "List of instances to strip banners from and the reason for doing so"
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :background_removal,
|
||||||
|
description:
|
||||||
|
"List of instances to strip user backgrounds from and the reason for doing so"
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :reject_deletes,
|
key: :reject_deletes,
|
||||||
description: "List of instances to reject deletions from and the reason for doing so"
|
description: "List of instances to reject deletions from and the reason for doing so"
|
||||||
|
|
|
@ -112,6 +112,8 @@ def render("user.json", %{user: user}) do
|
||||||
}
|
}
|
||||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||||
|
# Yes, the key is named ...Url eventhough it is a whole 'Image' object
|
||||||
|
|> Map.merge(maybe_insert_image("backgroundUrl", User.background_url(user)))
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -287,7 +289,12 @@ def collection(collection, iri, page, show_items \\ true, total \\ nil) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_make_image(func, key, user) do
|
defp maybe_make_image(func, key, user) do
|
||||||
if image = func.(user, no_default: true) do
|
image = func.(user, no_default: true)
|
||||||
|
maybe_insert_image(key, image)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_insert_image(key, image) do
|
||||||
|
if image do
|
||||||
%{
|
%{
|
||||||
key => %{
|
key => %{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
||||||
accept: [],
|
accept: [],
|
||||||
avatar_removal: [],
|
avatar_removal: [],
|
||||||
banner_removal: [],
|
banner_removal: [],
|
||||||
|
background_removal: [],
|
||||||
reject_deletes: []
|
reject_deletes: []
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -618,6 +619,42 @@ test "match with wildcard domain" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "when :background_removal" do
|
||||||
|
test "is empty" do
|
||||||
|
clear_config([:mrf_simple, :background_removal], [])
|
||||||
|
|
||||||
|
remote_user = build_remote_user()
|
||||||
|
|
||||||
|
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "is not empty but it doesn't have a matching host" do
|
||||||
|
clear_config([:mrf_simple, :background_removal], [{"non.matching.remote", ""}])
|
||||||
|
|
||||||
|
remote_user = build_remote_user()
|
||||||
|
|
||||||
|
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "has a matching host" do
|
||||||
|
clear_config([:mrf_simple, :background_removal], [{"remote.instance", ""}])
|
||||||
|
|
||||||
|
remote_user = build_remote_user()
|
||||||
|
{:ok, filtered} = SimplePolicy.filter(remote_user)
|
||||||
|
|
||||||
|
refute filtered["backgroundUrl"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "match with wildcard domain" do
|
||||||
|
clear_config([:mrf_simple, :background_removal], [{"*.remote.instance", ""}])
|
||||||
|
|
||||||
|
remote_user = build_remote_user()
|
||||||
|
{:ok, filtered} = SimplePolicy.filter(remote_user)
|
||||||
|
|
||||||
|
refute filtered["backgroundUrl"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "when :reject_deletes is empty" do
|
describe "when :reject_deletes is empty" do
|
||||||
setup do: clear_config([:mrf_simple, :reject_deletes], [])
|
setup do: clear_config([:mrf_simple, :reject_deletes], [])
|
||||||
|
|
||||||
|
@ -701,6 +738,10 @@ defp build_remote_user do
|
||||||
"url" => "http://example.com/image.jpg",
|
"url" => "http://example.com/image.jpg",
|
||||||
"type" => "Image"
|
"type" => "Image"
|
||||||
},
|
},
|
||||||
|
"backgroundUrl" => %{
|
||||||
|
"url" => "http://example.com/background.jpg",
|
||||||
|
"type" => "Image"
|
||||||
|
},
|
||||||
"type" => "Person"
|
"type" => "Person"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -155,7 +155,13 @@ test "it blocks but does not unfollow if the relevant setting is set", %{
|
||||||
user = insert(:user, local: false)
|
user = insert(:user, local: false)
|
||||||
|
|
||||||
{:ok, update_data, []} =
|
{:ok, update_data, []} =
|
||||||
Builder.update(user, %{"id" => user.ap_id, "type" => "Person", "name" => "new name!"})
|
Builder.update(user, %{
|
||||||
|
"id" => user.ap_id,
|
||||||
|
"type" => "Person",
|
||||||
|
"name" => "new name!",
|
||||||
|
"icon" => %{"type" => "Image", "url" => "https://example.org/icon.png"},
|
||||||
|
"backgroundUrl" => %{"type" => "Image", "url" => "https://example.org/bg.jxl"}
|
||||||
|
})
|
||||||
|
|
||||||
{:ok, update, _meta} = ActivityPub.persist(update_data, local: true)
|
{:ok, update, _meta} = ActivityPub.persist(update_data, local: true)
|
||||||
|
|
||||||
|
@ -165,7 +171,10 @@ test "it blocks but does not unfollow if the relevant setting is set", %{
|
||||||
test "it updates the user", %{user: user, update: update} do
|
test "it updates the user", %{user: user, update: update} do
|
||||||
{:ok, _, _} = SideEffects.handle(update)
|
{:ok, _, _} = SideEffects.handle(update)
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert user.name == "new name!"
|
assert user.name == "new name!"
|
||||||
|
assert [%{"href" => "https://example.org/icon.png"}] = user.avatar["url"]
|
||||||
|
assert [%{"href" => "https://example.org/bg.jxl"}] = user.background["url"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it uses a given changeset to update", %{user: user, update: update} do
|
test "it uses a given changeset to update", %{user: user, update: update} do
|
||||||
|
|
|
@ -58,16 +58,19 @@ test "Does not add an avatar image if the user hasn't set one" do
|
||||||
result = UserView.render("user.json", %{user: user})
|
result = UserView.render("user.json", %{user: user})
|
||||||
refute result["icon"]
|
refute result["icon"]
|
||||||
refute result["image"]
|
refute result["image"]
|
||||||
|
refute result["backgroundUrl"]
|
||||||
|
|
||||||
user =
|
user =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
avatar: %{"url" => [%{"href" => "https://someurl"}]},
|
avatar: %{"url" => [%{"href" => "https://someurl"}]},
|
||||||
banner: %{"url" => [%{"href" => "https://somebanner"}]}
|
banner: %{"url" => [%{"href" => "https://somebanner"}]},
|
||||||
|
background: %{"url" => [%{"href" => "https://somebackground"}]}
|
||||||
)
|
)
|
||||||
|
|
||||||
result = UserView.render("user.json", %{user: user})
|
result = UserView.render("user.json", %{user: user})
|
||||||
assert result["icon"]["url"] == "https://someurl"
|
assert result["icon"]["url"] == "https://someurl"
|
||||||
assert result["image"]["url"] == "https://somebanner"
|
assert result["image"]["url"] == "https://somebanner"
|
||||||
|
assert result["backgroundUrl"]["url"] == "https://somebackground"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "renders an invisible user with the invisible property set to true" do
|
test "renders an invisible user with the invisible property set to true" do
|
||||||
|
|
Loading…
Reference in a new issue