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