Merge branch 'prepared-statements-settings' into 'develop'
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.User do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10 import Ecto, only: [assoc: 2]
11
12 alias Comeonin.Pbkdf2
13 alias Ecto.Multi
14 alias Pleroma.Activity
15 alias Pleroma.Config
16 alias Pleroma.Conversation.Participation
17 alias Pleroma.Delivery
18 alias Pleroma.FollowingRelationship
19 alias Pleroma.Keys
20 alias Pleroma.Notification
21 alias Pleroma.Object
22 alias Pleroma.Registration
23 alias Pleroma.Repo
24 alias Pleroma.RepoStreamer
25 alias Pleroma.User
26 alias Pleroma.UserRelationship
27 alias Pleroma.Web
28 alias Pleroma.Web.ActivityPub.ActivityPub
29 alias Pleroma.Web.ActivityPub.Utils
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
32 alias Pleroma.Web.OAuth
33 alias Pleroma.Web.RelMe
34 alias Pleroma.Workers.BackgroundWorker
35
36 require Logger
37
38 @type t :: %__MODULE__{}
39 @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
40 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
41
42 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
43 @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])?)*$/
44
45 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
46 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
47
48 # AP ID user relationships (blocks, mutes etc.)
49 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
50 @user_relationships_config [
51 block: [
52 blocker_blocks: :blocked_users,
53 blockee_blocks: :blocker_users
54 ],
55 mute: [
56 muter_mutes: :muted_users,
57 mutee_mutes: :muter_users
58 ],
59 reblog_mute: [
60 reblog_muter_mutes: :reblog_muted_users,
61 reblog_mutee_mutes: :reblog_muter_users
62 ],
63 notification_mute: [
64 notification_muter_mutes: :notification_muted_users,
65 notification_mutee_mutes: :notification_muter_users
66 ],
67 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
68 inverse_subscription: [
69 subscribee_subscriptions: :subscriber_users,
70 subscriber_subscriptions: :subscribee_users
71 ]
72 ]
73
74 schema "users" do
75 field(:bio, :string)
76 field(:email, :string)
77 field(:name, :string)
78 field(:nickname, :string)
79 field(:password_hash, :string)
80 field(:password, :string, virtual: true)
81 field(:password_confirmation, :string, virtual: true)
82 field(:keys, :string)
83 field(:ap_id, :string)
84 field(:avatar, :map)
85 field(:local, :boolean, default: true)
86 field(:follower_address, :string)
87 field(:following_address, :string)
88 field(:search_rank, :float, virtual: true)
89 field(:search_type, :integer, virtual: true)
90 field(:tags, {:array, :string}, default: [])
91 field(:last_refreshed_at, :naive_datetime_usec)
92 field(:last_digest_emailed_at, :naive_datetime)
93 field(:banner, :map, default: %{})
94 field(:background, :map, default: %{})
95 field(:source_data, :map, default: %{})
96 field(:note_count, :integer, default: 0)
97 field(:follower_count, :integer, default: 0)
98 field(:following_count, :integer, default: 0)
99 field(:locked, :boolean, default: false)
100 field(:confirmation_pending, :boolean, default: false)
101 field(:password_reset_pending, :boolean, default: false)
102 field(:confirmation_token, :string, default: nil)
103 field(:default_scope, :string, default: "public")
104 field(:domain_blocks, {:array, :string}, default: [])
105 field(:deactivated, :boolean, default: false)
106 field(:no_rich_text, :boolean, default: false)
107 field(:ap_enabled, :boolean, default: false)
108 field(:is_moderator, :boolean, default: false)
109 field(:is_admin, :boolean, default: false)
110 field(:show_role, :boolean, default: true)
111 field(:settings, :map, default: nil)
112 field(:magic_key, :string, default: nil)
113 field(:uri, :string, default: nil)
114 field(:hide_followers_count, :boolean, default: false)
115 field(:hide_follows_count, :boolean, default: false)
116 field(:hide_followers, :boolean, default: false)
117 field(:hide_follows, :boolean, default: false)
118 field(:hide_favorites, :boolean, default: true)
119 field(:unread_conversation_count, :integer, default: 0)
120 field(:pinned_activities, {:array, :string}, default: [])
121 field(:email_notifications, :map, default: %{"digest" => false})
122 field(:mascot, :map, default: nil)
123 field(:emoji, {:array, :map}, default: [])
124 field(:pleroma_settings_store, :map, default: %{})
125 field(:fields, {:array, :map}, default: [])
126 field(:raw_fields, {:array, :map}, default: [])
127 field(:discoverable, :boolean, default: false)
128 field(:invisible, :boolean, default: false)
129 field(:allow_following_move, :boolean, default: true)
130 field(:skip_thread_containment, :boolean, default: false)
131 field(:actor_type, :string, default: "Person")
132 field(:also_known_as, {:array, :string}, default: [])
133
134 embeds_one(
135 :notification_settings,
136 Pleroma.User.NotificationSetting,
137 on_replace: :update
138 )
139
140 has_many(:notifications, Notification)
141 has_many(:registrations, Registration)
142 has_many(:deliveries, Delivery)
143
144 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
145 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
146
147 for {relationship_type,
148 [
149 {outgoing_relation, outgoing_relation_target},
150 {incoming_relation, incoming_relation_source}
151 ]} <- @user_relationships_config do
152 # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
153 has_many(outgoing_relation, UserRelationship,
154 foreign_key: :source_id,
155 where: [relationship_type: relationship_type]
156 )
157
158 # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
159 has_many(incoming_relation, UserRelationship,
160 foreign_key: :target_id,
161 where: [relationship_type: relationship_type]
162 )
163
164 # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
165 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
166
167 # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
168 has_many(incoming_relation_source, through: [incoming_relation, :source])
169 end
170
171 # `:blocks` is deprecated (replaced with `blocked_users` relation)
172 field(:blocks, {:array, :string}, default: [])
173 # `:mutes` is deprecated (replaced with `muted_users` relation)
174 field(:mutes, {:array, :string}, default: [])
175 # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
176 field(:muted_reblogs, {:array, :string}, default: [])
177 # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
178 field(:muted_notifications, {:array, :string}, default: [])
179 # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
180 field(:subscribers, {:array, :string}, default: [])
181
182 timestamps()
183 end
184
185 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
186 @user_relationships_config do
187 # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
188 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
189 target_users_query = assoc(user, unquote(outgoing_relation_target))
190
191 if restrict_deactivated? do
192 restrict_deactivated(target_users_query)
193 else
194 target_users_query
195 end
196 end
197
198 # Definitions of `blocked_users/1`, `muted_users/1`, etc.
199 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
200 __MODULE__
201 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
202 user,
203 restrict_deactivated?
204 ])
205 |> Repo.all()
206 end
207
208 # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
209 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
210 __MODULE__
211 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
212 user,
213 restrict_deactivated?
214 ])
215 |> select([u], u.ap_id)
216 |> Repo.all()
217 end
218 end
219
220 @doc "Returns status account"
221 @spec account_status(User.t()) :: account_status()
222 def account_status(%User{deactivated: true}), do: :deactivated
223 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
224
225 def account_status(%User{confirmation_pending: true}) do
226 case Config.get([:instance, :account_activation_required]) do
227 true -> :confirmation_pending
228 _ -> :active
229 end
230 end
231
232 def account_status(%User{}), do: :active
233
234 @spec visible_for?(User.t(), User.t() | nil) :: boolean()
235 def visible_for?(user, for_user \\ nil)
236
237 def visible_for?(%User{invisible: true}, _), do: false
238
239 def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
240
241 def visible_for?(%User{} = user, for_user) do
242 account_status(user) == :active || superuser?(for_user)
243 end
244
245 def visible_for?(_, _), do: false
246
247 @spec superuser?(User.t()) :: boolean()
248 def superuser?(%User{local: true, is_admin: true}), do: true
249 def superuser?(%User{local: true, is_moderator: true}), do: true
250 def superuser?(_), do: false
251
252 @spec invisible?(User.t()) :: boolean()
253 def invisible?(%User{invisible: true}), do: true
254 def invisible?(_), do: false
255
256 def avatar_url(user, options \\ []) do
257 case user.avatar do
258 %{"url" => [%{"href" => href} | _]} -> href
259 _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
260 end
261 end
262
263 def banner_url(user, options \\ []) do
264 case user.banner do
265 %{"url" => [%{"href" => href} | _]} -> href
266 _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
267 end
268 end
269
270 def profile_url(%User{source_data: %{"url" => url}}), do: url
271 def profile_url(%User{ap_id: ap_id}), do: ap_id
272 def profile_url(_), do: nil
273
274 def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
275
276 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
277 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
278
279 @spec ap_following(User.t()) :: Sring.t()
280 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
281 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
282
283 def follow_state(%User{} = user, %User{} = target) do
284 case Utils.fetch_latest_follow(user, target) do
285 %{data: %{"state" => state}} -> state
286 # Ideally this would be nil, but then Cachex does not commit the value
287 _ -> false
288 end
289 end
290
291 def get_cached_follow_state(user, target) do
292 key = "follow_state:#{user.ap_id}|#{target.ap_id}"
293 Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
294 end
295
296 @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
297 def set_follow_state_cache(user_ap_id, target_ap_id, state) do
298 Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
299 end
300
301 @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
302 def restrict_deactivated(query) do
303 from(u in query, where: u.deactivated != ^true)
304 end
305
306 defdelegate following_count(user), to: FollowingRelationship
307
308 defp truncate_fields_param(params) do
309 if Map.has_key?(params, :fields) do
310 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
311 else
312 params
313 end
314 end
315
316 defp truncate_if_exists(params, key, max_length) do
317 if Map.has_key?(params, key) and is_binary(params[key]) do
318 {value, _chopped} = String.split_at(params[key], max_length)
319 Map.put(params, key, value)
320 else
321 params
322 end
323 end
324
325 def remote_user_creation(params) do
326 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
327 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
328
329 params =
330 params
331 |> truncate_if_exists(:name, name_limit)
332 |> truncate_if_exists(:bio, bio_limit)
333 |> truncate_fields_param()
334
335 changeset =
336 %User{local: false}
337 |> cast(
338 params,
339 [
340 :bio,
341 :name,
342 :ap_id,
343 :nickname,
344 :avatar,
345 :ap_enabled,
346 :source_data,
347 :banner,
348 :locked,
349 :magic_key,
350 :uri,
351 :hide_followers,
352 :hide_follows,
353 :hide_followers_count,
354 :hide_follows_count,
355 :follower_count,
356 :fields,
357 :following_count,
358 :discoverable,
359 :invisible,
360 :actor_type,
361 :also_known_as
362 ]
363 )
364 |> validate_required([:name, :ap_id])
365 |> unique_constraint(:nickname)
366 |> validate_format(:nickname, @email_regex)
367 |> validate_length(:bio, max: bio_limit)
368 |> validate_length(:name, max: name_limit)
369 |> validate_fields(true)
370
371 case params[:source_data] do
372 %{"followers" => followers, "following" => following} ->
373 changeset
374 |> put_change(:follower_address, followers)
375 |> put_change(:following_address, following)
376
377 _ ->
378 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
379 put_change(changeset, :follower_address, followers)
380 end
381 end
382
383 def update_changeset(struct, params \\ %{}) do
384 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
385 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
386
387 struct
388 |> cast(
389 params,
390 [
391 :bio,
392 :name,
393 :avatar,
394 :locked,
395 :no_rich_text,
396 :default_scope,
397 :banner,
398 :hide_follows,
399 :hide_followers,
400 :hide_followers_count,
401 :hide_follows_count,
402 :hide_favorites,
403 :allow_following_move,
404 :background,
405 :show_role,
406 :skip_thread_containment,
407 :fields,
408 :raw_fields,
409 :pleroma_settings_store,
410 :discoverable,
411 :actor_type,
412 :also_known_as
413 ]
414 )
415 |> unique_constraint(:nickname)
416 |> validate_format(:nickname, local_nickname_regex())
417 |> validate_length(:bio, max: bio_limit)
418 |> validate_length(:name, min: 1, max: name_limit)
419 |> validate_fields(false)
420 end
421
422 def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
423 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
424 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
425
426 params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
427
428 params = if remote?, do: truncate_fields_param(params), else: params
429
430 struct
431 |> cast(
432 params,
433 [
434 :bio,
435 :name,
436 :follower_address,
437 :following_address,
438 :avatar,
439 :last_refreshed_at,
440 :ap_enabled,
441 :source_data,
442 :banner,
443 :locked,
444 :magic_key,
445 :follower_count,
446 :following_count,
447 :hide_follows,
448 :fields,
449 :hide_followers,
450 :allow_following_move,
451 :discoverable,
452 :hide_followers_count,
453 :hide_follows_count,
454 :actor_type,
455 :also_known_as
456 ]
457 )
458 |> unique_constraint(:nickname)
459 |> validate_format(:nickname, local_nickname_regex())
460 |> validate_length(:bio, max: bio_limit)
461 |> validate_length(:name, max: name_limit)
462 |> validate_fields(remote?)
463 end
464
465 def password_update_changeset(struct, params) do
466 struct
467 |> cast(params, [:password, :password_confirmation])
468 |> validate_required([:password, :password_confirmation])
469 |> validate_confirmation(:password)
470 |> put_password_hash()
471 |> put_change(:password_reset_pending, false)
472 end
473
474 @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
475 def reset_password(%User{id: user_id} = user, data) do
476 multi =
477 Multi.new()
478 |> Multi.update(:user, password_update_changeset(user, data))
479 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
480 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
481
482 case Repo.transaction(multi) do
483 {:ok, %{user: user} = _} -> set_cache(user)
484 {:error, _, changeset, _} -> {:error, changeset}
485 end
486 end
487
488 def update_password_reset_pending(user, value) do
489 user
490 |> change()
491 |> put_change(:password_reset_pending, value)
492 |> update_and_set_cache()
493 end
494
495 def force_password_reset_async(user) do
496 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
497 end
498
499 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
500 def force_password_reset(user), do: update_password_reset_pending(user, true)
501
502 def register_changeset(struct, params \\ %{}, opts \\ []) do
503 bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
504 name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
505
506 need_confirmation? =
507 if is_nil(opts[:need_confirmation]) do
508 Pleroma.Config.get([:instance, :account_activation_required])
509 else
510 opts[:need_confirmation]
511 end
512
513 struct
514 |> confirmation_changeset(need_confirmation: need_confirmation?)
515 |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
516 |> validate_required([:name, :nickname, :password, :password_confirmation])
517 |> validate_confirmation(:password)
518 |> unique_constraint(:email)
519 |> unique_constraint(:nickname)
520 |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
521 |> validate_format(:nickname, local_nickname_regex())
522 |> validate_format(:email, @email_regex)
523 |> validate_length(:bio, max: bio_limit)
524 |> validate_length(:name, min: 1, max: name_limit)
525 |> maybe_validate_required_email(opts[:external])
526 |> put_password_hash
527 |> put_ap_id()
528 |> unique_constraint(:ap_id)
529 |> put_following_and_follower_address()
530 end
531
532 def maybe_validate_required_email(changeset, true), do: changeset
533 def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
534
535 defp put_ap_id(changeset) do
536 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
537 put_change(changeset, :ap_id, ap_id)
538 end
539
540 defp put_following_and_follower_address(changeset) do
541 followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
542
543 changeset
544 |> put_change(:follower_address, followers)
545 end
546
547 defp autofollow_users(user) do
548 candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
549
550 autofollowed_users =
551 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
552 |> Repo.all()
553
554 follow_all(user, autofollowed_users)
555 end
556
557 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
558 def register(%Ecto.Changeset{} = changeset) do
559 with {:ok, user} <- Repo.insert(changeset) do
560 post_register_action(user)
561 end
562 end
563
564 def post_register_action(%User{} = user) do
565 with {:ok, user} <- autofollow_users(user),
566 {:ok, user} <- set_cache(user),
567 {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
568 {:ok, _} <- try_send_confirmation_email(user) do
569 {:ok, user}
570 end
571 end
572
573 def try_send_confirmation_email(%User{} = user) do
574 if user.confirmation_pending &&
575 Pleroma.Config.get([:instance, :account_activation_required]) do
576 user
577 |> Pleroma.Emails.UserEmail.account_confirmation_email()
578 |> Pleroma.Emails.Mailer.deliver_async()
579
580 {:ok, :enqueued}
581 else
582 {:ok, :noop}
583 end
584 end
585
586 def try_send_confirmation_email(users) do
587 Enum.each(users, &try_send_confirmation_email/1)
588 end
589
590 def needs_update?(%User{local: true}), do: false
591
592 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
593
594 def needs_update?(%User{local: false} = user) do
595 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
596 end
597
598 def needs_update?(_), do: true
599
600 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
601 def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
602 follow(follower, followed, "pending")
603 end
604
605 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
606 follow(follower, followed)
607 end
608
609 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
610 if not ap_enabled?(followed) do
611 follow(follower, followed)
612 else
613 {:ok, follower}
614 end
615 end
616
617 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
618 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
619 def follow_all(follower, followeds) do
620 followeds
621 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
622 |> Enum.each(&follow(follower, &1, "accept"))
623
624 set_cache(follower)
625 end
626
627 defdelegate following(user), to: FollowingRelationship
628
629 def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
630 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
631
632 cond do
633 followed.deactivated ->
634 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
635
636 deny_follow_blocked and blocks?(followed, follower) ->
637 {:error, "Could not follow user: #{followed.nickname} blocked you."}
638
639 true ->
640 FollowingRelationship.follow(follower, followed, state)
641
642 {:ok, _} = update_follower_count(followed)
643
644 follower
645 |> update_following_count()
646 |> set_cache()
647 end
648 end
649
650 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
651 {:error, "Not subscribed!"}
652 end
653
654 def unfollow(%User{} = follower, %User{} = followed) do
655 case get_follow_state(follower, followed) do
656 state when state in ["accept", "pending"] ->
657 FollowingRelationship.unfollow(follower, followed)
658 {:ok, followed} = update_follower_count(followed)
659
660 {:ok, follower} =
661 follower
662 |> update_following_count()
663 |> set_cache()
664
665 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
666
667 nil ->
668 {:error, "Not subscribed!"}
669 end
670 end
671
672 defdelegate following?(follower, followed), to: FollowingRelationship
673
674 def get_follow_state(%User{} = follower, %User{} = following) do
675 following_relationship = FollowingRelationship.get(follower, following)
676
677 case {following_relationship, following.local} do
678 {nil, false} ->
679 case Utils.fetch_latest_follow(follower, following) do
680 %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
681 _ -> nil
682 end
683
684 {%{state: state}, _} ->
685 state
686
687 {nil, _} ->
688 nil
689 end
690 end
691
692 def locked?(%User{} = user) do
693 user.locked || false
694 end
695
696 def get_by_id(id) do
697 Repo.get_by(User, id: id)
698 end
699
700 def get_by_ap_id(ap_id) do
701 Repo.get_by(User, ap_id: ap_id)
702 end
703
704 def get_all_by_ap_id(ap_ids) do
705 from(u in __MODULE__,
706 where: u.ap_id in ^ap_ids
707 )
708 |> Repo.all()
709 end
710
711 def get_all_by_ids(ids) do
712 from(u in __MODULE__, where: u.id in ^ids)
713 |> Repo.all()
714 end
715
716 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
717 # of the ap_id and the domain and tries to get that user
718 def get_by_guessed_nickname(ap_id) do
719 domain = URI.parse(ap_id).host
720 name = List.last(String.split(ap_id, "/"))
721 nickname = "#{name}@#{domain}"
722
723 get_cached_by_nickname(nickname)
724 end
725
726 def set_cache({:ok, user}), do: set_cache(user)
727 def set_cache({:error, err}), do: {:error, err}
728
729 def set_cache(%User{} = user) do
730 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
731 Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
732 {:ok, user}
733 end
734
735 def update_and_set_cache(struct, params) do
736 struct
737 |> update_changeset(params)
738 |> update_and_set_cache()
739 end
740
741 def update_and_set_cache(changeset) do
742 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
743 set_cache(user)
744 end
745 end
746
747 def invalidate_cache(user) do
748 Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
749 Cachex.del(:user_cache, "nickname:#{user.nickname}")
750 end
751
752 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
753 def get_cached_by_ap_id(ap_id) do
754 key = "ap_id:#{ap_id}"
755
756 with {:ok, nil} <- Cachex.get(:user_cache, key),
757 user when not is_nil(user) <- get_by_ap_id(ap_id),
758 {:ok, true} <- Cachex.put(:user_cache, key, user) do
759 user
760 else
761 {:ok, user} -> user
762 nil -> nil
763 end
764 end
765
766 def get_cached_by_id(id) do
767 key = "id:#{id}"
768
769 ap_id =
770 Cachex.fetch!(:user_cache, key, fn _ ->
771 user = get_by_id(id)
772
773 if user do
774 Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
775 {:commit, user.ap_id}
776 else
777 {:ignore, ""}
778 end
779 end)
780
781 get_cached_by_ap_id(ap_id)
782 end
783
784 def get_cached_by_nickname(nickname) do
785 key = "nickname:#{nickname}"
786
787 Cachex.fetch!(:user_cache, key, fn ->
788 case get_or_fetch_by_nickname(nickname) do
789 {:ok, user} -> {:commit, user}
790 {:error, _error} -> {:ignore, nil}
791 end
792 end)
793 end
794
795 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
796 restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
797
798 cond do
799 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
800 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
801
802 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
803 get_cached_by_nickname(nickname_or_id)
804
805 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
806 get_cached_by_nickname(nickname_or_id)
807
808 true ->
809 nil
810 end
811 end
812
813 def get_by_nickname(nickname) do
814 Repo.get_by(User, nickname: nickname) ||
815 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
816 Repo.get_by(User, nickname: local_nickname(nickname))
817 end
818 end
819
820 def get_by_email(email), do: Repo.get_by(User, email: email)
821
822 def get_by_nickname_or_email(nickname_or_email) do
823 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
824 end
825
826 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
827
828 def get_or_fetch_by_nickname(nickname) do
829 with %User{} = user <- get_by_nickname(nickname) do
830 {:ok, user}
831 else
832 _e ->
833 with [_nick, _domain] <- String.split(nickname, "@"),
834 {:ok, user} <- fetch_by_nickname(nickname) do
835 if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
836 fetch_initial_posts(user)
837 end
838
839 {:ok, user}
840 else
841 _e -> {:error, "not found " <> nickname}
842 end
843 end
844 end
845
846 @doc "Fetch some posts when the user has just been federated with"
847 def fetch_initial_posts(user) do
848 BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
849 end
850
851 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
852 def get_followers_query(%User{} = user, nil) do
853 User.Query.build(%{followers: user, deactivated: false})
854 end
855
856 def get_followers_query(user, page) do
857 user
858 |> get_followers_query(nil)
859 |> User.Query.paginate(page, 20)
860 end
861
862 @spec get_followers_query(User.t()) :: Ecto.Query.t()
863 def get_followers_query(user), do: get_followers_query(user, nil)
864
865 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
866 def get_followers(user, page \\ nil) do
867 user
868 |> get_followers_query(page)
869 |> Repo.all()
870 end
871
872 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
873 def get_external_followers(user, page \\ nil) do
874 user
875 |> get_followers_query(page)
876 |> User.Query.build(%{external: true})
877 |> Repo.all()
878 end
879
880 def get_followers_ids(user, page \\ nil) do
881 user
882 |> get_followers_query(page)
883 |> select([u], u.id)
884 |> Repo.all()
885 end
886
887 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
888 def get_friends_query(%User{} = user, nil) do
889 User.Query.build(%{friends: user, deactivated: false})
890 end
891
892 def get_friends_query(user, page) do
893 user
894 |> get_friends_query(nil)
895 |> User.Query.paginate(page, 20)
896 end
897
898 @spec get_friends_query(User.t()) :: Ecto.Query.t()
899 def get_friends_query(user), do: get_friends_query(user, nil)
900
901 def get_friends(user, page \\ nil) do
902 user
903 |> get_friends_query(page)
904 |> Repo.all()
905 end
906
907 def get_friends_ap_ids(user) do
908 user
909 |> get_friends_query(nil)
910 |> select([u], u.ap_id)
911 |> Repo.all()
912 end
913
914 def get_friends_ids(user, page \\ nil) do
915 user
916 |> get_friends_query(page)
917 |> select([u], u.id)
918 |> Repo.all()
919 end
920
921 defdelegate get_follow_requests(user), to: FollowingRelationship
922
923 def increase_note_count(%User{} = user) do
924 User
925 |> where(id: ^user.id)
926 |> update([u], inc: [note_count: 1])
927 |> select([u], u)
928 |> Repo.update_all([])
929 |> case do
930 {1, [user]} -> set_cache(user)
931 _ -> {:error, user}
932 end
933 end
934
935 def decrease_note_count(%User{} = user) do
936 User
937 |> where(id: ^user.id)
938 |> update([u],
939 set: [
940 note_count: fragment("greatest(0, note_count - 1)")
941 ]
942 )
943 |> select([u], u)
944 |> Repo.update_all([])
945 |> case do
946 {1, [user]} -> set_cache(user)
947 _ -> {:error, user}
948 end
949 end
950
951 def update_note_count(%User{} = user, note_count \\ nil) do
952 note_count =
953 note_count ||
954 from(
955 a in Object,
956 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
957 select: count(a.id)
958 )
959 |> Repo.one()
960
961 user
962 |> cast(%{note_count: note_count}, [:note_count])
963 |> update_and_set_cache()
964 end
965
966 @spec maybe_fetch_follow_information(User.t()) :: User.t()
967 def maybe_fetch_follow_information(user) do
968 with {:ok, user} <- fetch_follow_information(user) do
969 user
970 else
971 e ->
972 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
973
974 user
975 end
976 end
977
978 def fetch_follow_information(user) do
979 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
980 user
981 |> follow_information_changeset(info)
982 |> update_and_set_cache()
983 end
984 end
985
986 defp follow_information_changeset(user, params) do
987 user
988 |> cast(params, [
989 :hide_followers,
990 :hide_follows,
991 :follower_count,
992 :following_count,
993 :hide_followers_count,
994 :hide_follows_count
995 ])
996 end
997
998 def update_follower_count(%User{} = user) do
999 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
1000 follower_count_query =
1001 User.Query.build(%{followers: user, deactivated: false})
1002 |> select([u], %{count: count(u.id)})
1003
1004 User
1005 |> where(id: ^user.id)
1006 |> join(:inner, [u], s in subquery(follower_count_query))
1007 |> update([u, s],
1008 set: [follower_count: s.count]
1009 )
1010 |> select([u], u)
1011 |> Repo.update_all([])
1012 |> case do
1013 {1, [user]} -> set_cache(user)
1014 _ -> {:error, user}
1015 end
1016 else
1017 {:ok, maybe_fetch_follow_information(user)}
1018 end
1019 end
1020
1021 @spec update_following_count(User.t()) :: User.t()
1022 def update_following_count(%User{local: false} = user) do
1023 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
1024 maybe_fetch_follow_information(user)
1025 else
1026 user
1027 end
1028 end
1029
1030 def update_following_count(%User{local: true} = user) do
1031 following_count = FollowingRelationship.following_count(user)
1032
1033 user
1034 |> follow_information_changeset(%{following_count: following_count})
1035 |> Repo.update!()
1036 end
1037
1038 def set_unread_conversation_count(%User{local: true} = user) do
1039 unread_query = Participation.unread_conversation_count_for_user(user)
1040
1041 User
1042 |> join(:inner, [u], p in subquery(unread_query))
1043 |> update([u, p],
1044 set: [unread_conversation_count: p.count]
1045 )
1046 |> where([u], u.id == ^user.id)
1047 |> select([u], u)
1048 |> Repo.update_all([])
1049 |> case do
1050 {1, [user]} -> set_cache(user)
1051 _ -> {:error, user}
1052 end
1053 end
1054
1055 def set_unread_conversation_count(user), do: {:ok, user}
1056
1057 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
1058 unread_query =
1059 Participation.unread_conversation_count_for_user(user)
1060 |> where([p], p.conversation_id == ^conversation.id)
1061
1062 User
1063 |> join(:inner, [u], p in subquery(unread_query))
1064 |> update([u, p],
1065 inc: [unread_conversation_count: 1]
1066 )
1067 |> where([u], u.id == ^user.id)
1068 |> where([u, p], p.count == 0)
1069 |> select([u], u)
1070 |> Repo.update_all([])
1071 |> case do
1072 {1, [user]} -> set_cache(user)
1073 _ -> {:error, user}
1074 end
1075 end
1076
1077 def increment_unread_conversation_count(_, user), do: {:ok, user}
1078
1079 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
1080 def get_users_from_set(ap_ids, local_only \\ true) do
1081 criteria = %{ap_id: ap_ids, deactivated: false}
1082 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1083
1084 User.Query.build(criteria)
1085 |> Repo.all()
1086 end
1087
1088 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1089 def get_recipients_from_activity(%Activity{recipients: to}) do
1090 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
1091 |> Repo.all()
1092 end
1093
1094 @spec mute(User.t(), User.t(), boolean()) ::
1095 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1096 def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
1097 add_to_mutes(muter, mutee, notifications?)
1098 end
1099
1100 def unmute(%User{} = muter, %User{} = mutee) do
1101 remove_from_mutes(muter, mutee)
1102 end
1103
1104 def subscribe(%User{} = subscriber, %User{} = target) do
1105 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
1106
1107 if blocks?(target, subscriber) and deny_follow_blocked do
1108 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1109 else
1110 # Note: the relationship is inverse: subscriber acts as relationship target
1111 UserRelationship.create_inverse_subscription(target, subscriber)
1112 end
1113 end
1114
1115 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1116 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1117 subscribe(subscriber, subscribee)
1118 end
1119 end
1120
1121 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1122 # Note: the relationship is inverse: subscriber acts as relationship target
1123 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1124 end
1125
1126 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1127 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1128 unsubscribe(unsubscriber, user)
1129 end
1130 end
1131
1132 def block(%User{} = blocker, %User{} = blocked) do
1133 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1134 blocker =
1135 if following?(blocker, blocked) do
1136 {:ok, blocker, _} = unfollow(blocker, blocked)
1137 blocker
1138 else
1139 blocker
1140 end
1141
1142 # clear any requested follows as well
1143 blocked =
1144 case CommonAPI.reject_follow_request(blocked, blocker) do
1145 {:ok, %User{} = updated_blocked} -> updated_blocked
1146 nil -> blocked
1147 end
1148
1149 unsubscribe(blocked, blocker)
1150
1151 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1152
1153 {:ok, blocker} = update_follower_count(blocker)
1154 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1155 add_to_block(blocker, blocked)
1156 end
1157
1158 # helper to handle the block given only an actor's AP id
1159 def block(%User{} = blocker, %{ap_id: ap_id}) do
1160 block(blocker, get_cached_by_ap_id(ap_id))
1161 end
1162
1163 def unblock(%User{} = blocker, %User{} = blocked) do
1164 remove_from_block(blocker, blocked)
1165 end
1166
1167 # helper to handle the block given only an actor's AP id
1168 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1169 unblock(blocker, get_cached_by_ap_id(ap_id))
1170 end
1171
1172 def mutes?(nil, _), do: false
1173 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1174
1175 def mutes_user?(%User{} = user, %User{} = target) do
1176 UserRelationship.mute_exists?(user, target)
1177 end
1178
1179 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1180 def muted_notifications?(nil, _), do: false
1181
1182 def muted_notifications?(%User{} = user, %User{} = target),
1183 do: UserRelationship.notification_mute_exists?(user, target)
1184
1185 def blocks?(nil, _), do: false
1186
1187 def blocks?(%User{} = user, %User{} = target) do
1188 blocks_user?(user, target) ||
1189 (!User.following?(user, target) && blocks_domain?(user, target))
1190 end
1191
1192 def blocks_user?(%User{} = user, %User{} = target) do
1193 UserRelationship.block_exists?(user, target)
1194 end
1195
1196 def blocks_user?(_, _), do: false
1197
1198 def blocks_domain?(%User{} = user, %User{} = target) do
1199 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1200 %{host: host} = URI.parse(target.ap_id)
1201 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1202 end
1203
1204 def blocks_domain?(_, _), do: false
1205
1206 def subscribed_to?(%User{} = user, %User{} = target) do
1207 # Note: the relationship is inverse: subscriber acts as relationship target
1208 UserRelationship.inverse_subscription_exists?(target, user)
1209 end
1210
1211 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1212 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1213 subscribed_to?(user, target)
1214 end
1215 end
1216
1217 @doc """
1218 Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
1219 E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1220 """
1221 @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1222 def outgoing_relations_ap_ids(_, []), do: %{}
1223
1224 def outgoing_relations_ap_ids(%User{} = user, relationship_types)
1225 when is_list(relationship_types) do
1226 db_result =
1227 user
1228 |> assoc(:outgoing_relationships)
1229 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1230 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1231 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1232 |> group_by([user_rel, u], user_rel.relationship_type)
1233 |> Repo.all()
1234 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1235
1236 Enum.into(
1237 relationship_types,
1238 %{},
1239 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1240 )
1241 end
1242
1243 def deactivate_async(user, status \\ true) do
1244 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1245 end
1246
1247 def deactivate(user, status \\ true)
1248
1249 def deactivate(users, status) when is_list(users) do
1250 Repo.transaction(fn ->
1251 for user <- users, do: deactivate(user, status)
1252 end)
1253 end
1254
1255 def deactivate(%User{} = user, status) do
1256 with {:ok, user} <- set_activation_status(user, status) do
1257 user
1258 |> get_followers()
1259 |> Enum.filter(& &1.local)
1260 |> Enum.each(fn follower ->
1261 follower |> update_following_count() |> set_cache()
1262 end)
1263
1264 # Only update local user counts, remote will be update during the next pull.
1265 user
1266 |> get_friends()
1267 |> Enum.filter(& &1.local)
1268 |> Enum.each(&update_follower_count/1)
1269
1270 {:ok, user}
1271 end
1272 end
1273
1274 def update_notification_settings(%User{} = user, settings) do
1275 user
1276 |> cast(%{notification_settings: settings}, [])
1277 |> cast_embed(:notification_settings)
1278 |> validate_required([:notification_settings])
1279 |> update_and_set_cache()
1280 end
1281
1282 def delete(users) when is_list(users) do
1283 for user <- users, do: delete(user)
1284 end
1285
1286 def delete(%User{} = user) do
1287 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1288 end
1289
1290 def perform(:force_password_reset, user), do: force_password_reset(user)
1291
1292 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1293 def perform(:delete, %User{} = user) do
1294 {:ok, _user} = ActivityPub.delete(user)
1295
1296 # Remove all relationships
1297 user
1298 |> get_followers()
1299 |> Enum.each(fn follower ->
1300 ActivityPub.unfollow(follower, user)
1301 unfollow(follower, user)
1302 end)
1303
1304 user
1305 |> get_friends()
1306 |> Enum.each(fn followed ->
1307 ActivityPub.unfollow(user, followed)
1308 unfollow(user, followed)
1309 end)
1310
1311 delete_user_activities(user)
1312 invalidate_cache(user)
1313 Repo.delete(user)
1314 end
1315
1316 def perform(:fetch_initial_posts, %User{} = user) do
1317 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1318
1319 # Insert all the posts in reverse order, so they're in the right order on the timeline
1320 user.source_data["outbox"]
1321 |> Utils.fetch_ordered_collection(pages)
1322 |> Enum.reverse()
1323 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1324 end
1325
1326 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1327
1328 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1329 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1330 when is_list(blocked_identifiers) do
1331 Enum.map(
1332 blocked_identifiers,
1333 fn blocked_identifier ->
1334 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1335 {:ok, _user_block} <- block(blocker, blocked),
1336 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1337 blocked
1338 else
1339 err ->
1340 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1341 err
1342 end
1343 end
1344 )
1345 end
1346
1347 def perform(:follow_import, %User{} = follower, followed_identifiers)
1348 when is_list(followed_identifiers) do
1349 Enum.map(
1350 followed_identifiers,
1351 fn followed_identifier ->
1352 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1353 {:ok, follower} <- maybe_direct_follow(follower, followed),
1354 {:ok, _} <- ActivityPub.follow(follower, followed) do
1355 followed
1356 else
1357 err ->
1358 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1359 err
1360 end
1361 end
1362 )
1363 end
1364
1365 @spec external_users_query() :: Ecto.Query.t()
1366 def external_users_query do
1367 User.Query.build(%{
1368 external: true,
1369 active: true,
1370 order_by: :id
1371 })
1372 end
1373
1374 @spec external_users(keyword()) :: [User.t()]
1375 def external_users(opts \\ []) do
1376 query =
1377 external_users_query()
1378 |> select([u], struct(u, [:id, :ap_id]))
1379
1380 query =
1381 if opts[:max_id],
1382 do: where(query, [u], u.id > ^opts[:max_id]),
1383 else: query
1384
1385 query =
1386 if opts[:limit],
1387 do: limit(query, ^opts[:limit]),
1388 else: query
1389
1390 Repo.all(query)
1391 end
1392
1393 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1394 BackgroundWorker.enqueue("blocks_import", %{
1395 "blocker_id" => blocker.id,
1396 "blocked_identifiers" => blocked_identifiers
1397 })
1398 end
1399
1400 def follow_import(%User{} = follower, followed_identifiers)
1401 when is_list(followed_identifiers) do
1402 BackgroundWorker.enqueue("follow_import", %{
1403 "follower_id" => follower.id,
1404 "followed_identifiers" => followed_identifiers
1405 })
1406 end
1407
1408 def delete_user_activities(%User{ap_id: ap_id}) do
1409 ap_id
1410 |> Activity.Queries.by_actor()
1411 |> RepoStreamer.chunk_stream(50)
1412 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1413 |> Stream.run()
1414 end
1415
1416 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1417 activity
1418 |> Object.normalize()
1419 |> ActivityPub.delete()
1420 end
1421
1422 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1423 object = Object.normalize(activity)
1424
1425 activity.actor
1426 |> get_cached_by_ap_id()
1427 |> ActivityPub.unlike(object)
1428 end
1429
1430 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1431 object = Object.normalize(activity)
1432
1433 activity.actor
1434 |> get_cached_by_ap_id()
1435 |> ActivityPub.unannounce(object)
1436 end
1437
1438 defp delete_activity(_activity), do: "Doing nothing"
1439
1440 def html_filter_policy(%User{no_rich_text: true}) do
1441 Pleroma.HTML.Scrubber.TwitterText
1442 end
1443
1444 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1445
1446 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1447
1448 def get_or_fetch_by_ap_id(ap_id) do
1449 user = get_cached_by_ap_id(ap_id)
1450
1451 if !is_nil(user) and !needs_update?(user) do
1452 {:ok, user}
1453 else
1454 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1455 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1456
1457 resp = fetch_by_ap_id(ap_id)
1458
1459 if should_fetch_initial do
1460 with {:ok, %User{} = user} <- resp do
1461 fetch_initial_posts(user)
1462 end
1463 end
1464
1465 resp
1466 end
1467 end
1468
1469 @doc """
1470 Creates an internal service actor by URI if missing.
1471 Optionally takes nickname for addressing.
1472 """
1473 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1474 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1475 {_, user} =
1476 case get_cached_by_ap_id(uri) do
1477 nil ->
1478 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1479 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1480 {:error, nil}
1481 end
1482
1483 %User{invisible: false} = user ->
1484 set_invisible(user)
1485
1486 user ->
1487 {:ok, user}
1488 end
1489
1490 user
1491 end
1492
1493 @spec set_invisible(User.t()) :: {:ok, User.t()}
1494 defp set_invisible(user) do
1495 user
1496 |> change(%{invisible: true})
1497 |> update_and_set_cache()
1498 end
1499
1500 @spec create_service_actor(String.t(), String.t()) ::
1501 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1502 defp create_service_actor(uri, nickname) do
1503 %User{
1504 invisible: true,
1505 local: true,
1506 ap_id: uri,
1507 nickname: nickname,
1508 follower_address: uri <> "/followers"
1509 }
1510 |> change
1511 |> unique_constraint(:nickname)
1512 |> Repo.insert()
1513 |> set_cache()
1514 end
1515
1516 # AP style
1517 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1518 key =
1519 public_key_pem
1520 |> :public_key.pem_decode()
1521 |> hd()
1522 |> :public_key.pem_entry_decode()
1523
1524 {:ok, key}
1525 end
1526
1527 def public_key(_), do: {:error, "not found key"}
1528
1529 def get_public_key_for_ap_id(ap_id) do
1530 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1531 {:ok, public_key} <- public_key(user) do
1532 {:ok, public_key}
1533 else
1534 _ -> :error
1535 end
1536 end
1537
1538 defp blank?(""), do: nil
1539 defp blank?(n), do: n
1540
1541 def insert_or_update_user(data) do
1542 data
1543 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1544 |> remote_user_creation()
1545 |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
1546 |> set_cache()
1547 end
1548
1549 def ap_enabled?(%User{local: true}), do: true
1550 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1551 def ap_enabled?(_), do: false
1552
1553 @doc "Gets or fetch a user by uri or nickname."
1554 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1555 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1556 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1557
1558 # wait a period of time and return newest version of the User structs
1559 # this is because we have synchronous follow APIs and need to simulate them
1560 # with an async handshake
1561 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1562 with %User{} = a <- get_cached_by_id(a.id),
1563 %User{} = b <- get_cached_by_id(b.id) do
1564 {:ok, a, b}
1565 else
1566 nil -> :error
1567 end
1568 end
1569
1570 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1571 with :ok <- :timer.sleep(timeout),
1572 %User{} = a <- get_cached_by_id(a.id),
1573 %User{} = b <- get_cached_by_id(b.id) do
1574 {:ok, a, b}
1575 else
1576 nil -> :error
1577 end
1578 end
1579
1580 def parse_bio(bio) when is_binary(bio) and bio != "" do
1581 bio
1582 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1583 |> elem(0)
1584 end
1585
1586 def parse_bio(_), do: ""
1587
1588 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1589 # TODO: get profile URLs other than user.ap_id
1590 profile_urls = [user.ap_id]
1591
1592 bio
1593 |> CommonUtils.format_input("text/plain",
1594 mentions_format: :full,
1595 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1596 )
1597 |> elem(0)
1598 end
1599
1600 def parse_bio(_, _), do: ""
1601
1602 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1603 Repo.transaction(fn ->
1604 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1605 end)
1606 end
1607
1608 def tag(nickname, tags) when is_binary(nickname),
1609 do: tag(get_by_nickname(nickname), tags)
1610
1611 def tag(%User{} = user, tags),
1612 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1613
1614 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1615 Repo.transaction(fn ->
1616 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1617 end)
1618 end
1619
1620 def untag(nickname, tags) when is_binary(nickname),
1621 do: untag(get_by_nickname(nickname), tags)
1622
1623 def untag(%User{} = user, tags),
1624 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1625
1626 defp update_tags(%User{} = user, new_tags) do
1627 {:ok, updated_user} =
1628 user
1629 |> change(%{tags: new_tags})
1630 |> update_and_set_cache()
1631
1632 updated_user
1633 end
1634
1635 defp normalize_tags(tags) do
1636 [tags]
1637 |> List.flatten()
1638 |> Enum.map(&String.downcase/1)
1639 end
1640
1641 defp local_nickname_regex do
1642 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1643 @extended_local_nickname_regex
1644 else
1645 @strict_local_nickname_regex
1646 end
1647 end
1648
1649 def local_nickname(nickname_or_mention) do
1650 nickname_or_mention
1651 |> full_nickname()
1652 |> String.split("@")
1653 |> hd()
1654 end
1655
1656 def full_nickname(nickname_or_mention),
1657 do: String.trim_leading(nickname_or_mention, "@")
1658
1659 def error_user(ap_id) do
1660 %User{
1661 name: ap_id,
1662 ap_id: ap_id,
1663 nickname: "erroruser@example.com",
1664 inserted_at: NaiveDateTime.utc_now()
1665 }
1666 end
1667
1668 @spec all_superusers() :: [User.t()]
1669 def all_superusers do
1670 User.Query.build(%{super_users: true, local: true, deactivated: false})
1671 |> Repo.all()
1672 end
1673
1674 def showing_reblogs?(%User{} = user, %User{} = target) do
1675 not UserRelationship.reblog_mute_exists?(user, target)
1676 end
1677
1678 @doc """
1679 The function returns a query to get users with no activity for given interval of days.
1680 Inactive users are those who didn't read any notification, or had any activity where
1681 the user is the activity's actor, during `inactivity_threshold` days.
1682 Deactivated users will not appear in this list.
1683
1684 ## Examples
1685
1686 iex> Pleroma.User.list_inactive_users()
1687 %Ecto.Query{}
1688 """
1689 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1690 def list_inactive_users_query(inactivity_threshold \\ 7) do
1691 negative_inactivity_threshold = -inactivity_threshold
1692 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1693 # Subqueries are not supported in `where` clauses, join gets too complicated.
1694 has_read_notifications =
1695 from(n in Pleroma.Notification,
1696 where: n.seen == true,
1697 group_by: n.id,
1698 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1699 select: n.user_id
1700 )
1701 |> Pleroma.Repo.all()
1702
1703 from(u in Pleroma.User,
1704 left_join: a in Pleroma.Activity,
1705 on: u.ap_id == a.actor,
1706 where: not is_nil(u.nickname),
1707 where: u.deactivated != ^true,
1708 where: u.id not in ^has_read_notifications,
1709 group_by: u.id,
1710 having:
1711 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1712 is_nil(max(a.inserted_at))
1713 )
1714 end
1715
1716 @doc """
1717 Enable or disable email notifications for user
1718
1719 ## Examples
1720
1721 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1722 Pleroma.User{email_notifications: %{"digest" => true}}
1723
1724 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1725 Pleroma.User{email_notifications: %{"digest" => false}}
1726 """
1727 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1728 {:ok, t()} | {:error, Ecto.Changeset.t()}
1729 def switch_email_notifications(user, type, status) do
1730 User.update_email_notifications(user, %{type => status})
1731 end
1732
1733 @doc """
1734 Set `last_digest_emailed_at` value for the user to current time
1735 """
1736 @spec touch_last_digest_emailed_at(t()) :: t()
1737 def touch_last_digest_emailed_at(user) do
1738 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1739
1740 {:ok, updated_user} =
1741 user
1742 |> change(%{last_digest_emailed_at: now})
1743 |> update_and_set_cache()
1744
1745 updated_user
1746 end
1747
1748 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1749 def toggle_confirmation(%User{} = user) do
1750 user
1751 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1752 |> update_and_set_cache()
1753 end
1754
1755 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1756 def toggle_confirmation(users) do
1757 Enum.map(users, &toggle_confirmation/1)
1758 end
1759
1760 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1761 mascot
1762 end
1763
1764 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1765 # use instance-default
1766 config = Pleroma.Config.get([:assets, :mascots])
1767 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1768 mascot = Keyword.get(config, default_mascot)
1769
1770 %{
1771 "id" => "default-mascot",
1772 "url" => mascot[:url],
1773 "preview_url" => mascot[:url],
1774 "pleroma" => %{
1775 "mime_type" => mascot[:mime_type]
1776 }
1777 }
1778 end
1779
1780 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1781
1782 def ensure_keys_present(%User{} = user) do
1783 with {:ok, pem} <- Keys.generate_rsa_pem() do
1784 user
1785 |> cast(%{keys: pem}, [:keys])
1786 |> validate_required([:keys])
1787 |> update_and_set_cache()
1788 end
1789 end
1790
1791 def get_ap_ids_by_nicknames(nicknames) do
1792 from(u in User,
1793 where: u.nickname in ^nicknames,
1794 select: u.ap_id
1795 )
1796 |> Repo.all()
1797 end
1798
1799 defdelegate search(query, opts \\ []), to: User.Search
1800
1801 defp put_password_hash(
1802 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1803 ) do
1804 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1805 end
1806
1807 defp put_password_hash(changeset), do: changeset
1808
1809 def is_internal_user?(%User{nickname: nil}), do: true
1810 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1811 def is_internal_user?(_), do: false
1812
1813 # A hack because user delete activities have a fake id for whatever reason
1814 # TODO: Get rid of this
1815 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1816
1817 def get_delivered_users_by_object_id(object_id) do
1818 from(u in User,
1819 inner_join: delivery in assoc(u, :deliveries),
1820 where: delivery.object_id == ^object_id
1821 )
1822 |> Repo.all()
1823 end
1824
1825 def change_email(user, email) do
1826 user
1827 |> cast(%{email: email}, [:email])
1828 |> validate_required([:email])
1829 |> unique_constraint(:email)
1830 |> validate_format(:email, @email_regex)
1831 |> update_and_set_cache()
1832 end
1833
1834 # Internal function; public one is `deactivate/2`
1835 defp set_activation_status(user, deactivated) do
1836 user
1837 |> cast(%{deactivated: deactivated}, [:deactivated])
1838 |> update_and_set_cache()
1839 end
1840
1841 def update_banner(user, banner) do
1842 user
1843 |> cast(%{banner: banner}, [:banner])
1844 |> update_and_set_cache()
1845 end
1846
1847 def update_background(user, background) do
1848 user
1849 |> cast(%{background: background}, [:background])
1850 |> update_and_set_cache()
1851 end
1852
1853 def update_source_data(user, source_data) do
1854 user
1855 |> cast(%{source_data: source_data}, [:source_data])
1856 |> update_and_set_cache()
1857 end
1858
1859 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1860 %{
1861 admin: is_admin,
1862 moderator: is_moderator
1863 }
1864 end
1865
1866 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1867 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1868 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1869 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1870
1871 attachment
1872 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1873 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1874 |> Enum.take(limit)
1875 end
1876
1877 def fields(%{fields: nil}), do: []
1878
1879 def fields(%{fields: fields}), do: fields
1880
1881 def validate_fields(changeset, remote? \\ false) do
1882 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1883 limit = Pleroma.Config.get([:instance, limit_name], 0)
1884
1885 changeset
1886 |> validate_length(:fields, max: limit)
1887 |> validate_change(:fields, fn :fields, fields ->
1888 if Enum.all?(fields, &valid_field?/1) do
1889 []
1890 else
1891 [fields: "invalid"]
1892 end
1893 end)
1894 end
1895
1896 defp valid_field?(%{"name" => name, "value" => value}) do
1897 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1898 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1899
1900 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1901 String.length(value) <= value_limit
1902 end
1903
1904 defp valid_field?(_), do: false
1905
1906 defp truncate_field(%{"name" => name, "value" => value}) do
1907 {name, _chopped} =
1908 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1909
1910 {value, _chopped} =
1911 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1912
1913 %{"name" => name, "value" => value}
1914 end
1915
1916 def admin_api_update(user, params) do
1917 user
1918 |> cast(params, [
1919 :is_moderator,
1920 :is_admin,
1921 :show_role
1922 ])
1923 |> update_and_set_cache()
1924 end
1925
1926 @doc "Signs user out of all applications"
1927 def global_sign_out(user) do
1928 OAuth.Authorization.delete_user_authorizations(user)
1929 OAuth.Token.delete_user_tokens(user)
1930 end
1931
1932 def mascot_update(user, url) do
1933 user
1934 |> cast(%{mascot: url}, [:mascot])
1935 |> validate_required([:mascot])
1936 |> update_and_set_cache()
1937 end
1938
1939 def mastodon_settings_update(user, settings) do
1940 user
1941 |> cast(%{settings: settings}, [:settings])
1942 |> validate_required([:settings])
1943 |> update_and_set_cache()
1944 end
1945
1946 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1947 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1948 params =
1949 if need_confirmation? do
1950 %{
1951 confirmation_pending: true,
1952 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1953 }
1954 else
1955 %{
1956 confirmation_pending: false,
1957 confirmation_token: nil
1958 }
1959 end
1960
1961 cast(user, params, [:confirmation_pending, :confirmation_token])
1962 end
1963
1964 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1965 if id not in user.pinned_activities do
1966 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1967 params = %{pinned_activities: user.pinned_activities ++ [id]}
1968
1969 user
1970 |> cast(params, [:pinned_activities])
1971 |> validate_length(:pinned_activities,
1972 max: max_pinned_statuses,
1973 message: "You have already pinned the maximum number of statuses"
1974 )
1975 else
1976 change(user)
1977 end
1978 |> update_and_set_cache()
1979 end
1980
1981 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1982 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1983
1984 user
1985 |> cast(params, [:pinned_activities])
1986 |> update_and_set_cache()
1987 end
1988
1989 def update_email_notifications(user, settings) do
1990 email_notifications =
1991 user.email_notifications
1992 |> Map.merge(settings)
1993 |> Map.take(["digest"])
1994
1995 params = %{email_notifications: email_notifications}
1996 fields = [:email_notifications]
1997
1998 user
1999 |> cast(params, fields)
2000 |> validate_required(fields)
2001 |> update_and_set_cache()
2002 end
2003
2004 defp set_domain_blocks(user, domain_blocks) do
2005 params = %{domain_blocks: domain_blocks}
2006
2007 user
2008 |> cast(params, [:domain_blocks])
2009 |> validate_required([:domain_blocks])
2010 |> update_and_set_cache()
2011 end
2012
2013 def block_domain(user, domain_blocked) do
2014 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2015 end
2016
2017 def unblock_domain(user, domain_blocked) do
2018 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2019 end
2020
2021 @spec add_to_block(User.t(), User.t()) ::
2022 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2023 defp add_to_block(%User{} = user, %User{} = blocked) do
2024 UserRelationship.create_block(user, blocked)
2025 end
2026
2027 @spec add_to_block(User.t(), User.t()) ::
2028 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2029 defp remove_from_block(%User{} = user, %User{} = blocked) do
2030 UserRelationship.delete_block(user, blocked)
2031 end
2032
2033 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
2034 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
2035 {:ok, user_notification_mute} <-
2036 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
2037 {:ok, nil} do
2038 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
2039 end
2040 end
2041
2042 defp remove_from_mutes(user, %User{} = muted_user) do
2043 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
2044 {:ok, user_notification_mute} <-
2045 UserRelationship.delete_notification_mute(user, muted_user) do
2046 {:ok, [user_mute, user_notification_mute]}
2047 end
2048 end
2049
2050 def set_invisible(user, invisible) do
2051 params = %{invisible: invisible}
2052
2053 user
2054 |> cast(params, [:invisible])
2055 |> validate_required([:invisible])
2056 |> update_and_set_cache()
2057 end
2058 end