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