Commit 371d96b1 authored by kaniini's avatar kaniini
Browse files

Merge branch 'validate-user-info' into 'develop'

Validate user info

See merge request !465
parents a836d7b9 f18b86fd
Pipeline #4814 passed with stages
in 2 minutes and 30 seconds
......@@ -116,8 +116,8 @@ def add_user_links({subs, text}, mentions) do
subs ++
Enum.map(mentions, fn {match, %User{ap_id: ap_id, info: info}, uuid} ->
ap_id =
if is_binary(info["source_data"]["url"]) do
info["source_data"]["url"]
if is_binary(info.source_data["url"]) do
info.source_data["url"]
else
ap_id
end
......
......@@ -20,7 +20,7 @@ def call(conn, _) do
with token when not is_nil(token) <- token,
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
%User{} = user <- Repo.get(User, user_id),
false <- !!user.info["deactivated"] do
false <- !!user.info.deactivated do
conn
|> assign(:user, user)
else
......
......@@ -6,7 +6,7 @@ def init(options) do
options
end
def call(%{assigns: %{user: %User{info: %{"deactivated" => true}}}} = conn, _) do
def call(%{assigns: %{user: %User{info: %{deactivated: true}}}} = conn, _) do
conn
|> assign(:user, nil)
end
......
......@@ -6,7 +6,7 @@ def init(options) do
options
end
def call(%{assigns: %{user: %User{info: %{"is_admin" => true}}}} = conn, _) do
def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do
conn
end
......
......@@ -19,11 +19,11 @@ defmodule Pleroma.User do
field(:ap_id, :string)
field(:avatar, :map)
field(:local, :boolean, default: true)
field(:info, :map, default: %{})
field(:follower_address, :string)
field(:search_distance, :float, virtual: true)
field(:last_refreshed_at, :naive_datetime)
has_many(:notifications, Notification)
embeds_one(:info, Pleroma.User.Info)
timestamps()
end
......@@ -36,13 +36,13 @@ def avatar_url(user) do
end
def banner_url(user) do
case user.info["banner"] do
case user.info.banner do
%{"url" => [%{"href" => href} | _]} -> href
_ -> "#{Web.base_url()}/images/banner.png"
end
end
def profile_url(%User{info: %{"source_data" => %{"url" => url}}}), do: url
def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
def profile_url(%User{ap_id: ap_id}), do: ap_id
def profile_url(_), do: nil
......@@ -61,9 +61,7 @@ def follow_changeset(struct, params \\ %{}) do
end
def info_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:info])
|> validate_required([:info])
raise "NOT VALID ANYMORE"
end
def user_info(%User{} = user) do
......@@ -71,27 +69,34 @@ def user_info(%User{} = user) do
%{
following_count: length(user.following) - oneself,
note_count: user.info["note_count"] || 0,
follower_count: user.info["follower_count"] || 0,
locked: user.info["locked"] || false,
default_scope: user.info["default_scope"] || "public"
note_count: user.info.note_count,
follower_count: user.info.follower_count,
locked: user.info.locked,
default_scope: user.info.default_scope
}
end
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
def remote_user_creation(params) do
params =
params
|> Map.put(:info, params[:info] || %{})
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
changes =
%User{}
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
|> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
|> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: 5000)
|> validate_length(:name, max: 100)
|> put_change(:local, false)
|> put_embed(:info, info_cng)
if changes.valid? do
case changes.changes[:info]["source_data"] do
case info_cng.changes[:source_data] do
%{"followers" => followers} ->
changes
|> put_change(:follower_address, followers)
......@@ -109,7 +114,7 @@ def remote_user_creation(params) do
def update_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:bio, :name])
|> cast(params, [:bio, :name, :avatar])
|> unique_constraint(:nickname)
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|> validate_length(:bio, max: 5000)
......@@ -121,12 +126,17 @@ def upgrade_changeset(struct, params \\ %{}) do
params
|> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
info_cng =
struct.info
|> User.Info.user_upgrade(params[:info])
struct
|> cast(params, [:bio, :name, :info, :follower_address, :avatar, :last_refreshed_at])
|> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
|> unique_constraint(:nickname)
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|> validate_length(:bio, max: 5000)
|> validate_length(:name, max: 100)
|> put_embed(:info, info_cng)
end
def password_update_changeset(struct, params) do
......@@ -191,7 +201,7 @@ def needs_update?(%User{local: false} = user) do
def needs_update?(_), do: true
def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do
def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{locked: true}}) do
{:ok, follower}
end
......@@ -222,7 +232,7 @@ def follow(%User{} = follower, %User{info: info} = followed) do
ap_followers = followed.follower_address
cond do
following?(follower, followed) or info["deactivated"] ->
following?(follower, followed) or info.deactivated ->
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
deny_follow_blocked and blocks?(followed, follower) ->
......@@ -274,7 +284,7 @@ def following?(%User{} = follower, %User{} = followed) do
end
def locked?(%User{} = user) do
user.info["locked"] || false
user.info.locked || false
end
def get_by_ap_id(ap_id) do
......@@ -411,22 +421,23 @@ def get_follow_requests(%User{} = user) do
end
def increase_note_count(%User{} = user) do
note_count = (user.info["note_count"] || 0) + 1
new_info = Map.put(user.info, "note_count", note_count)
info_cng = User.Info.add_to_note_count(user.info, 1)
cs = info_changeset(user, %{info: new_info})
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def decrease_note_count(%User{} = user) do
note_count = user.info["note_count"] || 0
note_count = if note_count <= 0, do: 0, else: note_count - 1
new_info = Map.put(user.info, "note_count", note_count)
info_cng = User.Info.add_to_note_count(user.info, -1)
cs = info_changeset(user, %{info: new_info})
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def update_note_count(%User{} = user) do
......@@ -439,11 +450,13 @@ def update_note_count(%User{} = user) do
note_count = Repo.one(note_count_query)
new_info = Map.put(user.info, "note_count", note_count)
info_cng = User.Info.set_note_count(user.info, note_count)
cs = info_changeset(user, %{info: new_info})
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def update_follower_count(%User{} = user) do
......@@ -457,11 +470,15 @@ def update_follower_count(%User{} = user) do
follower_count = Repo.one(follower_count_query)
new_info = Map.put(user.info, "follower_count", follower_count)
info_cng =
user.info
|> User.Info.set_follower_count(follower_count)
cs = info_changeset(user, %{info: new_info})
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def get_users_from_set_query(ap_ids, false) do
......@@ -545,12 +562,15 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
unfollow(blocked, blocker)
end
blocks = blocker.info["blocks"] || []
new_blocks = Enum.uniq([ap_id | blocks])
new_info = Map.put(blocker.info, "blocks", new_blocks)
info_cng =
blocker.info
|> User.Info.add_to_block(ap_id)
cng =
change(blocker)
|> put_embed(:info, info_cng)
cs = User.info_changeset(blocker, %{info: new_info})
update_and_set_cache(cs)
update_and_set_cache(cng)
end
# helper to handle the block given only an actor's AP id
......@@ -558,18 +578,21 @@ def block(blocker, %{ap_id: ap_id}) do
block(blocker, User.get_by_ap_id(ap_id))
end
def unblock(user, %{ap_id: ap_id}) do
blocks = user.info["blocks"] || []
new_blocks = List.delete(blocks, ap_id)
new_info = Map.put(user.info, "blocks", new_blocks)
def unblock(blocker, %{ap_id: ap_id}) do
info_cng =
blocker.info
|> User.Info.remove_from_block(ap_id)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
cng =
change(blocker)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end
def blocks?(user, %{ap_id: ap_id}) do
blocks = user.info["blocks"] || []
domain_blocks = user.info["domain_blocks"] || []
blocks = user.info.blocks
domain_blocks = user.info.domain_blocks
%{host: host} = URI.parse(ap_id)
Enum.member?(blocks, ap_id) ||
......@@ -579,21 +602,27 @@ def blocks?(user, %{ap_id: ap_id}) do
end
def block_domain(user, domain) do
domain_blocks = user.info["domain_blocks"] || []
new_blocks = Enum.uniq([domain | domain_blocks])
new_info = Map.put(user.info, "domain_blocks", new_blocks)
info_cng =
user.info
|> User.Info.add_to_domain_block(domain)
cng =
change(user)
|> put_embed(:info, info_cng)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def unblock_domain(user, domain) do
blocks = user.info["domain_blocks"] || []
new_blocks = List.delete(blocks, domain)
new_info = Map.put(user.info, "domain_blocks", new_blocks)
info_cng =
user.info
|> User.Info.remove_from_domain_block(domain)
cng =
change(user)
|> put_embed(:info, info_cng)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
update_and_set_cache(cng)
end
def local_user_query() do
......@@ -613,9 +642,13 @@ def moderator_user_query() do
end
def deactivate(%User{} = user, status \\ true) do
new_info = Map.put(user.info, "deactivated", status)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
info_cng = User.Info.set_activation_status(user.info, status)
cng =
change(user)
|> put_embed(:info, info_cng)
update_and_set_cache(cng)
end
def delete(%User{} = user) do
......@@ -649,7 +682,7 @@ def delete(%User{} = user) do
{:ok, user}
end
def html_filter_policy(%User{info: %{"no_rich_text" => true}}) do
def html_filter_policy(%User{info: %{no_rich_text: true}}) do
Pleroma.HTML.Scrubber.TwitterText
end
......@@ -683,7 +716,7 @@ def get_or_create_instance_user do
user
else
changes =
%User{}
%User{info: %User.Info{}}
|> cast(%{}, [:ap_id, :nickname, :local])
|> put_change(:ap_id, relay_uri)
|> put_change(:nickname, nil)
......@@ -697,7 +730,7 @@ def get_or_create_instance_user do
# AP style
def public_key_from_info(%{
"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
}) do
key =
:public_key.pem_decode(public_key_pem)
......@@ -708,7 +741,7 @@ def public_key_from_info(%{
end
# OStatus Magic Key
def public_key_from_info(%{"magic_key" => magic_key}) do
def public_key_from_info(%{magic_key: magic_key}) do
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
end
......@@ -730,11 +763,12 @@ def insert_or_update_user(data) do
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
cs = User.remote_user_creation(data)
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
end
def ap_enabled?(%User{local: true}), do: true
def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
def ap_enabled?(%User{info: info}), do: info.ap_enabled
def ap_enabled?(_), do: false
def get_or_fetch(uri_or_nickname) do
......
defmodule Pleroma.User.Info do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field(:banner, :map, default: %{})
field(:background, :string, default: nil)
field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
field(:locked, :boolean, default: false)
field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: [])
field(:domain_blocks, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false)
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:keys, :string, default: nil)
field(:settings, :map, default: nil)
field(:magic_key, :string, default: nil)
field(:uri, :string, default: nil)
field(:topic, :string, default: nil)
field(:hub, :string, default: nil)
field(:salmon, :string, default: nil)
# Found in the wild
# ap_id -> Where is this used?
# bio -> Where is this used?
# avatar -> Where is this used?
# fqn -> Where is this used?
# host -> Where is this used?
# subject _> Where is this used?
end
def set_activation_status(info, deactivated) do
params = %{deactivated: deactivated}
info
|> cast(params, [:deactivated])
|> validate_required([:deactivated])
end
def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number)
end
def set_note_count(info, number) do
params = %{note_count: Enum.max([0, number])}
info
|> cast(params, [:note_count])
|> validate_required([:note_count])
end
def set_follower_count(info, number) do
params = %{follower_count: Enum.max([0, number])}
info
|> cast(params, [:follower_count])
|> validate_required([:follower_count])
end
def set_blocks(info, blocks) do
params = %{blocks: blocks}
info
|> cast(params, [:blocks])
|> validate_required([:blocks])
end
def add_to_block(info, blocked) do
set_blocks(info, Enum.uniq([blocked | info.blocks]))
end
def remove_from_block(info, blocked) do
set_blocks(info, List.delete(info.blocks, blocked))
end
def set_domain_blocks(info, domain_blocks) do
params = %{domain_blocks: domain_blocks}
info
|> cast(params, [:domain_blocks])
|> validate_required([:domain_blocks])
end
def add_to_domain_block(info, domain_blocked) do
set_domain_blocks(info, Enum.uniq([domain_blocked | info.domain_blocks]))
end
def remove_from_domain_block(info, domain_blocked) do
set_domain_blocks(info, List.delete(info.domain_blocks, domain_blocked))
end
def set_keys(info, keys) do
params = %{keys: keys}
info
|> cast(params, [:keys])
|> validate_required([:keys])
end
def remote_user_creation(info, params) do
info
|> cast(params, [
:ap_enabled,
:source_data,
:banner,
:locked,
:magic_key,
:uri,
:hub,
:topic,
:salmon
])
end
def user_upgrade(info, params) do
info
|> cast(params, [
:ap_enabled,
:source_data,
:banner,
:locked,
:magic_key
])
end
def profile_update(info, params) do
info
|> cast(params, [
:locked,
:no_rich_text,
:default_scope,
:banner
])
end
def mastodon_profile_update(info, params) do
info
|> cast(params, [
:locked,
:banner
])
end
def set_source_data(info, source_data) do
params = %{source_data: source_data}
info
|> cast(params, [:source_data])
|> validate_required([:source_data])
end
def admin_api_update(info, params) do
info
|> cast(params, [
:is_moderator,
:is_admin
])
end
end
......@@ -42,7 +42,7 @@ defp get_recipients(data) do
defp check_actor_is_active(actor) do
if not is_nil(actor) do
with user <- User.get_cached_by_ap_id(actor),
false <- !!user.info["deactivated"] do
false <- user.info.deactivated do
:ok
else
_e -> :reject
......@@ -509,8 +509,8 @@ defp restrict_recent(query, _) do
end
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
blocks = info["blocks"] || []
domain_blocks = info["domain_blocks"] || []
blocks = info.blocks || []
domain_blocks = info.domain_blocks || []
from(
activity in query,
......@@ -676,7 +676,7 @@ def publish(actor, activity) do
remote_inboxes =
(Pleroma.Web.Salmon.remote_users(activity) ++ followers)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{"source_data" => data}} ->
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
end)
|> Enum.uniq()
......
......@@ -447,7 +447,7 @@ def handle_incoming(
update_data =
new_user_data
|> Map.take([:name, :bio, :avatar])
|> Map.put(:info, Map.merge(actor.info, %{"banner" => banner, "locked" => locked}))
|> Map.put(:info, %{"banner" => banner, "locked" => locked})
actor
|> User.upgrade_changeset(update_data)
......@@ -850,10 +850,6 @@ defp user_upgrade_task(user) do
def upgrade_user_from_ap_id(ap_id, async \\ true) do
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
data =
data
|> Map.put(:info, Map.merge(user.info, data[:info]))
already_ap = User.ap_enabled?(user)