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