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