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