Add an authenticated fetch route for members
If the member is remote, it redirects to original instance Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
b4c624de23
commit
bdb4350624
|
@ -29,7 +29,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Member do
|
|||
"id" => member.url,
|
||||
"actor" => member.actor.url,
|
||||
"object" => member.parent.url,
|
||||
"role" => member.role
|
||||
"role" => to_string(member.role)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
19
lib/web/cache/activity_pub.ex
vendored
19
lib/web/cache/activity_pub.ex
vendored
|
@ -4,7 +4,7 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
|||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources, Todos, Tombstone}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
|
@ -174,6 +174,23 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
|||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a member by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_member_by_uuid_with_preload(String.t()) ::
|
||||
{:commit, Todo.t()} | {:ignore, nil}
|
||||
def get_member_by_uuid_with_preload(uuid) do
|
||||
Cachex.fetch(@cache, "member_" <> uuid, fn "member_" <> uuid ->
|
||||
case Actors.get_member(uuid) do
|
||||
%Member{} = member ->
|
||||
{:commit, member}
|
||||
|
||||
nil ->
|
||||
{:ignore, nil}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a relay.
|
||||
"""
|
||||
|
|
1
lib/web/cache/cache.ex
vendored
1
lib/web/cache/cache.ex
vendored
|
@ -24,6 +24,7 @@ defmodule Mobilizon.Web.Cache do
|
|||
defdelegate get_resource_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_todo_list_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_todo_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_member_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_post_by_slug_with_preload(slug), to: ActivityPub
|
||||
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
|
||||
defdelegate get_relay, to: ActivityPub
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Mobilizon.Web.ActivityPubController do
|
|||
use Mobilizon.Web, :controller
|
||||
|
||||
alias Mobilizon.{Actors, Config}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Federator
|
||||
|
@ -66,6 +66,41 @@ defmodule Mobilizon.Web.ActivityPubController do
|
|||
actor_collection(conn, "discussions", args)
|
||||
end
|
||||
|
||||
@ok_statuses [:ok, :commit]
|
||||
@spec member(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def member(conn, %{"uuid" => uuid}) do
|
||||
with {status, %Member{parent: %Actor{} = group, actor: %Actor{domain: nil} = _actor} = member}
|
||||
when status in @ok_statuses <-
|
||||
Cache.get_member_by_uuid_with_preload(uuid),
|
||||
actor <- Map.get(conn.assigns, :actor),
|
||||
true <- actor_applicant_group_member?(group, actor) do
|
||||
json(
|
||||
conn,
|
||||
ActorView.render("member.json", %{
|
||||
member: member,
|
||||
actor_applicant: actor
|
||||
})
|
||||
)
|
||||
else
|
||||
{status, %Member{actor: %Actor{url: domain}, parent: %Actor{} = group, url: url}}
|
||||
when status in @ok_statuses and not is_nil(domain) ->
|
||||
with actor <- Map.get(conn.assigns, :actor),
|
||||
true <- actor_applicant_group_member?(group, actor) do
|
||||
redirect(conn, external: url)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
end
|
||||
|
||||
def outbox(conn, args) do
|
||||
actor_collection(conn, "outbox", args)
|
||||
end
|
||||
|
@ -153,4 +188,15 @@ defmodule Mobilizon.Web.ActivityPubController do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp actor_applicant_group_member?(%Actor{}, nil), do: false
|
||||
|
||||
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),
|
||||
do:
|
||||
Actors.get_member(actor_applicant_id, group_id, [
|
||||
:member,
|
||||
:moderator,
|
||||
:administrator,
|
||||
:creator
|
||||
]) != {:error, :member_not_found}
|
||||
end
|
||||
|
|
|
@ -105,6 +105,7 @@ defmodule Mobilizon.Web.Router do
|
|||
get("/@:name/following", ActivityPubController, :following)
|
||||
get("/@:name/followers", ActivityPubController, :followers)
|
||||
get("/@:name/members", ActivityPubController, :members)
|
||||
get("/member/:uuid", ActivityPubController, :member)
|
||||
end
|
||||
|
||||
scope "/", Mobilizon.Web do
|
||||
|
|
|
@ -23,6 +23,12 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
|||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("member.json", %{member: %Member{} = member}) do
|
||||
member
|
||||
|> Convertible.model_to_as()
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
@doc """
|
||||
Render an actor collection
|
||||
"""
|
||||
|
|
|
@ -438,4 +438,60 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
|||
assert result["totalItems"] == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "/member/:uuid" do
|
||||
test "it returns a json representation of the member", %{conn: conn} do
|
||||
group = insert(:group)
|
||||
remote_actor_2 = insert(:actor, domain: "remote3.tld")
|
||||
insert(:member, actor: remote_actor_2, parent: group, role: :member)
|
||||
|
||||
member =
|
||||
insert(:member,
|
||||
parent: group,
|
||||
url: "https://someremote.url/member/here"
|
||||
)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:actor, remote_actor_2)
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||
|
||||
assert json_response(conn, 200) ==
|
||||
ActorView.render("member.json", %{member: member})
|
||||
end
|
||||
|
||||
test "it redirects for remote comments", %{conn: conn} do
|
||||
group = insert(:group, domain: "remote1.tld")
|
||||
remote_actor = insert(:actor, domain: "remote2.tld")
|
||||
remote_actor_2 = insert(:actor, domain: "remote3.tld")
|
||||
insert(:member, actor: remote_actor_2, parent: group, role: :member)
|
||||
|
||||
member =
|
||||
insert(:member,
|
||||
actor: remote_actor,
|
||||
parent: group,
|
||||
url: "https://someremote.url/member/here"
|
||||
)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:actor, remote_actor_2)
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||
|
||||
assert redirected_to(conn) == "https://someremote.url/member/here"
|
||||
end
|
||||
|
||||
test "it returns 404 if the fetch is not authenticated", %{conn: conn} do
|
||||
member = insert(:member)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||
|
||||
assert json_response(conn, 404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue