Federate user profile background #682

Merged
floatingghost merged 2 commits from Oneric/akkoma:background-federation into develop 2024-02-16 21:00:11 +00:00
10 changed files with 97 additions and 4 deletions

View file

@ -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
- Akkoma API is now documented
- 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
- OTP builds are now built on erlang OTP26
- The base Phoenix framework is now updated to 1.7
- 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
- Documentation issue in which a non-existing nginx file was referenced

View file

@ -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.
* `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.
* `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.
#### :mrf_subchain

View file

@ -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.
* `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.
* `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.
* `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.

View file

@ -382,6 +382,10 @@ def banner_url(user, options \\ []) do
do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options)
end
def background_url(user) do
do_optional_url(user.background, nil, no_default: true)
end
defp do_optional_url(field, default, options) do
case field do
%{"url" => [%{"href" => href} | _]} when is_binary(href) ->
@ -466,6 +470,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
:avatar,
:ap_enabled,
:banner,
:background,
:is_locked,
:last_refreshed_at,
:uri,

View file

@ -1603,6 +1603,7 @@ defp object_to_user_data(data, additional) do
uri: get_actor_url(data["url"]),
ap_enabled: true,
banner: normalize_image(data["image"]),
background: normalize_image(data["backgroundUrl"]),
fields: fields,
emoji: emojis,
is_locked: is_locked,

View file

@ -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_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
rest
|> String.split(",", parts: 2, trim: true)
@ -283,7 +300,8 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
with {:ok, _} <- check_accept(actor_info),
{:ok, _} <- check_reject(actor_info),
{: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}
else
{:reject, nil} -> {:reject, "[SimplePolicy]"}
@ -447,6 +465,11 @@ def config_description do
key: :banner_removal,
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,
description: "List of instances to reject deletions from and the reason for doing so"

View file

@ -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.banner_url/2, "image", user))
# Yes, the key is named ...Url eventhough it is a whole 'Image' object

hmmm, on closer inspection maybe we can do something about this

we've had situations like this before when dealing with quoteUrl - where we use a more coherent internal key and rewrite when other people throw us weird names like this

i guess you've used this for compatibility, which makes it hard.

i'll let it slide for now since we use a decent database key for it, but something to bear in mind

hmmm, on closer inspection maybe we can do something about this we've had situations like this before when [dealing with quoteUrl](https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/activity_pub/transmogrifier.ex#L151) - where we use a more coherent internal key and rewrite when other people throw us weird names like this i guess you've used this for compatibility, which makes it hard. i'll let it slide for now since we use a decent database key for it, but something to bear in mind

yeah, if we use a different name in the Activity Pub view, Sharkey instances won’t be able to read backgrounds from Akkoma.

Since afaik Sharkey is the only other implementation, I guess if you want I could ask on the Sharkey tracker whether they’re willing to deal with a property rename and its added work

But also:

a more coherent internal key

iinm unlike most other objects users don’t get their whole AP object shoved into the database, so user.background already is the only internal representation and backgroundUrl is only used when processing/creating AP objects from/for remote instances

Creating and parsing the field with a more sensible name and rewriting its name for both in- and outgoing messages in transmogrifier.ex imho doesn’t make much sense.


(force-pushed to rebase and resolve the changelog conflict)

yeah, if we use a different name in the Activity Pub view, Sharkey instances won’t be able to read backgrounds from Akkoma. Since afaik Sharkey is the only other implementation, I guess if you want I could ask on the Sharkey tracker whether they’re willing to deal with a property rename and its added work But also: > a more coherent internal key iinm unlike most other objects users don’t get their whole AP object shoved into the database, so `user.background` already is the only *internal* representation and `backgroundUrl` is only used when processing/creating AP objects from/for remote instances Creating and parsing the field with a more sensible name and rewriting its name for both in- and outgoing messages in `transmogrifier.ex` imho doesn’t make much sense. -------- *(force-pushed to rebase and resolve the changelog conflict)*
|> Map.merge(maybe_insert_image("backgroundUrl", User.background_url(user)))
|> Map.merge(Utils.make_json_ld_header())
end
@ -287,7 +289,12 @@ def collection(collection, iri, page, show_items \\ true, total \\ nil) do
end
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 => %{
"type" => "Image",

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
accept: [],
avatar_removal: [],
banner_removal: [],
background_removal: [],
reject_deletes: []
)
@ -618,6 +619,42 @@ test "match with wildcard domain" do
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
setup do: clear_config([:mrf_simple, :reject_deletes], [])
@ -701,6 +738,10 @@ defp build_remote_user do
"url" => "http://example.com/image.jpg",
"type" => "Image"
},
"backgroundUrl" => %{
"url" => "http://example.com/background.jpg",
"type" => "Image"
},
"type" => "Person"
}
end

View file

@ -155,7 +155,13 @@ test "it blocks but does not unfollow if the relevant setting is set", %{
user = insert(:user, local: false)
{: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)
@ -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
{:ok, _, _} = SideEffects.handle(update)
user = User.get_by_id(user.id)
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
test "it uses a given changeset to update", %{user: user, update: update} do

View file

@ -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})
refute result["icon"]
refute result["image"]
refute result["backgroundUrl"]
user =
insert(:user,
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})
assert result["icon"]["url"] == "https://someurl"
assert result["image"]["url"] == "https://somebanner"
assert result["backgroundUrl"]["url"] == "https://somebackground"
end
test "renders an invisible user with the invisible property set to true" do