From 7ee528925c2275ab811b2cb1e763c556cb095a54 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 4 Mar 2021 13:06:12 +0300 Subject: [PATCH 01/18] expanding WebFinger --- config/config.exs | 2 + config/test.exs | 2 + lib/pleroma/http.ex | 9 +- lib/pleroma/web/activity_pub/activity_pub.ex | 53 +++++----- lib/pleroma/web/web_finger.ex | 43 ++++---- .../tesla_mock/framatube.org_host_meta | 2 +- .../tesla_mock/status.alpicola.com_host_meta | 2 +- test/fixtures/webfinger/masto-host-meta.xml | 4 + test/fixtures/webfinger/masto-user.json | 92 +++++++++++++++++ test/fixtures/webfinger/masto-webfinger.json | 23 +++++ test/fixtures/webfinger/pleroma-host-meta.xml | 1 + test/fixtures/webfinger/pleroma-user.json | 58 +++++++++++ .../fixtures/webfinger/pleroma-webfinger.json | 27 +++++ test/pleroma/user_test.exs | 98 +++++++++++++++++++ .../remote_follow_controller_test.exs | 2 +- .../web_finger/web_finger_controller_test.exs | 29 ++++++ test/pleroma/web/web_finger_test.exs | 2 +- test/support/http_request_mock.ex | 20 ++-- 18 files changed, 407 insertions(+), 62 deletions(-) create mode 100644 test/fixtures/webfinger/masto-host-meta.xml create mode 100644 test/fixtures/webfinger/masto-user.json create mode 100644 test/fixtures/webfinger/masto-webfinger.json create mode 100644 test/fixtures/webfinger/pleroma-host-meta.xml create mode 100644 test/fixtures/webfinger/pleroma-user.json create mode 100644 test/fixtures/webfinger/pleroma-webfinger.json diff --git a/config/config.exs b/config/config.exs index 4381068ac..1fb4fc0e2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -851,6 +851,8 @@ config :pleroma, ConcurrentLimiter, [ {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]} ] +config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/test.exs b/config/test.exs index 87396a88d..55a4991ea 100644 --- a/config/test.exs +++ b/config/test.exs @@ -129,6 +129,8 @@ config :pleroma, :pipeline, config :pleroma, :cachex, provider: Pleroma.CachexMock +config :pleroma, Pleroma.Web.WebFinger, update_nickname_on_user_fetch: false + config :pleroma, :side_effects, ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, logger: Pleroma.LoggerMock diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex index 07b3ab0ae..c90793d68 100644 --- a/lib/pleroma/http.ex +++ b/lib/pleroma/http.ex @@ -106,5 +106,12 @@ defmodule Pleroma.HTTP do [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] end - defp adapter_middlewares(_), do: [] + defp adapter_middlewares(_) do + if Pleroma.Config.get(:env) == :test do + # Emulate redirects in test env, which are handled by adapters in other environments + [Tesla.Middleware.FollowRedirects] + else + [] + end + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d0051d1cb..e54711af3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1403,7 +1403,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image() defp normalize_image(_), do: nil - defp object_to_user_data(data) do + defp object_to_user_data(data, additional) do fields = data |> Map.get("attachment", []) @@ -1435,18 +1435,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do public_key = if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do data["publicKey"]["publicKeyPem"] - else - nil end shared_inbox = if is_map(data["endpoints"]) && is_binary(data["endpoints"]["sharedInbox"]) do data["endpoints"]["sharedInbox"] - else - nil end - user_data = %{ + # if WebFinger request was already done, we probably have acct, otherwise + # we request WebFinger here + nickname = additional[:nickname_from_acct] || generate_nickname(data) + + %{ ap_id: data["id"], uri: get_actor_url(data["url"]), ap_enabled: true, @@ -1468,21 +1468,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do inbox: data["inbox"], shared_inbox: shared_inbox, accepts_chat_messages: accepts_chat_messages, - pinned_objects: pinned_objects + pinned_objects: pinned_objects, + nickname: nickname } + end - # nickname can be nil because of virtual actors - if data["preferredUsername"] do - Map.put( - user_data, - :nickname, - "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}" - ) + defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do + generated = "#{username}@#{URI.parse(data["id"]).host}" + + if Config.get([WebFinger, :update_nickname_on_user_fetch]) do + case WebFinger.finger(generated) do + {:ok, %{"subject" => "acct:" <> acct}} -> acct + _ -> generated + end else - Map.put(user_data, :nickname, nil) + generated end end + # nickname can be nil because of virtual actors + defp generate_nickname(_), do: nil + def fetch_follow_information_for_user(user) do with {:ok, following_data} <- Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), @@ -1554,17 +1560,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp collection_private(_data), do: {:ok, true} - def user_data_from_user_object(data) do + def user_data_from_user_object(data, additional \\ []) do with {:ok, data} <- MRF.filter(data) do - {:ok, object_to_user_data(data)} + {:ok, object_to_user_data(data, additional)} else e -> {:error, e} end end - def fetch_and_prepare_user_from_ap_id(ap_id) do + def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), - {:ok, data} <- user_data_from_user_object(data) do + {:ok, data} <- user_data_from_user_object(data, additional) do {:ok, maybe_update_follow_information(data)} else # If this has been deleted, only log a debug and not an error @@ -1641,13 +1647,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def make_user_from_ap_id(ap_id) do + def make_user_from_ap_id(ap_id, additional \\ []) do user = User.get_cached_by_ap_id(ap_id) if user && !User.ap_enabled?(user) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else - with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do + with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end) if user do @@ -1667,8 +1673,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def make_user_from_nickname(nickname) do - with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do - make_user_from_ap_id(ap_id) + with {:ok, %{"ap_id" => ap_id, "subject" => "acct:" <> acct}} when not is_nil(ap_id) <- + WebFinger.finger(nickname) do + make_user_from_ap_id(ap_id, nickname_from_acct: acct) else _e -> {:error, "No AP id in WebFinger"} end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index 21b10e654..0d72d2c62 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -32,7 +32,13 @@ defmodule Pleroma.Web.WebFinger do def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do host = Pleroma.Web.Endpoint.host() - regex = ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@#{host}/ + + regex = + if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do + ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/ + else + ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@#{host}/ + end with %{"username" => username} <- Regex.named_captures(regex, resource), %User{} = user <- User.get_cached_by_nickname(username) do @@ -65,8 +71,10 @@ defmodule Pleroma.Web.WebFinger do def represent_user(user, "JSON") do {:ok, user} = User.ensure_keys_present(user) + domain = Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host() + %{ - "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", + "subject" => "acct:#{user.nickname}@#{domain}", "aliases" => gather_aliases(user), "links" => gather_links(user) } @@ -150,17 +158,15 @@ defmodule Pleroma.Web.WebFinger do end def find_lrdd_template(domain) do - with {:ok, %{status: status, body: body}} when status in 200..299 <- - HTTP.get("http://#{domain}/.well-known/host-meta") do + # WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1 + meta_url = "https://#{domain}/.well-known/host-meta" + + with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do get_template_from_xml(body) else - _ -> - with {:ok, %{body: body, status: status}} when status in 200..299 <- - HTTP.get("https://#{domain}/.well-known/host-meta") do - get_template_from_xml(body) - else - e -> {:error, "Can't find LRDD template: #{inspect(e)}"} - end + error -> + Logger.warn("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}") + {:error, :lrdd_not_found} end end @@ -174,7 +180,7 @@ defmodule Pleroma.Web.WebFinger do end end - defp get_address_from_domain(_, _), do: nil + defp get_address_from_domain(_, _), do: {:error, :webfinger_no_domain} @spec finger(String.t()) :: {:ok, map()} | {:error, any()} def finger(account) do @@ -191,13 +197,11 @@ defmodule Pleroma.Web.WebFinger do encoded_account = URI.encode("acct:#{account}") with address when is_binary(address) <- get_address_from_domain(domain, encoded_account), - response <- + {:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <- HTTP.get( address, [{"accept", "application/xrd+xml,application/jrd+json"}] - ), - {:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <- - response do + ) do case List.keyfind(headers, "content-type", 0) do {_, content_type} -> case Plug.Conn.Utils.media_type(content_type) do @@ -215,10 +219,9 @@ defmodule Pleroma.Web.WebFinger do {:error, {:content_type, nil}} end else - e -> - Logger.debug(fn -> "Couldn't finger #{account}" end) - Logger.debug(fn -> inspect(e) end) - {:error, e} + error -> + Logger.debug("Couldn't finger #{account}: #{inspect(error)}") + error end end end diff --git a/test/fixtures/tesla_mock/framatube.org_host_meta b/test/fixtures/tesla_mock/framatube.org_host_meta index 91516ff6d..02e25bd64 100644 --- a/test/fixtures/tesla_mock/framatube.org_host_meta +++ b/test/fixtures/tesla_mock/framatube.org_host_meta @@ -1,2 +1,2 @@ -framatube.orgResource Descriptor +framatube.orgResource Descriptor diff --git a/test/fixtures/tesla_mock/status.alpicola.com_host_meta b/test/fixtures/tesla_mock/status.alpicola.com_host_meta index 6948c30ea..78155f644 100644 --- a/test/fixtures/tesla_mock/status.alpicola.com_host_meta +++ b/test/fixtures/tesla_mock/status.alpicola.com_host_meta @@ -1,2 +1,2 @@ -status.alpicola.comResource Descriptor \ No newline at end of file +status.alpicola.comResource Descriptor diff --git a/test/fixtures/webfinger/masto-host-meta.xml b/test/fixtures/webfinger/masto-host-meta.xml new file mode 100644 index 000000000..f432a27c3 --- /dev/null +++ b/test/fixtures/webfinger/masto-host-meta.xml @@ -0,0 +1,4 @@ + + + + diff --git a/test/fixtures/webfinger/masto-user.json b/test/fixtures/webfinger/masto-user.json new file mode 100644 index 000000000..1702de011 --- /dev/null +++ b/test/fixtures/webfinger/masto-user.json @@ -0,0 +1,92 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "toot": "http://joinmastodon.org/ns#", + "featured": { + "@id": "toot:featured", + "@type": "@id" + }, + "featuredTags": { + "@id": "toot:featuredTags", + "@type": "@id" + }, + "alsoKnownAs": { + "@id": "as:alsoKnownAs", + "@type": "@id" + }, + "movedTo": { + "@id": "as:movedTo", + "@type": "@id" + }, + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "IdentityProof": "toot:IdentityProof", + "discoverable": "toot:discoverable", + "Device": "toot:Device", + "Ed25519Signature": "toot:Ed25519Signature", + "Ed25519Key": "toot:Ed25519Key", + "Curve25519Key": "toot:Curve25519Key", + "EncryptedMessage": "toot:EncryptedMessage", + "publicKeyBase64": "toot:publicKeyBase64", + "deviceId": "toot:deviceId", + "claim": { + "@type": "@id", + "@id": "toot:claim" + }, + "fingerprintKey": { + "@type": "@id", + "@id": "toot:fingerprintKey" + }, + "identityKey": { + "@type": "@id", + "@id": "toot:identityKey" + }, + "devices": { + "@type": "@id", + "@id": "toot:devices" + }, + "messageFranking": "toot:messageFranking", + "messageType": "toot:messageType", + "cipherText": "toot:cipherText", + "suspended": "toot:suspended", + "focalPoint": { + "@container": "@list", + "@id": "toot:focalPoint" + } + } + ], + "id": "https://{{domain}}/users/{{nickname}}", + "type": "Person", + "following": "https://{{domain}}/users/{{nickname}}/following", + "followers": "https://{{domain}}/users/{{nickname}}/followers", + "inbox": "https://{{domain}}/users/{{nickname}}/inbox", + "outbox": "https://{{domain}}/users/{{nickname}}/outbox", + "featured": "https://{{domain}}/users/{{nickname}}/collections/featured", + "featuredTags": "https://{{domain}}/users/{{nickname}}/collections/tags", + "preferredUsername": "{{nickname}}", + "name": "Name Name", + "summary": "

Summary

", + "url": "https://{{domain}}/@{{nickname}}", + "manuallyApprovesFollowers": false, + "discoverable": false, + "devices": "https://{{domain}}/users/{{nickname}}/collections/devices", + "publicKey": { + "id": "https://{{domain}}/users/{{nickname}}#main-key", + "owner": "https://{{domain}}/users/{{nickname}}", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwDujxmxoYHs64MyVB3L\nG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ+3Zb6CI8zOO+nM+Q2llrVRYjZa4ZFnOLvM\nTq/Kf+Zf5wy2aCRer88gX+MsJOAtItSi412y0a/rKOuFaDYLOLeTkRvmGLgZWbsr\nZJOp+YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBO\nfOHggSt1+eAIKGIsCmINEMzs1mG9D75xKtC/sM8GfbvBclQcBstGkHAEj1VHPW0c\nh6Bok5/QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Q\nawIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "tag": [], + "attachment": [], + "endpoints": { + "sharedInbox": "https://{{domain}}/inbox" + }, + "icon": { + "type": "Image", + "mediaType": "image/jpeg", + "url": "https://s3.wasabisys.com/merp/accounts/avatars/000/000/001/original/6fdd3eee632af247.jpg" + } +} diff --git a/test/fixtures/webfinger/masto-webfinger.json b/test/fixtures/webfinger/masto-webfinger.json new file mode 100644 index 000000000..561be3fff --- /dev/null +++ b/test/fixtures/webfinger/masto-webfinger.json @@ -0,0 +1,23 @@ +{ + "subject": "acct:{{nickname}}@{{domain}}", + "aliases": [ + "https://{{subdomain}}/@{{nickname}}", + "https://{{subdomain}}/users/{{nickname}}" + ], + "links": [ + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://{{subdomain}}/@{{nickname}}" + }, + { + "rel": "self", + "type": "application/activity+json", + "href": "https://{{subdomain}}/users/{{nickname}}" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "https://{{subdomain}}/authorize_interaction?uri={uri}" + } + ] +} diff --git a/test/fixtures/webfinger/pleroma-host-meta.xml b/test/fixtures/webfinger/pleroma-host-meta.xml new file mode 100644 index 000000000..88c274a1a --- /dev/null +++ b/test/fixtures/webfinger/pleroma-host-meta.xml @@ -0,0 +1 @@ + diff --git a/test/fixtures/webfinger/pleroma-user.json b/test/fixtures/webfinger/pleroma-user.json new file mode 100644 index 000000000..b822db46c --- /dev/null +++ b/test/fixtures/webfinger/pleroma-user.json @@ -0,0 +1,58 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://{{domain}}/schemas/litepub-0.1.jsonld", + { + "@language": "und" + } + ], + "alsoKnownAs": [], + "attachment": [], + "capabilities": { + "acceptsChatMessages": true + }, + "discoverable": true, + "endpoints": { + "oauthAuthorizationEndpoint": "https://{{domain}}/oauth/authorize", + "oauthRegistrationEndpoint": "https://{{domain}}/api/v1/apps", + "oauthTokenEndpoint": "https://{{domain}}/oauth/token", + "sharedInbox": "https://{{domain}}/inbox", + "uploadMedia": "https://{{domain}}/api/ap/upload_media" + }, + "followers": "https://{{domain}}/users/{{nickname}}/followers", + "following": "https://{{domain}}/users/{{nickname}}/following", + "icon": { + "type": "Image", + "url": "https://{{domain}}/media/a932a27f158b63c3a97e3a57d5384f714a82249274c6fc66c9eca581b4fd8af2.jpg" + }, + "id": "https://{{domain}}/users/{{nickname}}", + "image": { + "type": "Image", + "url": "https://{{domain}}/media/db15f476d0ad14488db4762b7800479e6ef67b1824f8b9ea5c1fa05b7525c5b7.jpg" + }, + "inbox": "https://{{domain}}/users/{{nickname}}/inbox", + "manuallyApprovesFollowers": false, + "name": "{{nickname}} :verified:", + "outbox": "https://{{domain}}/users/{{nickname}}/outbox", + "preferredUsername": "{{nickname}}", + "publicKey": { + "id": "https://{{domain}}/users/{{nickname}}#main-key", + "owner": "https://{{domain}}/users/{{nickname}}", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu4XOAopC4nRIxNlHlt60\n//nCicuedu5wvLGIoQ+KUM2u7/PhLrrTDEqr1A7yQL95S0X8ryYtALgFLI5A54ww\nqjMIbIGAs44lEmDLMEd+XI+XxREE8wdsFpb4QQzWug0DTyqlMouTU25k0tfKh1rF\n4PMJ3uBSjDTAGgFvLNyFWTiVVgChbTNgGOmrEBucRl4NmKzQ69/FIUwENV88oQSU\n3bWvQTEH9rWH1rCLpkmQwdRiWfnhFX/4EUqXukfgoskvenKR8ff3nYhElDqFoE0e\nqUnIW1OZceyl8JewVLcL6m0/wdKeosTsfrcWc8DKfnRYQcBGNoBEq9GrOHDU0q2v\nyQIDAQAB\n-----END PUBLIC KEY-----\n\n" + }, + "summary": "Pleroma BE dev", + "tag": [ + { + "icon": { + "type": "Image", + "url": "https://{{domain}}/emoji/mine/6143373a807b1ae7.png" + }, + "id": "https://{{domain}}/emoji/mine/6143373a807b1ae7.png", + "name": ":verified:", + "type": "Emoji", + "updated": "1970-01-01T00:00:00Z" + } + ], + "type": "Person", + "url": "https://{{domain}}/users/{{nickname}}" +} diff --git a/test/fixtures/webfinger/pleroma-webfinger.json b/test/fixtures/webfinger/pleroma-webfinger.json new file mode 100644 index 000000000..8f075eaaf --- /dev/null +++ b/test/fixtures/webfinger/pleroma-webfinger.json @@ -0,0 +1,27 @@ +{ + "aliases": [ + "https://{{subdomain}}/users/{{nickname}}" + ], + "links": [ + { + "href": "https://{{subdomain}}/users/{{nickname}}", + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html" + }, + { + "href": "https://{{subdomain}}/users/{{nickname}}", + "rel": "self", + "type": "application/activity+json" + }, + { + "href": "https://{{subdomain}}/users/{{nickname}}", + "rel": "self", + "type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "https://{{subdomain}}/ostatus_subscribe?acct={uri}" + } + ], + "subject": "acct:{{nickname}}@{{domain}}" +} diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index c6b631499..e3c263880 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -780,6 +780,104 @@ defmodule Pleroma.UserTest do end end + describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do + setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) + + test "for mastodon" do + Tesla.Mock.mock(fn + %{url: "https://example.com/.well-known/host-meta"} -> + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.example.com/.well-known/host-meta"}] + } + + %{url: "https://sub.example.com/.well-known/host-meta"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.example.com") + } + + %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "example.com") + |> String.replace("{{subdomain}}", "sub.example.com") + } + + %{url: "https://sub.example.com/users/a"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.example.com"), + headers: [{"content-type", "application/activity+json"}] + } + end) + + ap_id = "a@example.com" + {:ok, fetched_user} = User.get_or_fetch(ap_id) + + assert fetched_user.ap_id == "https://sub.example.com/users/a" + assert fetched_user.nickname == "a@example.com" + end + + test "for pleroma" do + Tesla.Mock.mock(fn + %{url: "https://example.com/.well-known/host-meta"} -> + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.example.com/.well-known/host-meta"}] + } + + %{url: "https://sub.example.com/.well-known/host-meta"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.example.com") + } + + %{url: "https://sub.example.com/.well-known/webfinger?resource=acct:a@example.com"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "example.com") + |> String.replace("{{subdomain}}", "sub.example.com") + } + + %{url: "https://sub.example.com/users/a"} -> + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.example.com"), + headers: [{"content-type", "application/activity+json"}] + } + end) + + ap_id = "a@example.com" + {:ok, fetched_user} = User.get_or_fetch(ap_id) + + assert fetched_user.ap_id == "https://sub.example.com/users/a" + assert fetched_user.nickname == "a@example.com" + end + end + describe "fetching a user from nickname or trying to build one" do test "gets an existing user" do user = insert(:user) diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs index fa3b29006..74ce6adc7 100644 --- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do - use Pleroma.Web.ConnCase + use Pleroma.Web.ConnCase, async: true alias Pleroma.MFA alias Pleroma.MFA.TOTP diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index 7059850bd..47c5e9930 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -50,6 +50,35 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do ] end + test "reach user on tld, while pleroma is runned on subdomain" do + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}], + [] + ) + + clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com") + + clear_config([Pleroma.Web.WebFinger, :domain], "example.com") + + user = insert(:user, ap_id: "https://sub.example.com/users/bobby", nickname: "bobby") + + response = + build_conn() + |> put_req_header("accept", "application/jrd+json") + |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@example.com") + |> json_response(200) + + assert response["subject"] == "acct:#{user.nickname}@example.com" + assert response["aliases"] == ["https://sub.example.com/users/#{user.nickname}"] + + on_exit(fn -> + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "localhost"]}], + [] + ) + end) + end + test "it returns 404 when user isn't found (JSON)" do result = build_conn() diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index 2d7b4a40b..b343df814 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -120,7 +120,7 @@ defmodule Pleroma.Web.WebFingerTest do test "it gets the xrd endpoint for statusnet" do {:ok, template} = WebFinger.find_lrdd_template("status.alpicola.com") - assert template == "http://status.alpicola.com/main/xrd?uri={uri}" + assert template == "https://status.alpicola.com/main/xrd?uri={uri}" end test "it works with idna domains as nickname" do diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 8807c2d14..3211d0e09 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -424,14 +424,6 @@ defmodule HttpRequestMock do {:error, :nxdomain} end - def get("http://osada.macgirvin.com/.well-known/host-meta", _, _, _) do - {:ok, - %Tesla.Env{ - status: 404, - body: "" - }} - end - def get("https://osada.macgirvin.com/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -756,7 +748,7 @@ defmodule HttpRequestMock do {:ok, %Tesla.Env{status: 406, body: ""}} end - def get("http://squeet.me/.well-known/host-meta", _, _, _) do + def get("https://squeet.me/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}} end @@ -797,7 +789,7 @@ defmodule HttpRequestMock do {:ok, %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/jrd+json"}]}} end - def get("http://framatube.org/.well-known/host-meta", _, _, _) do + def get("https://framatube.org/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -806,7 +798,7 @@ defmodule HttpRequestMock do end def get( - "http://framatube.org/main/xrd?uri=acct:framasoft@framatube.org", + "https://framatube.org/main/xrd?uri=acct:framasoft@framatube.org", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -841,7 +833,7 @@ defmodule HttpRequestMock do }} end - def get("http://status.alpicola.com/.well-known/host-meta", _, _, _) do + def get("https://status.alpicola.com/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -849,7 +841,7 @@ defmodule HttpRequestMock do }} end - def get("http://macgirvin.com/.well-known/host-meta", _, _, _) do + def get("https://macgirvin.com/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -857,7 +849,7 @@ defmodule HttpRequestMock do }} end - def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do + def get("https://gerzilla.de/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ status: 200, From fdf969cd179a054f627ad1e9f4904333d3f69cba Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 4 Mar 2021 17:58:18 +0300 Subject: [PATCH 02/18] docs & changelog --- CHANGELOG.md | 1 + ...w_to_serve_another_domain_for_webfinger.md | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/configuration/how_to_serve_another_domain_for_webfinger.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb4b1e73..e34b5f18a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded. - Return OAuth token `id` (primary key) in POST `/oauth/token`. +- Possibility to discover users like `user@example.org`, while Pleroma is working on `pleroma.example.org`. Additional configuration required. ### Fixed - Don't crash so hard when email settings are invalid. diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md new file mode 100644 index 000000000..b9800b1cf --- /dev/null +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -0,0 +1,60 @@ +# How to use a different domain name for Pleroma and the users it serves + +Pleroma users are primarily identified by a `user@example.org` handle, and you might want this identifier to be the same as your email or jabber account, for instance. +However, in this case, you are almost certainly serving some web content on `https://example.org` already, and you might want to use another domain (say `pleroma.example.org`) for Pleroma itself. + +Pleroma supports that, but it might be tricky to set up, and any error might prevent you from federating with other instances. + +## Account identifiers + +It is important to understand that for federation purposes, a user in Pleroma has two unique identifiers associated: + +- A webfinger `acct:` URI, used for discovery and as a verifiable global name for the user across Pleroma instances. In our example, our account's acct: URI is `acct:user@example.org` +- An author/actor URI, used in every other aspect of federation. This is the way in which users are identified in ActivityPub, the underlying protocol used for federation with other Pleroma instances. +In our case, it is `https://pleroma.example.org/users/user`. + +Both account identifiers are unique and required for Pleroma. An important risk if you set up your Pleroma instance incorrectly is to create two users (with different acct: URIs) with conflicting author/actor URIs. + +## WebFinger + +As said earlier, each Pleroma user has an `acct`: URI, which is used for discovery and authentication. When you add @user@example.org, a webfinger query is performed. This is done in two steps: + +1. Querying `https://example.org/.well-known/host-meta` (where the domain of the URL matches the domain part of the `acct`: URI) to get information on how to perform the query. +This file will indeed contain a URL template of the form `https://example.org/.well-known/webfinger?resource={uri}` that will be used in the second step. +2. Fill the returned template with the `acct`: URI to be queried and perform the query: `https://example.org/.well-known/webfinger?resource=acct:user@example.org` + +## Configuring your Pleroma instance + +**_DO NOT ATTEMPT TO CONFIGURE YOUR INSTANCE THIS WAY IF YOU DID NOT UNDERSTAND THE ABOVE_** + +### Configuring Pleroma + +Pleroma has a two configuration settings to enable using different domains for your users and Pleroma itself. `host` in `Pleroma.Web.Endpoint` and `domain` in `Pleroma.Web.WebFinger`. When the latter is not set, it defaults to the value of `host`. + +*Be extra careful when configuring your Pleroma instance, as changing `host` may cause remote instances to register different accounts with the same author/actor URI, which will result in federation issues!* + +```elixir +config :pleroma, Pleroma.Web.Endpoint, + url: [host: "pleroma.example.org"] + +config :pleroma, Pleroma.Web.WebFinger, domain: "example.org" +``` + +- `domain` - is the domain for which your Pleroma instance has authority, it's the domain used in `acct:` URI. In our example, `domain` would be set to `example.org. +- `host` - is the domain used for any URL generated for your instance, including the author/actor URL's. In our case, that would be `pleroma.example.org. + +### Configuring WebFinger domain + +Now, you have Pleroma running at `https://pleroma.example.org` as well as a website at `https://example.org`. If you recall how webfinger queries work, the first step is to query `https://example.org/.well-known/host-meta`, which will contain an URL template. + +Therefore, the easiest way to configure `example.org` is to redirect `/.well-known/host-meta` to `pleroma.example.org`. + +With nginx, it would be as simple as adding: + +```nginx +location = /.well-known/host-meta { + return 301 https://pleroma.example.org$request_uri; +} +``` + +in example.org's server block. From d510f3e645d21fe617a94d383463e442d55f0888 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 4 Mar 2021 19:14:00 +0300 Subject: [PATCH 03/18] XML WebFinger user representation correct domain --- lib/pleroma/web/web_finger.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index 0d72d2c62..4d3b55448 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -71,10 +71,8 @@ defmodule Pleroma.Web.WebFinger do def represent_user(user, "JSON") do {:ok, user} = User.ensure_keys_present(user) - domain = Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host() - %{ - "subject" => "acct:#{user.nickname}@#{domain}", + "subject" => "acct:#{user.nickname}@#{domain()}", "aliases" => gather_aliases(user), "links" => gather_links(user) } @@ -96,12 +94,16 @@ defmodule Pleroma.Web.WebFinger do :XRD, %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"}, [ - {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"} + {:Subject, "acct:#{user.nickname}@#{domain()}"} ] ++ aliases ++ links } |> XmlBuilder.to_doc() end + defp domain do + Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host() + end + defp webfinger_from_xml(body) do with {:ok, doc} <- XML.parse_document(body) do subject = XML.string_from_xpath("//Subject", doc) From e0246e0d597e723fe06ef6dc45d90c6102513d2e Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 5 Mar 2021 16:31:59 +0300 Subject: [PATCH 04/18] docs update --- docs/configuration/how_to_serve_another_domain_for_webfinger.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md index b9800b1cf..4e70f444c 100644 --- a/docs/configuration/how_to_serve_another_domain_for_webfinger.md +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -5,6 +5,8 @@ However, in this case, you are almost certainly serving some web content on `htt Pleroma supports that, but it might be tricky to set up, and any error might prevent you from federating with other instances. +*If you are already running Pleroma on `example.org`, it is no longer possible to move it to `pleroma.example.org`.* + ## Account identifiers It is important to understand that for federation purposes, a user in Pleroma has two unique identifiers associated: From b4a0f721fea260f4832df7f7ee729a559907e935 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sun, 21 Mar 2021 13:41:20 +0300 Subject: [PATCH 05/18] rebase fix --- test/pleroma/user_test.exs | 16 ++++++++++++++-- test/pleroma/web/web_finger_test.exs | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index e3c263880..6cf8c97f5 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -808,7 +808,8 @@ defmodule Pleroma.UserTest do |> File.read!() |> String.replace("{{nickname}}", "a") |> String.replace("{{domain}}", "example.com") - |> String.replace("{{subdomain}}", "sub.example.com") + |> String.replace("{{subdomain}}", "sub.example.com"), + headers: [{"content-type", "application/jrd+json"}] } %{url: "https://sub.example.com/users/a"} -> @@ -821,6 +822,16 @@ defmodule Pleroma.UserTest do |> String.replace("{{domain}}", "sub.example.com"), headers: [{"content-type", "application/activity+json"}] } + + %{url: "https://sub.example.com/users/a/collections/featured"} -> + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/users_mock/masto_featured.json") + |> String.replace("{{domain}}", "sub.example.com") + |> String.replace("{{nickname}}", "a"), + headers: [{"content-type", "application/activity+json"}] + } end) ap_id = "a@example.com" @@ -855,7 +866,8 @@ defmodule Pleroma.UserTest do |> File.read!() |> String.replace("{{nickname}}", "a") |> String.replace("{{domain}}", "example.com") - |> String.replace("{{subdomain}}", "sub.example.com") + |> String.replace("{{subdomain}}", "sub.example.com"), + headers: [{"content-type", "application/jrd+json"}] } %{url: "https://sub.example.com/users/a"} -> diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index b343df814..b8286d18c 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -47,7 +47,7 @@ defmodule Pleroma.Web.WebFingerTest do test "returns error when there is no content-type header" do Tesla.Mock.mock(fn - %{url: "http://social.heldscal.la/.well-known/host-meta"} -> + %{url: "https://social.heldscal.la/.well-known/host-meta"} -> {:ok, %Tesla.Env{ status: 200, @@ -147,7 +147,7 @@ defmodule Pleroma.Web.WebFingerTest do headers: [{"content-type", "application/jrd+json"}] }} - %{url: "http://mastodon.social/.well-known/host-meta"} -> + %{url: "https://mastodon.social/.well-known/host-meta"} -> {:ok, %Tesla.Env{ status: 200, @@ -170,7 +170,7 @@ defmodule Pleroma.Web.WebFingerTest do headers: [{"content-type", "application/xrd+xml"}] }} - %{url: "http://pawoo.net/.well-known/host-meta"} -> + %{url: "https://pawoo.net/.well-known/host-meta"} -> {:ok, %Tesla.Env{ status: 200, From de9ee15cbaea6a244deafa231a871390dfc1b9b3 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Tue, 29 Mar 2022 21:48:20 +0300 Subject: [PATCH 06/18] confirm user on password reset --- lib/pleroma/password_reset_token.ex | 4 ++-- .../web/twitter_api/controllers/password_controller.ex | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/password_reset_token.ex b/lib/pleroma/password_reset_token.ex index 42a789ea2..cb2233f5f 100644 --- a/lib/pleroma/password_reset_token.ex +++ b/lib/pleroma/password_reset_token.ex @@ -44,9 +44,9 @@ defmodule Pleroma.PasswordResetToken do %User{} = user <- User.get_cached_by_id(token.user_id), {:ok, _user} <- User.reset_password(user, data), {:ok, token} <- Repo.update(used_changeset(token)) do - {:ok, token} + {:ok, token, user} else - _e -> {:error, token} + _e -> {:error, token, nil} end end diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex index 31b7dd728..f2c81ab7b 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -43,7 +43,10 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do end def do_reset(conn, %{"data" => data}) do - with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do + with {:ok, _, u} <- PasswordResetToken.reset_password(data["token"], data) do + with %User{} = user <- u, true <- user.local and !user.is_confirmed do + User.confirm(user) + end render(conn, "reset_success.html") else _e -> render(conn, "reset_failed.html") From 7e00a94a1b396cf8301cecec0dfc2406276d87af Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Tue, 29 Mar 2022 23:48:23 +0300 Subject: [PATCH 07/18] enable wasm-unsafe-eval in CSP --- lib/pleroma/web/plugs/http_security_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index b89948cec..e7c09e727 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -117,7 +117,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do if Config.get(:env) == :dev do "script-src 'self' 'unsafe-eval'" else - "script-src 'self'" + "script-src 'self' 'wasm-unsafe-eval'" end report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] From 8ac9819f62f65e5142af9a14d040e4e30c5ab6d6 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Tue, 5 Apr 2022 19:41:24 +0300 Subject: [PATCH 08/18] eientei mrfs --- .../web/activity_pub/mrf/block_policy.ex | 76 +++++++++++++++++++ .../mrf/fix_formatting_bullshit.ex | 36 +++++++++ .../mrf/force_mentions_in_content.ex | 9 +-- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/mrf/block_policy.ex create mode 100644 lib/pleroma/web/activity_pub/mrf/fix_formatting_bullshit.ex diff --git a/lib/pleroma/web/activity_pub/mrf/block_policy.ex b/lib/pleroma/web/activity_pub/mrf/block_policy.ex new file mode 100644 index 000000000..431f91f56 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/block_policy.ex @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.BlockBotPolicy do + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + @moduledoc "Notify local users upon remote block." + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp is_block_or_unblock(%{"type" => "Block", "object" => object}), + do: {true, "blocked", object} + + defp is_block_or_unblock(%{ + "type" => "Undo", + "object" => %{"type" => "Block", "object" => object} + }), + do: {true, "unblocked", object} + + defp is_block_or_unblock(_), do: {false, nil, nil} + + defp is_remote_or_displaying_local?(%User{local: false}), do: true + + defp is_remote_or_displaying_local?(_), + do: Pleroma.Config.get([:mrf_blockbotpolicy, :display_local]) + + @impl true + def filter(message) do + + with {true, action, object} <- is_block_or_unblock(message), + %User{} = actor <- User.get_cached_by_ap_id(message["actor"]), + %User{} = recipient <- User.get_cached_by_ap_id(object), + true <- recipient.local, + true <- is_remote_or_displaying_local?(actor), + false <- User.blocks?(recipient, actor) do + bot_user = Pleroma.Config.get([:mrf_blockbotpolicy, :user]) + + _reply = + CommonAPI.post(User.get_by_nickname(bot_user), %{ + status: "@" <> recipient.nickname <> " you are now " <> action <> " by " <> actor.nickname, + visibility: "direct" + }) + end + + {:ok, message} + end + + @impl true + def config_description do + %{ + key: :mrf_blockbotpolicy, + related_policy: "Pleroma.Web.ActivityPub.MRF.BlockBotPolicy", + label: "BlockBot notifications", + description: @moduledoc, + children: [ + %{ + key: :user, + type: :string, + description: "A name of blockbot account", + suggestions: ["blockbot"] + }, + %{ + key: :display_local, + type: :boolean, + description: "Display local blocks" + } + ] + } + end + + @impl true + def describe, + do: {:ok, %{mrf_blockbotpolicy: Pleroma.Config.get(:mrf_blockbotpolicy) |> Enum.into(%{})}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/fix_formatting_bullshit.ex b/lib/pleroma/web/activity_pub/mrf/fix_formatting_bullshit.ex new file mode 100644 index 000000000..96dedad6f --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/fix_formatting_bullshit.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.FixFormattingBullshit do + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + @impl true + def filter( + %{ + "type" => "Create", + "object" => %{"type" => "Note"} + } = object + ) + do + content = object["object"]["content"] || "" + + # fix poast newlines + content = Regex.replace(~r/
]*><\/a>/, content, "") + + # fix poast mention spaces + content = Regex.replace( + ~r/((?:[ ]*]*>]*>.*?<\/a>[ ]*<\/span>)*) (<\/span>)/, + content, + "\\1\\2 " + ) + + {:ok, put_in(object["object"]["content"], content)} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index 255910b2f..16ecddadc 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -91,11 +91,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do |> Enum.map(&User.get_cached_by_ap_id/1) |> Enum.reject(&is_nil/1) |> sort_replied_user(replied_to_user) + |> Enum.uniq() explicitly_mentioned_uris = extract_mention_uris_from_content(content) added_mentions = - Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc -> + Enum.reduce(mention_users, "", fn %User{} = user, acc -> + uri = user.uri || user.ap_id unless uri in explicitly_mentioned_uris do acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " " else @@ -103,10 +105,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do end end) - recipients_inline = - if added_mentions != "", - do: "#{added_mentions}", - else: "" + recipients_inline = added_mentions content = cond do From d7e02bf30fa3d87b42977126a5dd087b84d6af6b Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Mon, 24 Jan 2022 21:43:33 +0300 Subject: [PATCH 09/18] WebFinger support for extra domains --- ...w_to_serve_another_domain_for_webfinger.md | 7 +++- lib/pleroma/web/web_finger.ex | 23 +++++++---- .../web_finger/web_finger_controller_test.exs | 40 ++++++++++++++++++- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md index 4e70f444c..718a6a5e4 100644 --- a/docs/configuration/how_to_serve_another_domain_for_webfinger.md +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -39,11 +39,16 @@ Pleroma has a two configuration settings to enable using different domains for y config :pleroma, Pleroma.Web.Endpoint, url: [host: "pleroma.example.org"] -config :pleroma, Pleroma.Web.WebFinger, domain: "example.org" +config :pleroma, Pleroma.Web.WebFinger, + domain: "example.org", + extra_domains: ["pleroma.example.org", "sub2.example.org"] ``` - `domain` - is the domain for which your Pleroma instance has authority, it's the domain used in `acct:` URI. In our example, `domain` would be set to `example.org. - `host` - is the domain used for any URL generated for your instance, including the author/actor URL's. In our case, that would be `pleroma.example.org. +- `extra_domains` - optional, extra domains besides `host` that will be accepted by webfinger requests. + Useful when host and domain are the same, for example when changing main instance domain, webfinger can be used to service + accounts using previous domain names. ### Configuring WebFinger domain diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index f46afa31d..3d12a522e 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -32,15 +32,12 @@ defmodule Pleroma.Web.WebFinger do def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do host = Pleroma.Web.Endpoint.host() + accepted_domains = [ host | extra_domains() ] - regex = - if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do - ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/ - else - ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@#{host}/ - end + regex = ~r/(acct:)?(?[a-z0-9A-Z_\.-]+)@(?[^\/:]*)/ - with %{"username" => username} <- Regex.named_captures(regex, resource), + with %{"username" => username, "hostname" => hostname} <- Regex.named_captures(regex, resource), + hostname in accepted_domains, %User{} = user <- User.get_cached_by_nickname(username) do {:ok, represent_user(user, fmt)} else @@ -64,8 +61,18 @@ defmodule Pleroma.Web.WebFinger do ] ++ Publisher.gather_webfinger_links(user) end + defp extra_domains() do + Pleroma.Config.get([__MODULE__, :extra_domains]) || [] + end + + defp extra_aps(nickname) do + host_url = Pleroma.Web.Endpoint.struct_url() + + Enum.map(extra_domains(), fn x -> "#{host_url.scheme}://#{x}/users/#{nickname}" end) + end + defp gather_aliases(%User{} = user) do - [user.ap_id | user.also_known_as] + [user.ap_id | user.also_known_as] ++ extra_aps(user.nickname) end def represent_user(user, "JSON") do diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index 5e3ac26f9..ac26cdec3 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -48,9 +48,9 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do ] end - test "reach user on tld, while pleroma is runned on subdomain" do + test "reach user on 2nd-level domain, while pleroma is running on 3rd-level domain" do Pleroma.Web.Endpoint.config_change( - [{Pleroma.Web.Endpoint, url: [host: "sub.example.com"]}], + [{Pleroma.Web.Endpoint, url: [host: "sub.example.com", scheme: "https"]}], [] ) @@ -58,6 +58,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do clear_config([Pleroma.Web.WebFinger, :domain], "example.com") + clear_config([Pleroma.Web.WebFinger, :extra_domains]) + user = insert(:user, ap_id: "https://sub.example.com/users/bobby", nickname: "bobby") response = @@ -77,6 +79,40 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do end) end + test "reach user on old abandoned domain, while pleroma is running on new domain" do + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "example.com", scheme: "https"]}], + [] + ) + + clear_config([Pleroma.Web.Endpoint, :url, :host], "example.com") + + clear_config([Pleroma.Web.WebFinger, :domain], "example.com") + + clear_config([Pleroma.Web.WebFinger, :extra_domains], ["oldexample.com"]) + + user = insert(:user, ap_id: "https://example.com/users/bobby", nickname: "bobby") + + response = + build_conn() + |> put_req_header("accept", "application/jrd+json") + |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@oldexample.com") + |> json_response(200) + + assert response["subject"] == "acct:#{user.nickname}@example.com" + assert response["aliases"] == [ + "https://example.com/users/#{user.nickname}", + "https://oldexample.com/users/#{user.nickname}" + ] + + on_exit(fn -> + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "localhost"]}], + [] + ) + end) + end + test "it returns 404 when user isn't found (JSON)" do result = build_conn() From 766d1b7f935e132f0d0311122e2469b0144d8fd4 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Tue, 25 Jan 2022 00:54:35 +0300 Subject: [PATCH 10/18] WebFinger docs --- config/config.exs | 2 +- config/description.exs | 28 +++++++++++++++++ docs/configuration/cheatsheet.md | 31 +++++++++++++++++++ ...w_to_serve_another_domain_for_webfinger.md | 29 ++++++----------- lib/pleroma/web/web_finger.ex | 14 +++++++-- .../web_finger/web_finger_controller_test.exs | 16 +++++++++- 6 files changed, 95 insertions(+), 25 deletions(-) diff --git a/config/config.exs b/config/config.exs index 232b0a724..8cb6c4a87 100644 --- a/config/config.exs +++ b/config/config.exs @@ -858,7 +858,7 @@ config :pleroma, ConcurrentLimiter, [ {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]} ] -config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true +config :pleroma, Pleroma.Web.WebFinger, domain: nil, extra_domains: [], update_nickname_on_user_fetch: true # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/config/description.exs b/config/description.exs index 704af8f68..d67e5eaec 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3439,5 +3439,33 @@ config :pleroma, :config_description, [ ] } ] + }, + %{ + group: :pleroma, + key: Pleroma.Web.WebFinger, + label: "Web finger", + type: :group, + description: "Web finger settings", + children: [ + %{ + key: :domain, + type: :string, + description: "The authoritative instance domain, that will be used in user account subjects. Default: listened HTTP hostname.", + suggestions: ["example.com"] + }, + %{ + key: :extra_domains, + label: "Extra domains", + type: {:list, :string}, + description: "Additional domains that should be served by webfinger requests in addition to authoritative domain and listened http hostname. Default: empty.", + suggestions: ["oldexample.com", "sub.example.com"] + }, + %{ + key: :update_nickname_on_user_fetch, + label: "Update nickname on user fetch", + type: :boolean, + description: "Enables using nicknames for remote users as returned by their respective webfinger responses. Default: enabled" + } + ] } ] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 4dacdc68c..9193a5e9a 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -488,6 +488,37 @@ Available caches: * `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration). * `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds). +### Pleroma.Web.WebFinger + +!!! note + This is only meaningful if you have HTTP server/reverse proxy serving additional domains + +- `domain` - is the domain for which your Pleroma instance has authority, it's the domain used in `acct:` URI. In our example, `domain` would be set to `example.org.` + If it is not set, defaults to `host` in `Pleroma.Web.Endpoint` +- `extra_domains` - extra domains besides `host` and `domain` that will be accepted by webfinger requests. + Useful when host and domain are the same, for example when changing main instance domain, webfinger can be used to service + accounts using previous domain names. +- `update_nickname_on_user_fetch` - If enabled, nicknames for newly encountered remote users will be taken from their + respective webfinger response. + +Example: + +```elixir +config :pleroma, Pleroma.Web.Endpoint, + url: [host: "pleroma.example.org"] + +config :pleroma, Pleroma.Web.WebFinger, + domain: "example.org", + extra_domains: ["sub2.example.org"] +``` + +Here pleroma is hosted on `pleroma.example.org` which is also part of AP ids, but authoritive domain and hence subject +of local users would be `example.org`, while webfinger requests will be accepted on all three of `pleroma.example.org`, +`example.org` and `sub2.example.org`. + +See [how_to_serve_another_domain_for_webfinger.md](how_to_serve_another_domain_for_webfinger.md) for additional details +and reverse proxy configuration example. + ## HTTP client ### :http diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md index 718a6a5e4..b6e699e59 100644 --- a/docs/configuration/how_to_serve_another_domain_for_webfinger.md +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -29,26 +29,6 @@ This file will indeed contain a URL template of the form `https://example.org/.w **_DO NOT ATTEMPT TO CONFIGURE YOUR INSTANCE THIS WAY IF YOU DID NOT UNDERSTAND THE ABOVE_** -### Configuring Pleroma - -Pleroma has a two configuration settings to enable using different domains for your users and Pleroma itself. `host` in `Pleroma.Web.Endpoint` and `domain` in `Pleroma.Web.WebFinger`. When the latter is not set, it defaults to the value of `host`. - -*Be extra careful when configuring your Pleroma instance, as changing `host` may cause remote instances to register different accounts with the same author/actor URI, which will result in federation issues!* - -```elixir -config :pleroma, Pleroma.Web.Endpoint, - url: [host: "pleroma.example.org"] - -config :pleroma, Pleroma.Web.WebFinger, - domain: "example.org", - extra_domains: ["pleroma.example.org", "sub2.example.org"] -``` - -- `domain` - is the domain for which your Pleroma instance has authority, it's the domain used in `acct:` URI. In our example, `domain` would be set to `example.org. -- `host` - is the domain used for any URL generated for your instance, including the author/actor URL's. In our case, that would be `pleroma.example.org. -- `extra_domains` - optional, extra domains besides `host` that will be accepted by webfinger requests. - Useful when host and domain are the same, for example when changing main instance domain, webfinger can be used to service - accounts using previous domain names. ### Configuring WebFinger domain @@ -65,3 +45,12 @@ location = /.well-known/host-meta { ``` in example.org's server block. + +### Limitations + +Some BEs and FEs may still use AP id instead of account subject to present user handles in UI. + +### Warnings + +Change `host` in `Pleroma.Web.Endpoint` with caution, it may result in local accounts being duplicated on remote instances, +which will affect federation. diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index 3d12a522e..640546d13 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -62,17 +62,25 @@ defmodule Pleroma.Web.WebFinger do end defp extra_domains() do - Pleroma.Config.get([__MODULE__, :extra_domains]) || [] + extra = Pleroma.Config.get([__MODULE__, :extra_domains]) || [] + webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) + host = Pleroma.Web.Endpoint.host() + + if webfinger_domain && webfinger_domain != host && webfinger_domain not in extra do + [ webfinger_domain | extra ] + else + extra + end end - defp extra_aps(nickname) do + defp extra_ap_ids(nickname) do host_url = Pleroma.Web.Endpoint.struct_url() Enum.map(extra_domains(), fn x -> "#{host_url.scheme}://#{x}/users/#{nickname}" end) end defp gather_aliases(%User{} = user) do - [user.ap_id | user.also_known_as] ++ extra_aps(user.nickname) + [user.ap_id | user.also_known_as] ++ extra_ap_ids(user.nickname) end def represent_user(user, "JSON") do diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index ac26cdec3..4650025be 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -28,6 +28,17 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do end test "Webfinger JRD" do + Pleroma.Web.Endpoint.config_change( + [{Pleroma.Web.Endpoint, url: [host: "localhost", scheme: "https"]}], + [] + ) + + clear_config([Pleroma.Web.Endpoint, :url, :host]) + + clear_config([Pleroma.Web.WebFinger, :domain]) + + clear_config([Pleroma.Web.WebFinger, :extra_domains]) + user = insert(:user, ap_id: "https://hyrule.world/users/zelda", @@ -69,7 +80,10 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do |> json_response(200) assert response["subject"] == "acct:#{user.nickname}@example.com" - assert response["aliases"] == ["https://sub.example.com/users/#{user.nickname}"] + assert response["aliases"] == [ + "https://sub.example.com/users/#{user.nickname}", + "https://example.com/users/#{user.nickname}" + ] on_exit(fn -> Pleroma.Web.Endpoint.config_change( From 7baa240841ef8830a8cb3f07fc80edfcad371c61 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sat, 29 Jan 2022 23:17:06 +0300 Subject: [PATCH 11/18] WebFinger documentation clarifications --- .../how_to_serve_another_domain_for_webfinger.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/configuration/how_to_serve_another_domain_for_webfinger.md b/docs/configuration/how_to_serve_another_domain_for_webfinger.md index b6e699e59..d544f8b94 100644 --- a/docs/configuration/how_to_serve_another_domain_for_webfinger.md +++ b/docs/configuration/how_to_serve_another_domain_for_webfinger.md @@ -52,5 +52,8 @@ Some BEs and FEs may still use AP id instead of account subject to present user ### Warnings -Change `host` in `Pleroma.Web.Endpoint` with caution, it may result in local accounts being duplicated on remote instances, -which will affect federation. +Change `host` in `Pleroma.Web.Endpoint` and `domain` in `Pleroma.Web.WebFinger` with caution, it will likely result in +local accounts being duplicated on remote instances, which will affect federation and users reachability. + +Webfinger results are generally cached by server implementations and may only be requested once (on first account encounter) +or with set rate limit depending on the implementation. From 57ca93c1505712dd361737cc15ac9e787d190d37 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sun, 23 Jan 2022 20:31:10 +0300 Subject: [PATCH 12/18] remove relay from reblogs --- .../web/mastodon_api/views/status_view.ex | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 1ebfd6740..4fa40afcd 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -78,6 +78,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # False if the user is logged out defp reblogged?(_activity, _user), do: false + defp reblogged_count(activity) do + with %Object{data: %{"announcements" => announcements, "announcement_count" => announcement_count}} + when is_list(announcements) <- Object.normalize(activity, fetch: false) do + if length(announcements) == 0 do + 0 + else + relays = length(Enum.filter(announcements, fn(x) -> Regex.match?(~r/\/relay$/, x) end)) + announcement_count - relays + end + else + _ -> 0 + end + end + def render("index.json", opts) do reading_user = opts[:for] @@ -208,7 +222,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do user_follower_address = user.follower_address like_count = object.data["like_count"] || 0 - announcement_count = object.data["announcement_count"] || 0 + announcement_count = reblogged_count(object.data) hashtags = Object.hashtags(object) sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw") From 159d6b6cb547874ed79e444bd35fe840c36146a4 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Thu, 7 Apr 2022 22:19:14 +0300 Subject: [PATCH 13/18] merge fix --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 79662efa9..f71168fda 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1538,7 +1538,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do accepts_chat_messages: accepts_chat_messages, pinned_objects: pinned_objects, birthday: birthday, - show_birthday: show_birthday + show_birthday: show_birthday, nickname: nickname } end From 85cbf773f010b1bb2c77e51b1e994314bbf4f008 Mon Sep 17 00:00:00 2001 From: Ilja Date: Sun, 20 Mar 2022 13:32:12 +0100 Subject: [PATCH 14/18] update sweet_xml [Security] --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index db2f1f069..9b4a3e239 100644 --- a/mix.exs +++ b/mix.exs @@ -141,7 +141,7 @@ defmodule Pleroma.Mixfile do {:mogrify, "~> 0.7.4"}, {:ex_aws, "~> 2.1.6"}, {:ex_aws_s3, "~> 2.0"}, - {:sweet_xml, "~> 0.6.6"}, + {:sweet_xml, "~> 0.7.2"}, {:earmark, "1.4.15"}, {:bbcode_pleroma, "~> 0.2.0"}, {:crypt, diff --git a/mix.lock b/mix.lock index 232649cd5..821c397b4 100644 --- a/mix.lock +++ b/mix.lock @@ -114,7 +114,7 @@ "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"}, "swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, From 4d482b765f8bebbad0d5e9e17fb923eb475313d6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 5 May 2022 18:39:34 -0400 Subject: [PATCH 15/18] Allow to skip cache in Cache plug Ref: fix-local-public --- lib/pleroma/web/plugs/cache.ex | 19 ++++++++++++------- test/pleroma/web/plugs/cache_test.exs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex index 111854859..e0467f107 100644 --- a/lib/pleroma/web/plugs/cache.ex +++ b/lib/pleroma/web/plugs/cache.ex @@ -98,14 +98,19 @@ defmodule Pleroma.Web.Plugs.Cache do content_type = content_type(conn) conn = - unless opts[:tracking_fun] do - @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl) - conn - else - tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) - @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl) + cond do + Map.get(conn.assigns, :skip_cache, false) -> + conn - opts.tracking_fun.(conn, tracking_fun_data) + !opts[:tracking_fun] -> + @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl) + conn + + true -> + tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) + @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl) + + opts.tracking_fun.(conn, tracking_fun_data) end put_resp_header(conn, "x-cache", "MISS from Pleroma") diff --git a/test/pleroma/web/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs index 0ceab6cab..4e729cafb 100644 --- a/test/pleroma/web/plugs/cache_test.exs +++ b/test/pleroma/web/plugs/cache_test.exs @@ -179,4 +179,22 @@ defmodule Pleroma.Web.Plugs.CacheTest do |> send_resp(:im_a_teapot, "🥤") |> sent_resp() end + + test "ignores if skip_cache is assigned" do + assert @miss_resp == + conn(:get, "/") + |> assign(:skip_cache, true) + |> Cache.call(%{query_params: false, ttl: nil}) + |> put_resp_content_type("cofe/hot") + |> send_resp(:ok, "cofe") + |> sent_resp() + + assert @miss_resp == + conn(:get, "/") + |> assign(:skip_cache, true) + |> Cache.call(%{query_params: false, ttl: nil}) + |> put_resp_content_type("cofe/hot") + |> send_resp(:ok, "cofe") + |> sent_resp() + end end From fa3157df964d4f88d0fd1ce466a44333c8c7ef60 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 5 May 2022 19:20:32 -0400 Subject: [PATCH 16/18] Skip cache when /objects or /activities is authenticated Ref: fix-local-public --- .../activity_pub/activity_pub_controller.ex | 11 +++++++++ lib/pleroma/web/plugs/cache.ex | 21 +++++++++------- .../activity_pub_controller_test.exs | 24 +++++++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 57ac40b42..d423b1139 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -84,6 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do user <- Map.get(assigns, :user, nil), {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do conn + |> maybe_skip_cache(user) |> assign(:tracking_fun_data, object.id) |> set_cache_ttl_for(object) |> put_resp_content_type("application/activity+json") @@ -112,6 +113,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do user <- Map.get(assigns, :user, nil), {_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do conn + |> maybe_skip_cache(user) |> maybe_set_tracking_data(activity) |> set_cache_ttl_for(activity) |> put_resp_content_type("application/activity+json") @@ -151,6 +153,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do assign(conn, :cache_ttl, ttl) end + def maybe_skip_cache(conn, user) do + if user do + conn + |> assign(:skip_cache, true) + else + conn + end + end + # GET /relay/following def relay_following(conn, _params) do with %{halted: false} = conn <- FederatingPlug.call(conn, []) do diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex index e0467f107..935b2d834 100644 --- a/lib/pleroma/web/plugs/cache.ex +++ b/lib/pleroma/web/plugs/cache.ex @@ -97,20 +97,23 @@ defmodule Pleroma.Web.Plugs.Cache do key = cache_key(conn, opts) content_type = content_type(conn) + should_cache = not Map.get(conn.assigns, :skip_cache, false) + conn = - cond do - Map.get(conn.assigns, :skip_cache, false) -> - conn - - !opts[:tracking_fun] -> + unless opts[:tracking_fun] do + if should_cache do @cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl) - conn + end - true -> - tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) + conn + else + tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) + + if should_cache do @cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl) + end - opts.tracking_fun.(conn, tracking_fun_data) + opts.tracking_fun.(conn, tracking_fun_data) end put_resp_header(conn, "x-cache", "MISS from Pleroma") diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 50315e21f..511405624 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -291,6 +291,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note}) end + test "does not cache authenticated response", %{conn: conn} do + user = insert(:user) + reader = insert(:user) + + {:ok, post} = + CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"}) + + object = Object.normalize(post, fetch: false) + uuid = String.split(object.data["id"], "/") |> List.last() + + assert response = + conn + |> assign(:user, reader) + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + + json_response(response, 200) + + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + |> json_response(404) + end + test "it returns 404 for non-public messages", %{conn: conn} do note = insert(:direct_note) uuid = String.split(note.data["id"], "/") |> List.last() From 57c486014c06715ff5cd5ad4361155d4a1776c23 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 6 May 2022 08:59:36 +0200 Subject: [PATCH 17/18] Release 2.4.3 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ad0ada9..95405bb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed +## 2.4.3 - 2022-05-06 + +### Security +- Private `/objects/` and `/activities/` leaking if cached by authenticated user +- SweetXML library DTD bomb + ## 2.4.2 - 2022-01-10 ### Fixed diff --git a/mix.exs b/mix.exs index 9b4a3e239..927f39975 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.4.2"), + version: version("2.4.3"), elixir: "~> 1.9", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), From beac518f294966ac6217993981d103bbe57917ba Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sat, 28 May 2022 14:14:34 +0300 Subject: [PATCH 18/18] grab emojis from all hosts --- .../activity_pub/mrf/steal_emoji_policy.ex | 25 ++++++++----------- mix.lock | 4 +-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 06305235e..9544385e5 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -10,8 +10,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do @moduledoc "Detect new emojis by their shortcode and steals them" @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], []) - defp steal_emoji({shortcode, url}, emoji_dir_path) do url = Pleroma.Web.MediaProxy.url(url) @@ -54,28 +52,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do host = URI.parse(actor).host - if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do + if host != Pleroma.Web.Endpoint.host() do installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end) emoji_dir_path = Config.get( [:mrf_steal_emoji, :path], - Path.join(Config.get([:instance, :static_dir]), "emoji/stolen") + Path.join(Config.get([:instance, :static_dir]), "emoji/stolen/#{host}") ) - File.mkdir_p(emoji_dir_path) + tograb_emojis = + foreign_emojis + |> Enum.reject(fn {shortcode, url} -> shortcode in installed_emoji || !Regex.match?(~r/\.(jpg|jpeg|png|gif)$/i, url) end) + + if !Enum.empty?(tograb_emojis) do + File.mkdir_p(emoji_dir_path) + end new_emojis = - foreign_emojis - |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) - |> Enum.filter(fn {shortcode, _url} -> - reject_emoji? = - [:mrf_steal_emoji, :rejected_shortcodes] - |> Config.get([]) - |> Enum.find(false, fn regex -> String.match?(shortcode, regex) end) - - !reject_emoji? - end) + tograb_emojis |> Enum.map(&steal_emoji(&1, emoji_dir_path)) |> Enum.filter(& &1) diff --git a/mix.lock b/mix.lock index 6a8a99485..41473c095 100644 --- a/mix.lock +++ b/mix.lock @@ -27,8 +27,8 @@ "db_connection": {:hex, :db_connection, "2.4.0", "d04b1b73795dae60cead94189f1b8a51cc9e1f911c234cc23074017c43c031e5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad416c21ad9f61b3103d254a71b63696ecadb6a917b36f563921e0de00d7d7c8"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "earmark": {:hex, :earmark, "1.4.18", "618c4ff1563450d1832b7fb41dc6755e470f91a6fd4c70f350a58b14f64a7db8", [:mix], [{:earmark_parser, ">= 1.4.17", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "57ac3b6da3958ed09c669a9b159e86377fcccda56bacde8a209fa4dcdef52560"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"}, + "earmark": {:hex, :earmark, "1.4.15", "2c7f924bf495ec1f65bd144b355d0949a05a254d0ec561740308a54946a67888", [:mix], [{:earmark_parser, ">= 1.4.13", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "3b1209b85bc9f3586f370f7c363f6533788fb4e51db23aa79565875e7f9999ee"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"}, "eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"}, "ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},