Docs + test for DM privacy
This commit is contained in:
parent
208d2a6e0d
commit
5469a1268e
2 changed files with 86 additions and 48 deletions
|
@ -3,6 +3,11 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
|
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
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||||
|
@ -17,7 +22,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerController do
|
||||||
# Note: (requires read:search)
|
# Note: (requires read:search)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated} when action in @oauth_search_actions)
|
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
|
def reroute(conn, %{"target" => target_param}) do
|
||||||
conn |> redirect(to: "/api/v1/akkoma/protocol-handler?#{URI.encode_query([target: target_param])}")
|
conn |> redirect(to: "/api/v1/akkoma/protocol-handler?#{URI.encode_query([target: target_param])}")
|
||||||
end
|
end
|
||||||
|
@ -38,12 +42,13 @@ def handle(%{assigns: %{user: user}} = conn, %{"target" => "web+ap:" <> identifi
|
||||||
|
|
||||||
def handle(conn, _), do: conn |> json_response(:bad_request, "Could not handle protocol URL")
|
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
|
defp find_and_redirect(%{assigns: %{user: user}} = conn, identifier) do
|
||||||
# Remove userinfo if present (username:password@)
|
# Remove userinfo if present (username:password@)
|
||||||
cleaned = URI.parse("https:" <> identifier) |> Map.merge(%{ userinfo: nil }) |> URI.to_string()
|
cleaned = URI.parse("https:" <> identifier) |> Map.merge(%{ userinfo: nil }) |> URI.to_string()
|
||||||
with {:error, _err} <- User.get_or_fetch(cleaned),
|
with {:error, _err} <- User.get_or_fetch(cleaned),
|
||||||
[] <- DatabaseSearch.maybe_fetch([], user, 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}")
|
conn |> json_response(:not_found, "Not Found - #{cleaned}")
|
||||||
else
|
else
|
||||||
{:ok, %User{} = found_user} -> conn |> redirect(to: "/users/#{found_user.id}")
|
{:ok, %User{} = found_user} -> conn |> redirect(to: "/users/#{found_user.id}")
|
||||||
|
@ -54,7 +59,7 @@ defp find_and_redirect(%{assigns: %{user: user}} = conn, identifier) do
|
||||||
end
|
end
|
||||||
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
|
case User.search(identifier, limit: 1, for_user: user) do
|
||||||
[%User{:ap_id => ^identifier} = found_user] -> [found_user]
|
[%User{:ap_id => ^identifier} = found_user] -> [found_user]
|
||||||
[%User{:uri => ^identifier} = found_user] -> [found_user]
|
[%User{:uri => ^identifier} = found_user] -> [found_user]
|
||||||
|
|
|
@ -6,38 +6,6 @@ defmodule Pleroma.Web.AkkomaAPI.ProtocolHandlerControllerTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
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
|
describe "GET /.well-known/protocol-handler" do
|
||||||
test "should return bad_request when missing `target`" do
|
test "should return bad_request when missing `target`" do
|
||||||
%{conn: conn} = oauth_access([])
|
%{conn: conn} = oauth_access([])
|
||||||
|
@ -63,6 +31,42 @@ test "should return redirect when target parameter is present" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /api/v1/akkoma/protocol-handler" do
|
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
|
test "should return bad_request when target prefix has unknown protocol" do
|
||||||
%{conn: conn} = oauth_access([])
|
%{conn: conn} = oauth_access([])
|
||||||
|
|
||||||
|
@ -75,7 +79,6 @@ test "should return bad_request when target prefix has unknown protocol" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should return forbidden for unauthed user when target is remote" do
|
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([])
|
%{conn: conn} = oauth_access([])
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
|
@ -87,7 +90,6 @@ test "should return forbidden for unauthed user when target is remote" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should return redirect for unauthed user when target is local AP ID for user" do
|
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([])
|
%{conn: conn} = oauth_access([])
|
||||||
local_user = insert(:user, %{nickname: "akkoma@sub.example.com", local: true, ap_id: "https://sub.example.com/users/akkoma"})
|
local_user = insert(:user, %{nickname: "akkoma@sub.example.com", local: true, ap_id: "https://sub.example.com/users/akkoma"})
|
||||||
|
|
||||||
|
@ -100,23 +102,54 @@ test "should return redirect for unauthed user when target is local AP ID for us
|
||||||
assert resp =~ "<a href=\"/users/#{local_user.id}\">"
|
assert resp =~ "<a href=\"/users/#{local_user.id}\">"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should return redirect for unauthed user when target is local AP ID for note activity" do
|
test "should return not_found for unauthed user when target is local AP ID for DM note activity" do
|
||||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "mastodon.social")
|
%{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: conn} = oauth_access(["read:search"])
|
|> 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 =
|
resp =
|
||||||
conn
|
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)
|
|> 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 =~ "You are being"
|
assert resp =~ "<a href=\"/notice/#{activity.id}\">"
|
||||||
assert resp =~ "<a href=\"/notice/#{activity.id}\">"
|
|
||||||
end
|
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"])
|
%{conn: conn} = oauth_access(["read:search"])
|
||||||
remote_user = insert(:user, %{nickname: "akkoma@ihatebeinga.live", local: false, ap_id: "https://ihatebeinga.live/users/akkoma"})
|
remote_user = insert(:user, %{nickname: "akkoma@ihatebeinga.live", local: false, ap_id: "https://ihatebeinga.live/users/akkoma"})
|
||||||
|
|
||||||
|
@ -129,7 +162,7 @@ test "should return redirect for authed user when target is AP ID for user" do
|
||||||
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
|
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
|
||||||
end
|
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"])
|
%{conn: conn} = oauth_access(["read:search"])
|
||||||
remote_user = insert(:user, %{
|
remote_user = insert(:user, %{
|
||||||
nickname: "emelie@mastodon.social",
|
nickname: "emelie@mastodon.social",
|
||||||
|
@ -160,7 +193,7 @@ test "should return redirect for authed user when target is AP ID for user, stri
|
||||||
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
|
assert resp =~ "<a href=\"/users/#{remote_user.id}\">"
|
||||||
end
|
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"])
|
%{conn: conn} = oauth_access(["read:search"])
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
|
|
Loading…
Reference in a new issue