X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=5f3f63acefdb35e73c25141f53ea2d57a21c060e;hb=32d1e048178a94017c9d8cca79b28272fa6da9f4;hp=d97c19d38926383b53974ae279207d8a9fa97bec;hpb=4f88b1b4353c1216273e462fb687e8b43b257e37;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d97c19d38..55c164272 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -12,6 +12,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Ecto.Multi alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Delivery alias Pleroma.FollowingRelationship @@ -35,7 +36,7 @@ defmodule Pleroma.User do require Logger @type t :: %__MODULE__{} - + @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @@ -94,8 +95,7 @@ defmodule Pleroma.User do field(:source_data, :map, default: %{}) field(:note_count, :integer, default: 0) field(:follower_count, :integer, default: 0) - # Should be filled in only for remote users - field(:following_count, :integer, default: nil) + field(:following_count, :integer, default: 0) field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) @@ -126,15 +126,15 @@ defmodule Pleroma.User do field(:raw_fields, {:array, :map}, default: []) field(:discoverable, :boolean, default: false) field(:invisible, :boolean, default: false) + field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) + field(:actor_type, :string, default: "Person") + field(:also_known_as, {:array, :string}, default: []) - field(:notification_settings, :map, - default: %{ - "followers" => true, - "follows" => true, - "non_follows" => true, - "non_followers" => true - } + embeds_one( + :notification_settings, + Pleroma.User.NotificationSetting, + on_replace: :update ) has_many(:notifications, Notification) @@ -168,8 +168,6 @@ defmodule Pleroma.User do has_many(incoming_relation_source, through: [incoming_relation, :source]) end - field(:info, :map, default: %{}) - # `:blocks` is deprecated (replaced with `blocked_users` relation) field(:blocks, {:array, :string}, default: []) # `:mutes` is deprecated (replaced with `muted_users` relation) @@ -219,28 +217,39 @@ defmodule Pleroma.User do end end - @doc "Returns if the user should be allowed to authenticate" - def auth_active?(%User{deactivated: true}), do: false + @doc "Returns status account" + @spec account_status(User.t()) :: account_status() + def account_status(%User{deactivated: true}), do: :deactivated + def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def auth_active?(%User{confirmation_pending: true}), - do: !Pleroma.Config.get([:instance, :account_activation_required]) + def account_status(%User{confirmation_pending: true}) do + case Config.get([:instance, :account_activation_required]) do + true -> :confirmation_pending + _ -> :active + end + end - def auth_active?(%User{}), do: true + def account_status(%User{}), do: :active + @spec visible_for?(User.t(), User.t() | nil) :: boolean() def visible_for?(user, for_user \\ nil) + def visible_for?(%User{invisible: true}, _), do: false + def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true def visible_for?(%User{} = user, for_user) do - auth_active?(user) || superuser?(for_user) + account_status(user) == :active || superuser?(for_user) end def visible_for?(_, _), do: false + @spec superuser?(User.t()) :: boolean() def superuser?(%User{local: true, is_admin: true}), do: true def superuser?(%User{local: true, is_moderator: true}), do: true def superuser?(_), do: false + @spec invisible?(User.t()) :: boolean() def invisible?(%User{invisible: true}), do: true def invisible?(_), do: false @@ -271,22 +280,6 @@ defmodule Pleroma.User do def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa def ap_following(%User{} = user), do: "#{ap_id(user)}/following" - def user_info(%User{} = user, args \\ %{}) do - following_count = - Map.get(args, :following_count, user.following_count || following_count(user)) - - follower_count = Map.get(args, :follower_count, user.follower_count) - - %{ - note_count: user.note_count, - locked: user.locked, - confirmation_pending: user.confirmation_pending, - default_scope: user.default_scope - } - |> Map.put(:following_count, following_count) - |> Map.put(:follower_count, follower_count) - end - def follow_state(%User{} = user, %User{} = target) do case Utils.fetch_latest_follow(user, target) do %{data: %{"state" => state}} -> state @@ -305,10 +298,6 @@ defmodule Pleroma.User do Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state) end - def set_info_cache(user, args) do - Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args)) - end - @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t() def restrict_deactivated(query) do from(u in query, where: u.deactivated != ^true) @@ -339,7 +328,6 @@ defmodule Pleroma.User do params = params - |> Map.put(:info, params[:info] || %{}) |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) |> truncate_fields_param() @@ -368,7 +356,9 @@ defmodule Pleroma.User do :fields, :following_count, :discoverable, - :invisible + :invisible, + :actor_type, + :also_known_as ] ) |> validate_required([:name, :ap_id]) @@ -410,13 +400,16 @@ defmodule Pleroma.User do :hide_followers_count, :hide_follows_count, :hide_favorites, + :allow_following_move, :background, :show_role, :skip_thread_containment, :fields, :raw_fields, :pleroma_settings_store, - :discoverable + :discoverable, + :actor_type, + :also_known_as ] ) |> unique_constraint(:nickname) @@ -454,9 +447,12 @@ defmodule Pleroma.User do :hide_follows, :fields, :hide_followers, + :allow_following_move, :discoverable, :hide_followers_count, - :hide_follows_count + :hide_follows_count, + :actor_type, + :also_known_as ] ) |> unique_constraint(:nickname) @@ -587,6 +583,10 @@ defmodule Pleroma.User do end end + def try_send_confirmation_email(users) do + Enum.each(users, &try_send_confirmation_email/1) + end + def needs_update?(%User{local: true}), do: false def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true @@ -617,14 +617,9 @@ defmodule Pleroma.User do @doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} def follow_all(follower, followeds) do - followeds = - Enum.reject(followeds, fn followed -> - blocks?(follower, followed) || blocks?(followed, follower) - end) - - Enum.each(followeds, &follow(follower, &1, "accept")) - - Enum.each(followeds, &update_follower_count/1) + followeds + |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) + |> Enum.each(&follow(follower, &1, "accept")) set_cache(follower) end @@ -644,32 +639,56 @@ defmodule Pleroma.User do true -> FollowingRelationship.follow(follower, followed, state) - follower = maybe_update_following_count(follower) - {:ok, _} = update_follower_count(followed) - set_cache(follower) + follower + |> update_following_count() + |> set_cache() end end - def unfollow(%User{} = follower, %User{} = followed) do - if following?(follower, followed) and follower.ap_id != followed.ap_id do - FollowingRelationship.unfollow(follower, followed) + def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do + {:error, "Not subscribed!"} + end - follower = maybe_update_following_count(follower) + def unfollow(%User{} = follower, %User{} = followed) do + case get_follow_state(follower, followed) do + state when state in ["accept", "pending"] -> + FollowingRelationship.unfollow(follower, followed) + {:ok, followed} = update_follower_count(followed) - {:ok, followed} = update_follower_count(followed) + {:ok, follower} = + follower + |> update_following_count() + |> set_cache() - set_cache(follower) + {:ok, follower, Utils.fetch_latest_follow(follower, followed)} - {:ok, follower, Utils.fetch_latest_follow(follower, followed)} - else - {:error, "Not subscribed!"} + nil -> + {:error, "Not subscribed!"} end end defdelegate following?(follower, followed), to: FollowingRelationship + def get_follow_state(%User{} = follower, %User{} = following) do + following_relationship = FollowingRelationship.get(follower, following) + + case {following_relationship, following.local} do + {nil, false} -> + case Utils.fetch_latest_follow(follower, following) do + %{data: %{"state" => state}} when state in ["pending", "accept"] -> state + _ -> nil + end + + {%{state: state}, _} -> + state + + {nil, _} -> + nil + end + end + def locked?(%User{} = user) do user.locked || false end @@ -710,7 +729,6 @@ defmodule Pleroma.User do def set_cache(%User{} = user) do Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) Cachex.put(:user_cache, "nickname:#{user.nickname}", user) - Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) {:ok, user} end @@ -729,12 +747,20 @@ defmodule Pleroma.User do def invalidate_cache(user) do Cachex.del(:user_cache, "ap_id:#{user.ap_id}") Cachex.del(:user_cache, "nickname:#{user.nickname}") - Cachex.del(:user_cache, "user_info:#{user.id}") end + @spec get_cached_by_ap_id(String.t()) :: User.t() | nil def get_cached_by_ap_id(ap_id) do key = "ap_id:#{ap_id}" - Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end) + + with {:ok, nil} <- Cachex.get(:user_cache, key), + user when not is_nil(user) <- get_by_ap_id(ap_id), + {:ok, true} <- Cachex.put(:user_cache, key, user) do + user + else + {:ok, user} -> user + nil -> nil + end end def get_cached_by_id(id) do @@ -797,11 +823,6 @@ defmodule Pleroma.User do get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email) end - def get_cached_user_info(user) do - key = "user_info:#{user.id}" - Cachex.fetch!(:user_cache, key, fn -> user_info(user) end) - end - def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname) def get_or_fetch_by_nickname(nickname) do @@ -841,14 +862,14 @@ defmodule Pleroma.User do @spec get_followers_query(User.t()) :: Ecto.Query.t() def get_followers_query(user), do: get_followers_query(user, nil) - @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} + @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} def get_followers(user, page \\ nil) do user |> get_followers_query(page) |> Repo.all() end - @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} + @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} def get_external_followers(user, page \\ nil) do user |> get_followers_query(page) @@ -883,6 +904,13 @@ defmodule Pleroma.User do |> Repo.all() end + def get_friends_ap_ids(user) do + user + |> get_friends_query(nil) + |> select([u], u.ap_id) + |> Repo.all() + end + def get_friends_ids(user, page \\ nil) do user |> get_friends_query(page) @@ -990,8 +1018,8 @@ defmodule Pleroma.User do end end - @spec maybe_update_following_count(User.t()) :: User.t() - def maybe_update_following_count(%User{local: false} = user) do + @spec update_following_count(User.t()) :: User.t() + def update_following_count(%User{local: false} = user) do if Pleroma.Config.get([:instance, :external_user_synchronization]) do maybe_fetch_follow_information(user) else @@ -999,7 +1027,13 @@ defmodule Pleroma.User do end end - def maybe_update_following_count(user), do: user + def update_following_count(%User{local: true} = user) do + following_count = FollowingRelationship.following_count(user) + + user + |> follow_information_changeset(%{following_count: following_count}) + |> Repo.update!() + end def set_unread_conversation_count(%User{local: true} = user) do unread_query = Participation.unread_conversation_count_for_user(user) @@ -1151,7 +1185,8 @@ defmodule Pleroma.User do def blocks?(nil, _), do: false def blocks?(%User{} = user, %User{} = target) do - blocks_user?(user, target) || blocks_domain?(user, target) + blocks_user?(user, target) || + (!User.following?(user, target) && blocks_domain?(user, target)) end def blocks_user?(%User{} = user, %User{} = target) do @@ -1219,7 +1254,12 @@ defmodule Pleroma.User do def deactivate(%User{} = user, status) do with {:ok, user} <- set_activation_status(user, status) do - Enum.each(get_followers(user), &invalidate_cache/1) + user + |> get_followers() + |> Enum.filter(& &1.local) + |> Enum.each(fn follower -> + follower |> update_following_count() |> set_cache() + end) # Only update local user counts, remote will be update during the next pull. user @@ -1232,20 +1272,9 @@ defmodule Pleroma.User do end def update_notification_settings(%User{} = user, settings) do - settings = - settings - |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end) - |> Map.new() - - notification_settings = - user.notification_settings - |> Map.merge(settings) - |> Map.take(["followers", "follows", "non_follows", "non_followers"]) - - params = %{notification_settings: notification_settings} - user - |> cast(params, [:notification_settings]) + |> cast(%{notification_settings: settings}, []) + |> cast_embed(:notification_settings) |> validate_required([:notification_settings]) |> update_and_set_cache() end @@ -1284,7 +1313,6 @@ defmodule Pleroma.User do Repo.delete(user) end - @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:fetch_initial_posts, %User{} = user) do pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) @@ -1316,7 +1344,6 @@ defmodule Pleroma.User do ) end - @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} def perform(:follow_import, %User{} = follower, followed_identifiers) when is_list(followed_identifiers) do Enum.map( @@ -1348,7 +1375,7 @@ defmodule Pleroma.User do def external_users(opts \\ []) do query = external_users_query() - |> select([u], struct(u, [:id, :ap_id, :info])) + |> select([u], struct(u, [:id, :ap_id])) query = if opts[:max_id], @@ -1439,23 +1466,51 @@ defmodule Pleroma.User do end end - @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing." - def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do - with %User{} = user <- get_cached_by_ap_id(uri) do - user - else - _ -> - {:ok, user} = - %User{} - |> cast(%{}, [:ap_id, :nickname, :local]) - |> put_change(:ap_id, uri) - |> put_change(:nickname, nickname) - |> put_change(:local, true) - |> put_change(:follower_address, uri <> "/followers") - |> Repo.insert() + @doc """ + Creates an internal service actor by URI if missing. + Optionally takes nickname for addressing. + """ + @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil + def get_or_create_service_actor_by_ap_id(uri, nickname) do + {_, user} = + case get_cached_by_ap_id(uri) do + nil -> + with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do + Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}") + {:error, nil} + end - user - end + %User{invisible: false} = user -> + set_invisible(user) + + user -> + {:ok, user} + end + + user + end + + @spec set_invisible(User.t()) :: {:ok, User.t()} + defp set_invisible(user) do + user + |> change(%{invisible: true}) + |> update_and_set_cache() + end + + @spec create_service_actor(String.t(), String.t()) :: + {:ok, User.t()} | {:error, Ecto.Changeset.t()} + defp create_service_actor(uri, nickname) do + %User{ + invisible: true, + local: true, + ap_id: uri, + nickname: nickname, + follower_address: uri <> "/followers" + } + |> change + |> unique_constraint(:nickname) + |> Repo.insert() + |> set_cache() end # AP style @@ -1487,7 +1542,7 @@ defmodule Pleroma.User do data |> Map.put(:name, blank?(data[:name]) || data[:nickname]) |> remote_user_creation() - |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname) + |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname) |> set_cache() end @@ -1697,6 +1752,11 @@ defmodule Pleroma.User do |> update_and_set_cache() end + @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}] + def toggle_confirmation(users) do + Enum.map(users, &toggle_confirmation/1) + end + def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do mascot end @@ -1863,6 +1923,12 @@ defmodule Pleroma.User do |> update_and_set_cache() end + @doc "Signs user out of all applications" + def global_sign_out(user) do + OAuth.Authorization.delete_user_authorizations(user) + OAuth.Token.delete_user_tokens(user) + end + def mascot_update(user, url) do user |> cast(%{mascot: url}, [:mascot])