X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=2c343eb227b7d1a8e2cb791b830c68cdbf17854a;hb=4ec2fb967e0eb0559e39a6d698107f6af4d7a891;hp=896bab14021b1a228f286b3d2ec44626c5989de0;hpb=0ddee13c832df08da65ad0b1b7c11eea18afba26;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 896bab140..ff3ec1197 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -9,7 +9,6 @@ defmodule Pleroma.User do import Ecto.Query import Ecto, only: [assoc: 2] - alias Comeonin.Pbkdf2 alias Ecto.Multi alias Pleroma.Activity alias Pleroma.Config @@ -20,6 +19,7 @@ defmodule Pleroma.User do alias Pleroma.Formatter alias Pleroma.HTML alias Pleroma.Keys + alias Pleroma.MFA alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Registration @@ -29,7 +29,9 @@ defmodule Pleroma.User do alias Pleroma.UserRelationship alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils @@ -77,6 +79,7 @@ defmodule Pleroma.User do schema "users" do field(:bio, :string) + field(:raw_bio, :string) field(:email, :string) field(:name, :string) field(:nickname, :string) @@ -113,7 +116,6 @@ defmodule Pleroma.User do field(:is_admin, :boolean, default: false) field(:show_role, :boolean, default: true) field(:settings, :map, default: nil) - field(:magic_key, :string, default: nil) field(:uri, Types.Uri, default: nil) field(:hide_followers_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false) @@ -189,6 +191,12 @@ defmodule Pleroma.User do # `:subscribers` is deprecated (replaced with `subscriber_users` relation) field(:subscribers, {:array, :string}, default: []) + embeds_one( + :multi_factor_authentication_settings, + MFA.Settings, + on_replace: :delete + ) + timestamps() end @@ -298,8 +306,13 @@ defmodule Pleroma.User do def avatar_url(user, options \\ []) do case user.avatar do - %{"url" => [%{"href" => href} | _]} -> href - _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png" + %{"url" => [%{"href" => href} | _]} -> + href + + _ -> + unless options[:no_default] do + Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png") + end end end @@ -387,7 +400,6 @@ defmodule Pleroma.User do :banner, :locked, :last_refreshed_at, - :magic_key, :uri, :follower_address, :following_address, @@ -421,6 +433,7 @@ defmodule Pleroma.User do params, [ :bio, + :raw_bio, :name, :emoji, :avatar, @@ -527,9 +540,10 @@ defmodule Pleroma.User do |> delete_change(:also_known_as) |> unique_constraint(:email) |> validate_format(:email, @email_regex) + |> validate_inclusion(:actor_type, ["Person", "Service"]) end - @spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()} def update_as_admin(user, params) do params = Map.put(params, "password_confirmation", params["password"]) changeset = update_as_admin_changeset(user, params) @@ -550,7 +564,7 @@ defmodule Pleroma.User do |> put_change(:password_reset_pending, false) end - @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()} def reset_password(%User{} = user, params) do reset_password(user, user, params) end @@ -595,7 +609,16 @@ defmodule Pleroma.User do struct |> confirmation_changeset(need_confirmation: need_confirmation?) - |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji]) + |> cast(params, [ + :bio, + :raw_bio, + :email, + :name, + :nickname, + :password, + :password_confirmation, + :emoji + ]) |> validate_required([:name, :nickname, :password, :password_confirmation]) |> validate_confirmation(:password) |> unique_constraint(:email) @@ -688,6 +711,8 @@ defmodule Pleroma.User do def needs_update?(_), do: true @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} + + # "Locked" (self-locked) users demand explicit authorization of follow requests def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do follow(follower, followed, :follow_pending) end @@ -741,7 +766,19 @@ defmodule Pleroma.User do {:error, "Not subscribed!"} end + @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()} def unfollow(%User{} = follower, %User{} = followed) do + case do_unfollow(follower, followed) do + {:ok, follower, followed} -> + {:ok, follower, Utils.fetch_latest_follow(follower, followed)} + + error -> + error + end + end + + @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()} + defp do_unfollow(%User{} = follower, %User{} = followed) do case get_follow_state(follower, followed) do state when state in [:follow_pending, :follow_accept] -> FollowingRelationship.unfollow(follower, followed) @@ -752,7 +789,7 @@ defmodule Pleroma.User do |> update_following_count() |> set_cache() - {:ok, follower, Utils.fetch_latest_follow(follower, followed)} + {:ok, follower, followed} nil -> {:error, "Not subscribed!"} @@ -830,6 +867,7 @@ 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, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user)) {:ok, user} end @@ -845,9 +883,22 @@ defmodule Pleroma.User do end end + def get_user_friends_ap_ids(user) do + from(u in User.get_friends_query(user), select: u.ap_id) + |> Repo.all() + end + + @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()] + def get_cached_user_friends_ap_ids(user) do + Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ -> + get_user_friends_ap_ids(user) + end) + end + 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, "friends_ap_ids:#{user.ap_id}") end @spec get_cached_by_ap_id(String.t()) :: User.t() | nil @@ -911,6 +962,7 @@ defmodule Pleroma.User do end end + @spec get_by_nickname(String.t()) :: User.t() | nil def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) || if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do @@ -1168,8 +1220,9 @@ defmodule Pleroma.User do def increment_unread_conversation_count(_, user), do: {:ok, user} - @spec get_users_from_set([String.t()], boolean()) :: [User.t()] - def get_users_from_set(ap_ids, local_only \\ true) do + @spec get_users_from_set([String.t()], keyword()) :: [User.t()] + def get_users_from_set(ap_ids, opts \\ []) do + local_only = Keyword.get(opts, :local_only, true) criteria = %{ap_id: ap_ids, deactivated: false} criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria @@ -1178,8 +1231,12 @@ defmodule Pleroma.User do end @spec get_recipients_from_activity(Activity.t()) :: [User.t()] - def get_recipients_from_activity(%Activity{recipients: to}) do - User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false}) + def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do + to = [actor | to] + + query = User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false}) + + query |> Repo.all() end @@ -1375,15 +1432,13 @@ defmodule Pleroma.User do user |> get_followers() |> Enum.filter(& &1.local) - |> Enum.each(fn follower -> - follower |> update_following_count() |> set_cache() - end) + |> Enum.each(&set_cache(update_following_count(&1))) # Only update local user counts, remote will be update during the next pull. user |> get_friends() |> Enum.filter(& &1.local) - |> Enum.each(&update_follower_count/1) + |> Enum.each(&do_unfollow(user, &1)) {:ok, user} end @@ -1405,12 +1460,29 @@ defmodule Pleroma.User do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end + defp delete_and_invalidate_cache(%User{} = user) do + invalidate_cache(user) + Repo.delete(user) + end + + defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) + + defp delete_or_deactivate(%User{local: true} = user) do + status = account_status(user) + + if status == :confirmation_pending do + delete_and_invalidate_cache(user) + else + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() + end + end + def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do - {:ok, _user} = ActivityPub.delete(user) - # Remove all relationships user |> get_followers() @@ -1427,8 +1499,11 @@ defmodule Pleroma.User do end) delete_user_activities(user) - invalidate_cache(user) - Repo.delete(user) + delete_notifications_from_user_activities(user) + + delete_outgoing_pending_follow_requests(user) + + delete_or_deactivate(user) end def perform(:deactivate_async, user, status), do: deactivate(user, status) @@ -1513,38 +1588,56 @@ defmodule Pleroma.User do }) end - def delete_user_activities(%User{ap_id: ap_id}) do + def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do + Notification + |> join(:inner, [n], activity in assoc(n, :activity)) + |> where([n, a], fragment("? = ?", a.actor, ^ap_id)) + |> Repo.delete_all() + end + + def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id |> Activity.Queries.by_actor() |> RepoStreamer.chunk_stream(50) - |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end) + |> Stream.each(fn activities -> + Enum.each(activities, fn activity -> delete_activity(activity, user) end) + end) |> Stream.run() end - defp delete_activity(%{data: %{"type" => "Create"}} = activity) do - activity - |> Object.normalize() - |> ActivityPub.delete() - end + defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do + with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)}, + {:ok, delete_data, _} <- Builder.delete(user, object) do + Pipeline.common_pipeline(delete_data, local: user.local) + else + {:find_object, nil} -> + # We have the create activity, but not the object, it was probably pruned. + # Insert a tombstone and try again + with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object), + {:ok, _tombstone} <- Object.create(tombstone_data) do + delete_activity(activity, user) + end - defp delete_activity(%{data: %{"type" => "Like"}} = activity) do - object = Object.normalize(activity) + e -> + Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}") + Logger.error("Error: #{inspect(e)}") + end + end - activity.actor - |> get_cached_by_ap_id() - |> ActivityPub.unlike(object) + defp delete_activity(%{data: %{"type" => type}} = activity, user) + when type in ["Like", "Announce"] do + {:ok, undo, _} = Builder.undo(user, activity) + Pipeline.common_pipeline(undo, local: user.local) end - defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do - object = Object.normalize(activity) + defp delete_activity(_activity, _user), do: "Doing nothing" - activity.actor - |> get_cached_by_ap_id() - |> ActivityPub.unannounce(object) + defp delete_outgoing_pending_follow_requests(user) do + user + |> FollowingRelationship.outgoing_pending_follow_requests_query() + |> Repo.delete_all() end - defp delete_activity(_activity), do: "Doing nothing" - def html_filter_policy(%User{no_rich_text: true}) do Pleroma.HTML.Scrubber.TwitterText end @@ -1554,12 +1647,19 @@ defmodule Pleroma.User do def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) def get_or_fetch_by_ap_id(ap_id) do - user = get_cached_by_ap_id(ap_id) + cached_user = get_cached_by_ap_id(ap_id) - if !is_nil(user) and !needs_update?(user) do - {:ok, user} - else - fetch_by_ap_id(ap_id) + maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id) + + case {cached_user, maybe_fetched_user} do + {_, {:ok, %User{} = user}} -> + {:ok, user} + + {%User{} = user, _} -> + {:ok, user} + + _ -> + {:error, :not_found} end end @@ -1890,7 +1990,7 @@ defmodule Pleroma.User do defp put_password_hash( %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset ) do - change(changeset, password_hash: Pbkdf2.hashpwsalt(password)) + change(changeset, password_hash: Pbkdf2.hash_pwd_salt(password)) end defp put_password_hash(changeset), do: changeset