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