Introduce relay

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-07-30 16:40:59 +02:00
parent 56467301a1
commit c51115bdbe
54 changed files with 3100 additions and 1038 deletions

View File

@@ -163,7 +163,6 @@ defmodule Mobilizon.Actors.Actor do
])
|> validate_required([
:url,
:outbox_url,
:inbox_url,
:type,
:domain,
@@ -184,6 +183,44 @@ defmodule Mobilizon.Actors.Actor do
changes
end
def relay_creation(%{url: url, preferred_username: preferred_username} = _params) do
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
pem = [entry] |> :public_key.pem_encode() |> String.trim_trailing()
vars = %{
"name" => Mobilizon.CommonConfig.get([:instance, :name], "Mobilizon"),
"summary" =>
Mobilizon.CommonConfig.get(
[:instance, :description],
"An internal service actor for this Mobilizon instance"
),
"url" => url,
"keys" => pem,
"preferred_username" => preferred_username,
"domain" => nil,
"inbox_url" => "#{MobilizonWeb.Endpoint.url()}/inbox",
"followers_url" => "#{url}/followers",
"following_url" => "#{url}/following",
"shared_inbox_url" => "#{MobilizonWeb.Endpoint.url()}/inbox",
"type" => :Application
}
cast(%Actor{}, vars, [
:type,
:name,
:summary,
:url,
:keys,
:preferred_username,
:domain,
:inbox_url,
:followers_url,
:following_url,
:shared_inbox_url
])
end
@doc """
Changeset for group creation
"""
@@ -240,6 +277,14 @@ defmodule Mobilizon.Actors.Actor do
:outbox_url,
build_url(username, :outbox)
)
|> put_change(
:followers_url,
build_url(username, :followers)
)
|> put_change(
:following_url,
build_url(username, :following)
)
|> put_change(
:inbox_url,
build_url(username, :inbox)
@@ -325,18 +370,30 @@ defmodule Mobilizon.Actors.Actor do
%{total: Task.await(total), elements: Task.await(elements)}
end
@spec get_full_followers(struct()) :: list()
def get_full_followers(%Actor{id: actor_id} = _actor) do
Repo.all(
from(
a in Actor,
join: f in Follower,
on: a.id == f.actor_id,
where: f.target_actor_id == ^actor_id
)
defp get_full_followers_query(%Actor{id: actor_id} = _actor) do
from(
a in Actor,
join: f in Follower,
on: a.id == f.actor_id,
where: f.target_actor_id == ^actor_id
)
end
@spec get_full_followers(struct()) :: list()
def get_full_followers(%Actor{} = actor) do
actor
|> get_full_followers_query()
|> Repo.all()
end
@spec get_full_external_followers(struct()) :: list()
def get_full_external_followers(%Actor{} = actor) do
actor
|> get_full_followers_query()
|> where([a], not is_nil(a.domain))
|> Repo.all()
end
@doc """
Get followings from an actor
@@ -404,18 +461,19 @@ defmodule Mobilizon.Actors.Actor do
Make an actor follow another
"""
@spec follow(struct(), struct(), boolean()) :: Follower.t() | {:error, String.t()}
def follow(%Actor{} = followed, %Actor{} = follower, approved \\ true) do
def follow(%Actor{} = followed, %Actor{} = follower, url \\ nil, approved \\ true) do
with {:suspended, false} <- {:suspended, followed.suspended},
# Check if followed has blocked follower
{:already_following, false} <- {:already_following, following?(follower, followed)} do
do_follow(follower, followed, approved)
do_follow(follower, followed, approved, url)
else
{:already_following, %Follower{}} ->
{:error,
{:error, :already_following,
"Could not follow actor: you are already following #{followed.preferred_username}"}
{:suspended, _} ->
{:error, "Could not follow actor: #{followed.preferred_username} has been suspended"}
{:error, :suspended,
"Could not follow actor: #{followed.preferred_username} has been suspended"}
end
end
@@ -433,13 +491,20 @@ defmodule Mobilizon.Actors.Actor do
end
end
@spec do_follow(struct(), struct(), boolean) ::
@spec do_follow(struct(), struct(), boolean(), String.t()) ::
{:ok, Follower.t()} | {:error, Ecto.Changeset.t()}
defp do_follow(%Actor{} = follower, %Actor{} = followed, approved) do
defp do_follow(%Actor{} = follower, %Actor{} = followed, approved, url) do
Logger.info(
"Making #{follower.preferred_username} follow #{followed.preferred_username} (approved: #{
approved
})"
)
Actors.create_follower(%{
"actor_id" => follower.id,
"target_actor_id" => followed.id,
"approved" => approved
"approved" => approved,
"url" => url
})
end

View File

@@ -297,7 +297,7 @@ defmodule Mobilizon.Actors do
{:ok, actor}
err ->
Logger.error(inspect(err))
Logger.debug(inspect(err))
{:error, err}
end
end
@@ -475,16 +475,16 @@ defmodule Mobilizon.Actors do
@spec get_or_fetch_by_url(String.t(), bool()) :: {:ok, Actor.t()} | {:error, String.t()}
def get_or_fetch_by_url(url, preload \\ false) do
case get_actor_by_url(url, preload) do
{:ok, actor} ->
{:ok, %Actor{} = actor} ->
{:ok, actor}
_ ->
case ActivityPub.make_actor_from_url(url, preload) do
{:ok, actor} ->
{:ok, %Actor{} = actor} ->
{:ok, actor}
_ ->
Logger.error("Could not fetch by AP id")
Logger.warn("Could not fetch by AP id")
{:error, "Could not fetch by AP id"}
end
end
@@ -655,6 +655,18 @@ defmodule Mobilizon.Actors do
end
end
def get_or_create_service_actor_by_url(url, preferred_username \\ "relay") do
case get_actor_by_url(url) do
{:ok, %Actor{} = actor} ->
{:ok, actor}
_ ->
%{url: url, preferred_username: preferred_username}
|> Actor.relay_creation()
|> Repo.insert()
end
end
alias Mobilizon.Actors.Member
@doc """
@@ -895,7 +907,7 @@ defmodule Mobilizon.Actors do
end
@doc """
Get a follower by the followed actor and following actor
Get a follow by the followed actor and following actor
"""
@spec get_follower(Actor.t(), Actor.t()) :: Follower.t()
def get_follower(%Actor{id: followed_id}, %Actor{id: follower_id}) do
@@ -904,6 +916,19 @@ defmodule Mobilizon.Actors do
)
end
@doc """
Get a follow by the followed actor and following actor
"""
@spec get_follow_by_url(String.t()) :: Follower.t()
def get_follow_by_url(url) do
Repo.one(
from(f in Follower,
where: f.url == ^url,
preload: [:actor, :target_actor]
)
)
end
@doc """
Creates a follower.
@@ -1009,7 +1034,7 @@ defmodule Mobilizon.Actors do
{:error, error} ->
Logger.error("Error while removing an upload file")
Logger.error(inspect(error))
Logger.debug(inspect(error))
{:ok, actor}
end
end

View File

@@ -7,9 +7,11 @@ defmodule Mobilizon.Actors.Follower do
alias Mobilizon.Actors.Follower
alias Mobilizon.Actors.Actor
@primary_key {:id, :binary_id, autogenerate: true}
schema "followers" do
field(:approved, :boolean, default: false)
field(:score, :integer, default: 1000)
field(:url, :string)
belongs_to(:target_actor, Actor)
belongs_to(:actor, Actor)
end
@@ -17,8 +19,34 @@ defmodule Mobilizon.Actors.Follower do
@doc false
def changeset(%Follower{} = member, attrs) do
member
|> cast(attrs, [:score, :approved, :target_actor_id, :actor_id])
|> validate_required([:score, :approved, :target_actor_id, :actor_id])
|> cast(attrs, [:url, :approved, :target_actor_id, :actor_id])
|> generate_url()
|> validate_required([:url, :approved, :target_actor_id, :actor_id])
|> unique_constraint(:target_actor_id, name: :followers_actor_target_actor_unique_index)
end
# If there's a blank URL that's because we're doing the first insert
defp generate_url(%Ecto.Changeset{data: %Follower{url: nil}} = changeset) do
case fetch_change(changeset, :url) do
{:ok, _url} -> changeset
:error -> do_generate_url(changeset)
end
end
# Most time just go with the given URL
defp generate_url(%Ecto.Changeset{} = changeset), do: changeset
defp do_generate_url(%Ecto.Changeset{} = changeset) do
uuid = Ecto.UUID.generate()
changeset
|> put_change(
:url,
"#{MobilizonWeb.Endpoint.url()}/follow/#{uuid}"
)
|> put_change(
:id,
uuid
)
end
end

View File

@@ -32,7 +32,8 @@ defmodule Mobilizon.Events do
:tracks,
:tags,
:participants,
:physical_address
:physical_address,
:picture
]
)
|> paginate(page, limit)
@@ -248,7 +249,8 @@ defmodule Mobilizon.Events do
:tracks,
:tags,
:participants,
:physical_address
:physical_address,
:picture
]
)
)