Docs + test for DM privacy
ci/woodpecker/pr/build-amd64 Pipeline is pending Details
ci/woodpecker/pr/build-arm64 Pipeline is pending Details
ci/woodpecker/pr/docs Pipeline is pending Details
ci/woodpecker/pr/lint Pipeline is pending Details
ci/woodpecker/pr/test Pipeline is pending Details

This commit is contained in:
smitten 2023-07-29 00:08:29 -04:00
parent 208d2a6e0d
commit 5469a1268e
Signed by untrusted user: smitten
GPG Key ID: 1DDD22F13552A07A
2 changed files with 86 additions and 48 deletions

View File

@ -3,6 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
@moduledoc """
Handles web-based protocol requests, in particular web+ap: which reference ActivityPub URIs.
see https://datatracker.ietf.org/doc/draft-soni-protocol-handler-well-known-uri/
A web+ap: URI should be handled like an https: URI, redirecting to the local representation of the remote or local object.
"""
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
@ -17,7 +22,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
# Note: (requires read:search)
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated} when action in @oauth_search_actions)
# Protocol definition: https://datatracker.ietf.org/doc/draft-soni-protocol-handler-well-known-uri/
def reroute(conn, %{"target" => target_param}) do
conn |> redirect(to: "/api/v1/akkoma/protocol-handler?#{URI.encode_query([target: target_param])}")
end
@ -38,12 +42,13 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
def handle(conn, _), do: conn |> json_response(:bad_request, "Could not handle protocol URL")
@spec find_and_redirect(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
defp find_and_redirect(%{assigns: %{user: user}} = conn, identifier) do
# Remove userinfo if present (username:password@)
cleaned = URI.parse("https:" <> identifier) |> Map.merge(%{ userinfo: nil }) |> URI.to_string()
with {:error, _err} <- User.get_or_fetch(cleaned),
[] <- DatabaseSearch.maybe_fetch([], user, cleaned),
[] <- exact_search(cleaned, user) do
[] <- exact_user_search(cleaned, user) do
conn |> json_response(:not_found, "Not Found - #{cleaned}")
else
{:ok, %User{} = found_user} -> conn |> redirect(to: "/users/#{found_user.id}")
@ -54,7 +59,7 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
end
end
defp exact_search(identifier, user) do
defp exact_user_search(identifier, user) do
case User.search(identifier, limit: 1, for_user: user) do
[%User{:ap_id => ^identifier} = found_user] -> [found_user]
[%User{:uri => ^identifier} = found_user] -> [found_user]

View File

@ -6,38 +6,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
import Pleroma.Factory
setup do
Tesla.Mock.mock(fn
%{method: :get, url: "https://mastodon.social/users/emelie/statuses/101849165031453009"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
}
%{method: :get, url: "https://mastodon.social/users/emelie"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/emelie.json")
}
%{method: :get, url: "https://mastodon.social/@emelie"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/emelie.json")
}
%{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body:
File.read!("test/fixtures/users_mock/masto_featured.json")
|> String.replace("{{domain}}", "mastodon.social")
|> String.replace("{{nickname}}", "emelie")
}
end)
end
describe "GET /.well-known/protocol-handler" do
test "should return bad_request when missing `target`" do
%{conn: conn} = oauth_access([])
@ -63,6 +31,42 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
end
describe "GET /api/v1/akkoma/protocol-handler" do
setup do
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
Tesla.Mock.mock(fn
%{method: :get, url: "https://mastodon.social/users/emelie/statuses/101849165031453009"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
}
%{method: :get, url: "https://mastodon.social/users/emelie"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/emelie.json")
}
%{method: :get, url: "https://mastodon.social/@emelie"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body: File.read!("test/fixtures/tesla_mock/emelie.json")
}
%{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} ->
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body:
File.read!("test/fixtures/users_mock/masto_featured.json")
|> String.replace("{{domain}}", "mastodon.social")
|> String.replace("{{nickname}}", "emelie")
}
_ -> %Tesla.Env{
status: 404,
}
end)
end
test "should return bad_request when target prefix has unknown protocol" do
%{conn: conn} = oauth_access([])
@ -75,7 +79,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
end
test "should return forbidden for unauthed user when target is remote" do
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
%{conn: conn} = oauth_access([])
resp =
@ -87,7 +90,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
end
test "should return redirect for unauthed user when target is local AP ID for user" do
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
%{conn: conn} = oauth_access([])
local_user = insert(:user, %{nickname: "akkoma@sub.example.com", local: true, ap_id: "https://sub.example.com/users/akkoma"})
@ -100,23 +102,54 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
assert resp =~ "<a href=\"/users/#{local_user.id}\">"
end
test "should return redirect for unauthed user when target is local AP ID for note activity" do
clear_config([Pleroma.Web.Endpoint, :url, :host], "mastodon.social")
test "should return not_found for unauthed user when target is local AP ID for DM note activity" do
%{conn: conn} = oauth_access([])
local_user = insert(:user, %{nickname: "akkoma@sub.example.com", local: true, ap_id: "https://sub.example.com/users/akkoma"})
note = insert(:note, %{
id: "AYAsX3ZRH6NJAzZmEa",
data: %{
"cc" => [],
"to" => [],
"actor" => local_user.ap_id,
"id" => "https://sub.example.com/notice/AYAsX3ZRH6NJAzZmEa",
"summary" => "",
"content" => "Pleroma's really cool!",
"directMessage" => true,
}
})
insert(:note_activity, note: note, user: local_user)
clear_config([Pleroma.Web.Endpoint, :url, :host], "sub.example.com")
%{conn: conn} = oauth_access(["read:search"])
conn
|> get("/api/v1/akkoma/protocol-handler?target=web%2Bap%3A%2F%2Fsub.example.com/notice/AYAsX3ZRH6NJAzZmEa")
|> json_response(404)
end
test "should return not_found for unauthed user when target is local AP ID for public note activity" do
%{conn: conn} = oauth_access([])
local_user = insert(:user, %{nickname: "akkoma@sub.example.com", local: true, ap_id: "https://sub.example.com/users/akkoma"})
note = insert(:note, %{
id: "AYAsX3ZRH6NJAzZmPa",
data: %{
"cc" => [],
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => local_user.ap_id,
"id" => "https://sub.example.com/notice/AYAsX3ZRH6NJAzZmPa",
"summary" => "",
"content" => "Pleroma's really cool!",
}
})
activity = insert(:note_activity, note: note, user: local_user, visibility: "direct")
resp =
conn
|> get("/api/v1/akkoma/protocol-handler?target=web%2Bap%3A%2F%2Fmastodon.social/users/emelie/statuses/101849165031453009")
|> get("/api/v1/akkoma/protocol-handler?target=web%2Bap%3A%2F%2Fsub.example.com/notice/AYAsX3ZRH6NJAzZmPa")
|> html_response(302)
assert activity = Activity.get_by_object_ap_id_with_object("https://mastodon.social/users/emelie/statuses/101849165031453009")
assert resp =~ "You are being"
assert resp =~ "<a href=\"/notice/#{activity.id}\">"
assert resp =~ "You are being"
assert resp =~ "<a href=\"/notice/#{activity.id}\">"
end
test "should return redirect for authed user when target is AP ID for user" do
test "should return redirect for authed user when target is AP ID for remote user" do
%{conn: conn} = oauth_access(["read:search"])
remote_user = insert(:user, %{nickname: "akkoma@ihatebeinga.live", local: false, ap_id: "https://ihatebeinga.live/users/akkoma"})
@ -129,7 +162,7 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
end
test "should return redirect for authed user when target is URL for user" do
test "should return redirect for authed user when target is URI for remote user" do
%{conn: conn} = oauth_access(["read:search"])
remote_user = insert(:user, %{
nickname: "emelie@mastodon.social",
@ -160,7 +193,7 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
end
test "should return redirect for authed user when target is AP ID for note activity" do
test "should return redirect for authed user when target is AP ID for remote note activity" do
%{conn: conn} = oauth_access(["read:search"])
resp =