Make sure actor usernames are unique

Closes #72

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-02-25 18:35:00 +01:00
parent 3507438f17
commit 8efb1af306
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
2 changed files with 33 additions and 7 deletions

View file

@ -50,7 +50,7 @@ defmodule Mobilizon.Actors.Actor do
field(:suspended, :boolean, default: false) field(:suspended, :boolean, default: false)
field(:avatar_url, :string) field(:avatar_url, :string)
field(:banner_url, :string) field(:banner_url, :string)
# field(:openness, Mobilizon.Actors.ActorOpennesssEnum, default: :moderated) # field(:openness, Mobilizon.Actors.ActorOpennessEnum, default: :moderated)
has_many(:followers, Follower, foreign_key: :target_actor_id) has_many(:followers, Follower, foreign_key: :target_actor_id)
has_many(:followings, Follower, foreign_key: :actor_id) has_many(:followings, Follower, foreign_key: :actor_id)
has_many(:organized_events, Event, foreign_key: :organizer_actor_id) has_many(:organized_events, Event, foreign_key: :organizer_actor_id)
@ -83,6 +83,7 @@ defmodule Mobilizon.Actors.Actor do
:user_id :user_id
]) ])
|> build_urls() |> build_urls()
|> unique_username_validator()
|> validate_required([:preferred_username, :keys, :suspended, :url]) |> validate_required([:preferred_username, :keys, :suspended, :url])
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index) |> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|> unique_constraint(:url, name: :actors_url_index) |> unique_constraint(:url, name: :actors_url_index)
@ -141,6 +142,8 @@ defmodule Mobilizon.Actors.Actor do
:preferred_username, :preferred_username,
:keys :keys
]) ])
# Needed because following constraint can't work for domain null values (local)
|> unique_username_validator()
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index) |> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|> unique_constraint(:url, name: :actors_url_index) |> unique_constraint(:url, name: :actors_url_index)
|> validate_length(:summary, max: 5000) |> validate_length(:summary, max: 5000)
@ -171,6 +174,7 @@ defmodule Mobilizon.Actors.Actor do
|> put_change(:domain, nil) |> put_change(:domain, nil)
|> put_change(:keys, Actors.create_keys()) |> put_change(:keys, Actors.create_keys())
|> put_change(:type, :Group) |> put_change(:type, :Group)
|> unique_username_validator()
|> validate_required([:url, :outbox_url, :inbox_url, :type, :preferred_username]) |> validate_required([:url, :outbox_url, :inbox_url, :type, :preferred_username])
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index) |> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|> unique_constraint(:url, name: :actors_url_index) |> unique_constraint(:url, name: :actors_url_index)
@ -179,16 +183,22 @@ defmodule Mobilizon.Actors.Actor do
|> put_change(:local, true) |> put_change(:local, true)
end end
def unique_username_validator( defp unique_username_validator(
%Ecto.Changeset{changes: %{preferred_username: username}} = changeset %Ecto.Changeset{changes: %{preferred_username: username} = changes} = changeset
) do ) do
if Actors.get_local_actor_by_name(username) do with nil <- Map.get(changes, :domain, nil),
%Actor{preferred_username: _username} <- Actors.get_local_actor_by_name(username) do
changeset |> add_error(:preferred_username, "Username is already taken") changeset |> add_error(:preferred_username, "Username is already taken")
else else
changeset _ -> changeset
end end
end end
# When we don't even have any preferred_username, don't even try validating preferred_username
defp unique_username_validator(changeset) do
changeset
end
@spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t() @spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
defp build_urls(changeset, type \\ :Person) defp build_urls(changeset, type \\ :Person)

View file

@ -35,7 +35,7 @@ defmodule Mobilizon.ActorsTest do
suspended: nil, suspended: nil,
uri: nil, uri: nil,
url: nil, url: nil,
preferred_username: nil preferred_username: "never"
} }
@remote_account_url "https://social.tcit.fr/users/tcit" @remote_account_url "https://social.tcit.fr/users/tcit"
@ -388,6 +388,22 @@ defmodule Mobilizon.ActorsTest do
assert group.preferred_username == "some-title" assert group.preferred_username == "some-title"
end end
test "create_group/1 with an existing profile username fails" do
_actor = insert(:actor, preferred_username: @valid_attrs.preferred_username)
assert {:error,
%Ecto.Changeset{errors: [preferred_username: {"Username is already taken", []}]}} =
Actors.create_group(@valid_attrs)
end
test "create_group/1 with an existing group username fails" do
assert {:ok, %Actor{} = group} = Actors.create_group(@valid_attrs)
assert {:error,
%Ecto.Changeset{errors: [preferred_username: {"Username is already taken", []}]}} =
Actors.create_group(@valid_attrs)
end
test "create_group/1 with invalid data returns error changeset" do test "create_group/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Actors.create_group(@invalid_attrs) assert {:error, %Ecto.Changeset{}} = Actors.create_group(@invalid_attrs)
end end