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,
|
"id" => member.url,
|
||||||
"actor" => member.actor.url,
|
"actor" => member.actor.url,
|
||||||
"object" => member.parent.url,
|
"object" => member.parent.url,
|
||||||
"role" => member.role
|
"role" => to_string(member.role)
|
||||||
}
|
}
|
||||||
end
|
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, Discussions, Events, Posts, Resources, Todos, Tombstone}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Federation.ActivityPub.Relay
|
alias Mobilizon.Federation.ActivityPub.Relay
|
||||||
|
@ -174,6 +174,23 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||||
end)
|
end)
|
||||||
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 """
|
@doc """
|
||||||
Gets a relay.
|
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_resource_by_uuid_with_preload(uuid), to: ActivityPub
|
||||||
defdelegate get_todo_list_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_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_post_by_slug_with_preload(slug), to: ActivityPub
|
||||||
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
|
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
|
||||||
defdelegate get_relay, to: ActivityPub
|
defdelegate get_relay, to: ActivityPub
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
use Mobilizon.Web, :controller
|
use Mobilizon.Web, :controller
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Config}
|
alias Mobilizon.{Actors, Config}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.Federator
|
alias Mobilizon.Federation.ActivityPub.Federator
|
||||||
|
@ -66,6 +66,41 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
actor_collection(conn, "discussions", args)
|
actor_collection(conn, "discussions", args)
|
||||||
end
|
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
|
def outbox(conn, args) do
|
||||||
actor_collection(conn, "outbox", args)
|
actor_collection(conn, "outbox", args)
|
||||||
end
|
end
|
||||||
|
@ -153,4 +188,15 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -105,6 +105,7 @@ defmodule Mobilizon.Web.Router do
|
||||||
get("/@:name/following", ActivityPubController, :following)
|
get("/@:name/following", ActivityPubController, :following)
|
||||||
get("/@:name/followers", ActivityPubController, :followers)
|
get("/@:name/followers", ActivityPubController, :followers)
|
||||||
get("/@:name/members", ActivityPubController, :members)
|
get("/@:name/members", ActivityPubController, :members)
|
||||||
|
get("/member/:uuid", ActivityPubController, :member)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Mobilizon.Web do
|
scope "/", Mobilizon.Web do
|
||||||
|
|
|
@ -23,6 +23,12 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("member.json", %{member: %Member{} = member}) do
|
||||||
|
member
|
||||||
|
|> Convertible.model_to_as()
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Render an actor collection
|
Render an actor collection
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -438,4 +438,60 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
||||||
assert result["totalItems"] == 2
|
assert result["totalItems"] == 2
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue