import Ecto.Query
import Ecto, only: [assoc: 2]
- alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Emoji
alias Pleroma.FollowingRelationship
+ alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Keys
+ alias Pleroma.MFA
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Registration
alias Pleroma.UserRelationship
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
schema "users" do
field(:bio, :string)
+ field(:raw_bio, :string)
field(:email, :string)
field(:name, :string)
field(:nickname, :string)
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
field(:keys, :string)
+ field(:public_key, :string)
field(:ap_id, :string)
- field(:avatar, :map)
+ field(:avatar, :map, default: %{})
field(:local, :boolean, default: true)
field(:follower_address, :string)
field(:following_address, :string)
field(:last_digest_emailed_at, :naive_datetime)
field(:banner, :map, default: %{})
field(:background, :map, default: %{})
- field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
field(:following_count, :integer, default: 0)
field(:is_moderator, :boolean, default: false)
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, :string, default: nil)
+ field(:mastofe_settings, :map, default: nil)
+ field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false)
field(:hide_followers, :boolean, default: false)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
- field(:emoji, {:array, :map}, default: [])
+ field(:emoji, :map, default: %{})
field(:pleroma_settings_store, :map, default: %{})
field(:fields, {:array, :map}, default: [])
field(:raw_fields, {:array, :map}, default: [])
field(:skip_thread_containment, :boolean, default: false)
field(:actor_type, :string, default: "Person")
field(:also_known_as, {:array, :string}, default: [])
+ field(:inbox, :string)
+ field(:shared_inbox, :string)
+ field(:accepts_chat_messages, :boolean, default: nil)
embeds_one(
:notification_settings,
# `: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
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def account_status(%User{confirmation_pending: true}) do
- case Config.get([:instance, :account_activation_required]) do
- true -> :confirmation_pending
- _ -> :active
+ if Config.get([:instance, :account_activation_required]) do
+ :confirmation_pending
+ else
+ :active
end
end
def account_status(%User{}), do: :active
- @spec visible_for?(User.t(), User.t() | nil) :: boolean()
- def visible_for?(user, for_user \\ nil)
+ @spec visible_for(User.t(), User.t() | nil) ::
+ :visible
+ | :invisible
+ | :restricted_unauthenticated
+ | :deactivated
+ | :confirmation_pending
+ def visible_for(user, for_user \\ nil)
- def visible_for?(%User{invisible: true}, _), do: false
+ def visible_for(%User{invisible: true}, _), do: :invisible
- def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
+ def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
- def visible_for?(%User{local: local} = user, nil) do
- cfg_key =
- if local,
- do: :local,
- else: :remote
+ def visible_for(%User{} = user, nil) do
+ if restrict_unauthenticated?(user) do
+ :restrict_unauthenticated
+ else
+ visible_account_status(user)
+ end
+ end
- if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
- do: false,
- else: account_status(user) == :active
+ def visible_for(%User{} = user, for_user) do
+ if superuser?(for_user) do
+ :visible
+ else
+ visible_account_status(user)
+ end
end
- def visible_for?(%User{} = user, for_user) do
- account_status(user) == :active || superuser?(for_user)
+ def visible_for(_, _), do: :invisible
+
+ defp restrict_unauthenticated?(%User{local: local}) do
+ config_key = if local, do: :local, else: :remote
+
+ Config.get([:restrict_unauthenticated, :profiles, config_key], false)
end
- def visible_for?(_, _), do: false
+ defp visible_account_status(user) do
+ status = account_status(user)
+
+ if status in [:active, :password_reset_pending] do
+ :visible
+ else
+ status
+ end
+ end
@spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true
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
end
end
- def profile_url(%User{uri: url}) when url != nil, do: url
- def profile_url(%User{source_data: %{"url" => url}}) when is_binary(url), do: url
- def profile_url(%User{ap_id: ap_id}), do: ap_id
- def profile_url(_), do: nil
-
+ # Should probably be renamed or removed
def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
end
end
- def remote_user_creation(params) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
+
+ defp fix_follower_address(%{nickname: nickname} = params),
+ do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
+
+ defp fix_follower_address(params), do: params
+
+ def remote_user_changeset(struct \\ %User{local: false}, params) do
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
+
+ name =
+ case params[:name] do
+ name when is_binary(name) and byte_size(name) > 0 -> name
+ _ -> params[:nickname]
+ end
params =
params
+ |> Map.put(:name, name)
+ |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
|> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit)
|> truncate_fields_param()
+ |> fix_follower_address()
- changeset =
- %User{local: false}
- |> cast(
- params,
- [
- :bio,
- :name,
- :ap_id,
- :nickname,
- :avatar,
- :ap_enabled,
- :source_data,
- :banner,
- :locked,
- :magic_key,
- :uri,
- :hide_followers,
- :hide_follows,
- :hide_followers_count,
- :hide_follows_count,
- :follower_count,
- :fields,
- :following_count,
- :discoverable,
- :invisible,
- :actor_type,
- :also_known_as
- ]
- )
- |> validate_required([:name, :ap_id])
- |> unique_constraint(:nickname)
- |> validate_format(:nickname, @email_regex)
- |> validate_length(:bio, max: bio_limit)
- |> validate_length(:name, max: name_limit)
- |> validate_fields(true)
-
- case params[:source_data] do
- %{"followers" => followers, "following" => following} ->
- changeset
- |> put_change(:follower_address, followers)
- |> put_change(:following_address, following)
-
- _ ->
- followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
- put_change(changeset, :follower_address, followers)
- end
+ struct
+ |> cast(
+ params,
+ [
+ :bio,
+ :name,
+ :emoji,
+ :ap_id,
+ :inbox,
+ :shared_inbox,
+ :nickname,
+ :public_key,
+ :avatar,
+ :ap_enabled,
+ :banner,
+ :locked,
+ :last_refreshed_at,
+ :uri,
+ :follower_address,
+ :following_address,
+ :hide_followers,
+ :hide_follows,
+ :hide_followers_count,
+ :hide_follows_count,
+ :follower_count,
+ :fields,
+ :following_count,
+ :discoverable,
+ :invisible,
+ :actor_type,
+ :also_known_as,
+ :accepts_chat_messages
+ ]
+ )
+ |> validate_required([:name, :ap_id])
+ |> unique_constraint(:nickname)
+ |> validate_format(:nickname, @email_regex)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, max: name_limit)
+ |> validate_fields(true)
end
def update_changeset(struct, params \\ %{}) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
struct
|> cast(
params,
[
:bio,
+ :raw_bio,
:name,
+ :emoji,
:avatar,
+ :public_key,
+ :inbox,
+ :shared_inbox,
:locked,
:no_rich_text,
:default_scope,
:pleroma_settings_store,
:discoverable,
:actor_type,
- :also_known_as
+ :also_known_as,
+ :accepts_chat_messages
]
)
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
+ |> validate_inclusion(:actor_type, ["Person", "Service"])
|> put_fields()
+ |> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|> put_change_if_present(:banner, &put_upload(&1, :banner))
fields =
raw_fields
- |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
+ |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
changeset
|> put_change(:raw_fields, raw_fields)
end
end
- defp put_change_if_present(changeset, map_field, value_function) do
- if value = get_change(changeset, map_field) do
- with {:ok, new_value} <- value_function.(value) do
- put_change(changeset, map_field, new_value)
- else
- _ -> changeset
- end
+ defp parse_fields(value) do
+ value
+ |> Formatter.linkify(mentions_format: :full)
+ |> elem(0)
+ end
+
+ defp put_emoji(changeset) do
+ emojified_fields = [:bio, :name, :raw_fields]
+
+ if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
+ bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
+ name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
+
+ emoji = Map.merge(bio, name)
+
+ emoji =
+ changeset
+ |> get_field(:raw_fields)
+ |> Enum.reduce(emoji, fn x, acc ->
+ Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
+ end)
+
+ put_change(changeset, :emoji, emoji)
else
changeset
end
end
+ defp put_change_if_present(changeset, map_field, value_function) do
+ with {:ok, value} <- fetch_change(changeset, map_field),
+ {:ok, new_value} <- value_function.(value) do
+ put_change(changeset, map_field, new_value)
+ else
+ _ -> changeset
+ end
+ end
+
defp put_upload(value, type) do
with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: type) do
end
end
- def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
-
- params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
-
- params = if remote?, do: truncate_fields_param(params), else: params
-
- struct
- |> cast(
- params,
- [
- :bio,
- :name,
- :follower_address,
- :following_address,
- :avatar,
- :last_refreshed_at,
- :ap_enabled,
- :source_data,
- :banner,
- :locked,
- :magic_key,
- :follower_count,
- :following_count,
- :hide_follows,
- :fields,
- :hide_followers,
- :allow_following_move,
- :discoverable,
- :hide_followers_count,
- :hide_follows_count,
- :actor_type,
- :also_known_as
- ]
- )
- |> unique_constraint(:nickname)
- |> validate_format(:nickname, local_nickname_regex())
- |> validate_length(:bio, max: bio_limit)
- |> validate_length(:name, max: name_limit)
- |> validate_fields(remote?)
- end
-
def update_as_admin_changeset(struct, params) do
struct
|> update_changeset(params)
|> 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)
|> 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
def force_password_reset(user), do: update_password_reset_pending(user, true)
def register_changeset(struct, params \\ %{}, opts \\ []) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
+ params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
- Pleroma.Config.get([:instance, :account_activation_required])
+ Config.get([:instance, :account_activation_required])
else
opts[:need_confirmation]
end
struct
|> confirmation_changeset(need_confirmation: need_confirmation?)
- |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
+ |> cast(params, [
+ :bio,
+ :raw_bio,
+ :email,
+ :name,
+ :nickname,
+ :password,
+ :password_confirmation,
+ :emoji,
+ :accepts_chat_messages
+ ])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
+ |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
def maybe_validate_required_email(changeset, true), do: changeset
def maybe_validate_required_email(changeset, _) do
- if Pleroma.Config.get([:instance, :account_activation_required]) do
+ if Config.get([:instance, :account_activation_required]) do
validate_required(changeset, [:email])
else
changeset
end
defp autofollow_users(user) do
- candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
+ candidates = Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
def try_send_confirmation_email(%User{} = user) do
if user.confirmation_pending &&
- Pleroma.Config.get([:instance, :account_activation_required]) do
+ Config.get([:instance, :account_activation_required]) do
user
|> Pleroma.Emails.UserEmail.account_confirmation_email()
|> Pleroma.Emails.Mailer.deliver_async()
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, "pending")
+ follow(follower, followed, :follow_pending)
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
def follow_all(follower, followeds) do
followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
- |> Enum.each(&follow(follower, &1, "accept"))
+ |> Enum.each(&follow(follower, &1, :follow_accept))
set_cache(follower)
end
defdelegate following(user), to: FollowingRelationship
- def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+ def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
+ deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
cond do
followed.deactivated ->
follower
|> update_following_count()
- |> set_cache()
end
end
{: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 ["accept", "pending"] ->
+ state when state in [:follow_pending, :follow_accept] ->
FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
{:ok, follower} =
follower
|> update_following_count()
- |> set_cache()
- {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
+ {:ok, follower, followed}
nil ->
{:error, "Not subscribed!"}
defdelegate following?(follower, followed), to: FollowingRelationship
+ @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following)
get_follow_state(follower, following, following_relationship)
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
+ %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
+ FollowingRelationship.state_to_enum(state)
+
+ _ ->
+ nil
end
{%{state: state}, _} ->
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
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
end
def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
- restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
+ restrict_to_local = Config.get([:instance, :limit_to_local_content])
cond do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
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
])
end
+ @spec update_follower_count(User.t()) :: {:ok, User.t()}
def update_follower_count(%User{} = user) do
- if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
- follower_count_query =
- User.Query.build(%{followers: user, deactivated: false})
- |> select([u], %{count: count(u.id)})
-
- User
- |> where(id: ^user.id)
- |> join(:inner, [u], s in subquery(follower_count_query))
- |> update([u, s],
- set: [follower_count: s.count]
- )
- |> select([u], u)
- |> Repo.update_all([])
- |> case do
- {1, [user]} -> set_cache(user)
- _ -> {:error, user}
- end
+ if user.local or !Config.get([:instance, :external_user_synchronization]) do
+ follower_count = FollowingRelationship.follower_count(user)
+
+ user
+ |> follow_information_changeset(%{follower_count: follower_count})
+ |> update_and_set_cache
else
{:ok, maybe_fetch_follow_information(user)}
end
end
- @spec update_following_count(User.t()) :: User.t()
+ @spec update_following_count(User.t()) :: {:ok, 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)
+ if Config.get([:instance, :external_user_synchronization]) do
+ {:ok, maybe_fetch_follow_information(user)}
else
- user
+ {:ok, user}
end
end
user
|> follow_information_changeset(%{following_count: following_count})
- |> Repo.update!()
+ |> update_and_set_cache()
end
def set_unread_conversation_count(%User{local: true} = 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
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
end
def subscribe(%User{} = subscriber, %User{} = target) do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+ deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
if blocks?(target, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
unsubscribe(blocked, blocker)
- if following?(blocked, blocker), do: unfollow(blocked, blocker)
+ unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
+ if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker)
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
def blocks?(%User{} = user, %User{} = target) do
blocks_user?(user, target) ||
- (!User.following?(user, target) && blocks_domain?(user, target))
+ (blocks_domain?(user, target) and not User.following?(user, target))
end
def blocks_user?(%User{} = user, %User{} = target) 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
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()
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)
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
- {:ok, _user_block} <- block(blocker, blocked),
- {:ok, _} <- ActivityPub.block(blocker, blocked) do
+ {:ok, _block} <- CommonAPI.block(blocker, blocked) do
blocked
else
err ->
fn followed_identifier ->
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
- {:ok, _} <- ActivityPub.follow(follower, followed) do
+ {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
followed
else
err ->
})
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
- def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
+ def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
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
|> set_cache()
end
- # AP style
- def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
+ def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
key =
public_key_pem
|> :public_key.pem_decode()
{:ok, key}
end
- def public_key(_), do: {:error, "not found key"}
+ def public_key(_), do: {:error, "key not found"}
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
end
end
- defp blank?(""), do: nil
- defp blank?(n), do: n
-
- def insert_or_update_user(data) do
- data
- |> Map.put(:name, blank?(data[:name]) || data[:nickname])
- |> remote_user_creation()
- |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
- |> set_cache()
- end
-
def ap_enabled?(%User{local: true}), do: true
def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
def ap_enabled?(_), do: false
end
defp local_nickname_regex do
- if Pleroma.Config.get([:instance, :extended_nickname_format]) do
+ if Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex
else
@strict_local_nickname_regex
def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
# use instance-default
- config = Pleroma.Config.get([:assets, :mascots])
- default_mascot = Pleroma.Config.get([:assets, :default_mascot])
+ config = Config.get([:assets, :mascots])
+ default_mascot = Config.get([:assets, :default_mascot])
mascot = Keyword.get(config, default_mascot)
%{
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
|> update_and_set_cache()
end
- def update_source_data(user, source_data) do
- user
- |> cast(%{source_data: source_data}, [:source_data])
- |> update_and_set_cache()
- end
-
def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
%{
admin: is_admin,
}
end
- # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
- # For example: [{"name": "Pronoun", "value": "she/her"}, …]
- def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
- limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
-
- attachment
- |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
- |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
- |> Enum.take(limit)
- end
-
- def fields(%{fields: nil}), do: []
-
- def fields(%{fields: fields}), do: fields
-
- def sanitized_fields(%User{} = user) do
- user
- |> User.fields()
- |> Enum.map(fn %{"name" => name, "value" => value} ->
- %{
- "name" => name,
- "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
- }
- end)
- end
-
def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
- limit = Pleroma.Config.get([:instance, limit_name], 0)
+ limit = Config.get([:instance, limit_name], 0)
changeset
|> validate_length(:fields, max: limit)
end
defp valid_field?(%{"name" => name, "value" => value}) do
- name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
- value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+ name_limit = Config.get([:instance, :account_field_name_length], 255)
+ value_limit = Config.get([:instance, :account_field_value_length], 255)
is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
String.length(value) <= value_limit
defp truncate_field(%{"name" => name, "value" => value}) do
{name, _chopped} =
- String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
+ String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
{value, _chopped} =
- String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
+ String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
%{"name" => name, "value" => value}
end
def mastodon_settings_update(user, settings) do
user
- |> cast(%{settings: settings}, [:settings])
- |> validate_required([:settings])
+ |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
+ |> validate_required([:mastofe_settings])
|> update_and_set_cache()
end
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do
- max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
+ max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
params = %{pinned_activities: user.pinned_activities ++ [id]}
user
# - display name
def sanitize_html(%User{} = user, filter) do
fields =
- user
- |> User.fields()
- |> Enum.map(fn %{"name" => name, "value" => value} ->
+ Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
%{
"name" => name,
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)