alias Pleroma.Object
alias Pleroma.Registration
alias Pleroma.Repo
- alias Pleroma.RepoStreamer
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web
]
schema "users" do
- field(:bio, :string)
+ field(:bio, :string, default: "")
field(:raw_bio, :string)
field(:email, :string)
field(:name, :string)
end
end
+ defdelegate following_count(user), to: FollowingRelationship
+ defdelegate following(user), to: FollowingRelationship
+ defdelegate following?(follower, followed), to: FollowingRelationship
+ defdelegate following_ap_ids(user), to: FollowingRelationship
+ defdelegate get_follow_requests(user), to: FollowingRelationship
+ defdelegate search(query, opts \\ []), to: User.Search
+
@doc """
Dumps Flake Id to SQL-compatible format (16-byte UUID).
E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
@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 account_status(%User{approval_pending: true}), do: :approval_pending
+ def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
- def account_status(%User{confirmation_pending: true}) do
+ def account_status(%User{local: true, confirmation_pending: true}) do
if Config.get([:instance, :account_activation_required]) do
:confirmation_pending
else
def visible_for(_, _), do: :invisible
- defp restrict_unauthenticated?(%User{local: local}) do
- config_key = if local, do: :local, else: :remote
+ defp restrict_unauthenticated?(%User{local: true}) do
+ Config.restrict_unauthenticated_access?(:profiles, :local)
+ end
- Config.get([:restrict_unauthenticated, :profiles, config_key], false)
+ defp restrict_unauthenticated?(%User{local: _}) do
+ Config.restrict_unauthenticated_access?(:profiles, :remote)
end
defp visible_account_status(user) do
from(u in query, where: u.deactivated != ^true)
end
- defdelegate following_count(user), to: FollowingRelationship
-
defp truncate_fields_param(params) do
if Map.has_key?(params, :fields) do
Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def force_password_reset(user), do: update_password_reset_pending(user, true)
+ # Used to auto-register LDAP accounts which won't have a password hash stored locally
+ def register_changeset_ldap(struct, params = %{password: password})
+ when is_nil(password) do
+ params = Map.put_new(params, :accepts_chat_messages, true)
+
+ params =
+ if Map.has_key?(params, :email) do
+ Map.put_new(params, :email, params[:email])
+ else
+ params
+ end
+
+ struct
+ |> cast(params, [
+ :name,
+ :nickname,
+ :email,
+ :accepts_chat_messages
+ ])
+ |> validate_required([:name, :nickname])
+ |> unique_constraint(:nickname)
+ |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_format(:nickname, local_nickname_regex())
+ |> put_ap_id()
+ |> unique_constraint(:ap_id)
+ |> put_following_and_follower_address()
+ end
+
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
+ |> validate_format(:email, @email_regex)
+ |> validate_change(:email, fn :email, email ->
+ valid? =
+ Config.get([User, :email_blacklist])
+ |> Enum.all?(fn blacklisted_domain ->
+ !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
+ end)
+
+ if valid?, do: [], else: [email: "Invalid email"]
+ end)
|> unique_constraint(:nickname)
|> 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)
|> validate_length(:name, min: 1, max: name_limit)
|> validate_length(:registration_reason, max: reason_limit)
def send_welcome_email(_), do: {:ok, :noop}
@spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
- def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
+ def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
+ when is_binary(email) do
if Config.get([:instance, :account_activation_required]) do
send_confirmation_email(user)
{:ok, :enqueued}
set_cache(follower)
end
- defdelegate following(user), to: FollowingRelationship
-
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
- {:ok, follower} =
- follower
- |> update_following_count()
+ {:ok, follower} = update_following_count(follower)
{:ok, follower, followed}
end
end
- 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)
User.Query.build(%{followers: user, deactivated: false})
end
- def get_followers_query(user, page) do
+ def get_followers_query(%User{} = user, page) do
user
|> get_followers_query(nil)
|> User.Query.paginate(page, 20)
end
@spec get_followers_query(User.t()) :: Ecto.Query.t()
- def get_followers_query(user), do: get_followers_query(user, nil)
+ def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
- def get_followers(user, page \\ nil) do
+ def get_followers(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> Repo.all()
end
@spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
- def get_external_followers(user, page \\ nil) do
+ def get_external_followers(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> User.Query.build(%{external: true})
|> Repo.all()
end
- def get_followers_ids(user, page \\ nil) do
+ def get_followers_ids(%User{} = user, page \\ nil) do
user
|> get_followers_query(page)
|> select([u], u.id)
User.Query.build(%{friends: user, deactivated: false})
end
- def get_friends_query(user, page) do
+ def get_friends_query(%User{} = user, page) do
user
|> get_friends_query(nil)
|> User.Query.paginate(page, 20)
end
@spec get_friends_query(User.t()) :: Ecto.Query.t()
- def get_friends_query(user), do: get_friends_query(user, nil)
+ def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
- def get_friends(user, page \\ nil) do
+ def get_friends(%User{} = user, page \\ nil) do
user
|> get_friends_query(page)
|> Repo.all()
end
- def get_friends_ap_ids(user) do
+ def get_friends_ap_ids(%User{} = user) do
user
|> get_friends_query(nil)
|> select([u], u.ap_id)
|> Repo.all()
end
- def get_friends_ids(user, page \\ nil) do
+ def get_friends_ids(%User{} = user, page \\ nil) do
user
|> get_friends_query(page)
|> select([u], u.id)
|> Repo.all()
end
- defdelegate get_follow_requests(user), to: FollowingRelationship
-
def increase_note_count(%User{} = user) do
User
|> where(id: ^user.id)
|> update_and_set_cache()
end
+ @spec purge_user_changeset(User.t()) :: Changeset.t()
+ def purge_user_changeset(user) do
+ # "Right to be forgotten"
+ # https://gdpr.eu/right-to-be-forgotten/
+ change(user, %{
+ bio: "",
+ raw_bio: nil,
+ email: nil,
+ name: nil,
+ password_hash: nil,
+ keys: nil,
+ public_key: nil,
+ avatar: %{},
+ tags: [],
+ last_refreshed_at: nil,
+ last_digest_emailed_at: nil,
+ banner: %{},
+ background: %{},
+ note_count: 0,
+ follower_count: 0,
+ following_count: 0,
+ locked: false,
+ confirmation_pending: false,
+ password_reset_pending: false,
+ approval_pending: false,
+ registration_reason: nil,
+ confirmation_token: nil,
+ domain_blocks: [],
+ deactivated: true,
+ ap_enabled: false,
+ is_moderator: false,
+ is_admin: false,
+ mastofe_settings: nil,
+ mascot: nil,
+ emoji: %{},
+ pleroma_settings_store: %{},
+ fields: [],
+ raw_fields: [],
+ discoverable: false,
+ also_known_as: []
+ })
+ end
+
def delete(users) when is_list(users) do
for user <- users, do: delete(user)
end
_ ->
user
- |> change(%{deactivated: true, email: nil})
+ |> purge_user_changeset()
|> update_and_set_cache()
end
end
def perform(:deactivate_async, user, status), do: deactivate(user, status)
- @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
- def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
- when is_list(blocked_identifiers) do
- Enum.map(
- blocked_identifiers,
- fn blocked_identifier ->
- with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
- {:ok, _block} <- CommonAPI.block(blocker, blocked) do
- blocked
- else
- err ->
- Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
- err
- end
- end
- )
- end
-
- def perform(:follow_import, %User{} = follower, followed_identifiers)
- when is_list(followed_identifiers) do
- Enum.map(
- followed_identifiers,
- fn followed_identifier ->
- with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
- {:ok, follower} <- maybe_direct_follow(follower, followed),
- {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
- followed
- else
- err ->
- Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
- err
- end
- end
- )
- end
-
@spec external_users_query() :: Ecto.Query.t()
def external_users_query do
User.Query.build(%{
Repo.all(query)
end
- def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
- BackgroundWorker.enqueue("blocks_import", %{
- "blocker_id" => blocker.id,
- "blocked_identifiers" => blocked_identifiers
- })
- end
-
- def follow_import(%User{} = follower, followed_identifiers)
- when is_list(followed_identifiers) do
- BackgroundWorker.enqueue("follow_import", %{
- "follower_id" => follower.id,
- "followed_identifiers" => followed_identifiers
- })
- end
-
def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
Notification
|> join(:inner, [n], activity in assoc(n, :activity))
def delete_user_activities(%User{ap_id: ap_id} = user) do
ap_id
|> Activity.Queries.by_actor()
- |> RepoStreamer.chunk_stream(50)
+ |> Repo.chunk_stream(50, :batches)
|> Stream.each(fn activities ->
Enum.each(activities, fn activity -> delete_activity(activity, user) end)
end)
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 fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
- def get_or_fetch_by_ap_id(ap_id) do
+ def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
cached_user = get_cached_by_ap_id(ap_id)
- maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
+ maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
case {cached_user, maybe_fetched_user} do
{_, {:ok, %User{} = user}} ->
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),
+ def get_public_key_for_ap_id(ap_id, opts \\ []) do
+ with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
{:ok, public_key} <- public_key(user) do
{:ok, public_key}
else
Enum.map(users, &toggle_confirmation/1)
end
+ @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def need_confirmation(%User{} = user, bool) do
+ user
+ |> confirmation_changeset(need_confirmation: bool)
+ |> update_and_set_cache()
+ end
+
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
mascot
end
|> Repo.all()
end
- defdelegate search(query, opts \\ []), to: User.Search
-
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
params = %{pinned_activities: user.pinned_activities ++ [id]}
+ # if pinned activity was scheduled for deletion, we remove job
+ if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
+ Oban.cancel_job(expiration.id)
+ end
+
user
|> cast(params, [:pinned_activities])
|> validate_length(:pinned_activities,
|> update_and_set_cache()
end
- def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+ def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
params = %{pinned_activities: List.delete(user.pinned_activities, id)}
+ # if pinned activity was scheduled for deletion, we reschedule it for deletion
+ if data["expires_at"] do
+ # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
+ {:ok, expires_at} =
+ data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
+
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: id,
+ expires_at: expires_at
+ })
+ end
+
user
|> cast(params, [:pinned_activities])
|> update_and_set_cache()