601aa9cf04963c50e2d448f3323850c903dd7954
[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_ap_ids(user) do
766 user
767 |> get_friends_query(nil)
768 |> select([u], u.ap_id)
769 |> Repo.all()
770 end
771
772 def get_friends_ids(user, page \\ nil) do
773 user
774 |> get_friends_query(page)
775 |> select([u], u.id)
776 |> Repo.all()
777 end
778
779 defdelegate get_follow_requests(user), to: FollowingRelationship
780
781 def increase_note_count(%User{} = user) do
782 User
783 |> where(id: ^user.id)
784 |> update([u], inc: [note_count: 1])
785 |> select([u], u)
786 |> Repo.update_all([])
787 |> case do
788 {1, [user]} -> set_cache(user)
789 _ -> {:error, user}
790 end
791 end
792
793 def decrease_note_count(%User{} = user) do
794 User
795 |> where(id: ^user.id)
796 |> update([u],
797 set: [
798 note_count: fragment("greatest(0, note_count - 1)")
799 ]
800 )
801 |> select([u], u)
802 |> Repo.update_all([])
803 |> case do
804 {1, [user]} -> set_cache(user)
805 _ -> {:error, user}
806 end
807 end
808
809 def update_note_count(%User{} = user, note_count \\ nil) do
810 note_count =
811 note_count ||
812 from(
813 a in Object,
814 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
815 select: count(a.id)
816 )
817 |> Repo.one()
818
819 user
820 |> cast(%{note_count: note_count}, [:note_count])
821 |> update_and_set_cache()
822 end
823
824 @spec maybe_fetch_follow_information(User.t()) :: User.t()
825 def maybe_fetch_follow_information(user) do
826 with {:ok, user} <- fetch_follow_information(user) do
827 user
828 else
829 e ->
830 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
831
832 user
833 end
834 end
835
836 def fetch_follow_information(user) do
837 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
838 user
839 |> follow_information_changeset(info)
840 |> update_and_set_cache()
841 end
842 end
843
844 defp follow_information_changeset(user, params) do
845 user
846 |> cast(params, [
847 :hide_followers,
848 :hide_follows,
849 :follower_count,
850 :following_count,
851 :hide_followers_count,
852 :hide_follows_count
853 ])
854 end
855
856 def update_follower_count(%User{} = user) do
857 if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
858 follower_count_query =
859 User.Query.build(%{followers: user, deactivated: false})
860 |> select([u], %{count: count(u.id)})
861
862 User
863 |> where(id: ^user.id)
864 |> join(:inner, [u], s in subquery(follower_count_query))
865 |> update([u, s],
866 set: [follower_count: s.count]
867 )
868 |> select([u], u)
869 |> Repo.update_all([])
870 |> case do
871 {1, [user]} -> set_cache(user)
872 _ -> {:error, user}
873 end
874 else
875 {:ok, maybe_fetch_follow_information(user)}
876 end
877 end
878
879 @spec update_following_count(User.t()) :: User.t()
880 def update_following_count(%User{local: false} = user) do
881 if Pleroma.Config.get([:instance, :external_user_synchronization]) do
882 maybe_fetch_follow_information(user)
883 else
884 user
885 end
886 end
887
888 def update_following_count(%User{local: true} = user) do
889 following_count = FollowingRelationship.following_count(user)
890
891 user
892 |> follow_information_changeset(%{following_count: following_count})
893 |> Repo.update!()
894 end
895
896 def set_unread_conversation_count(%User{local: true} = user) do
897 unread_query = Participation.unread_conversation_count_for_user(user)
898
899 User
900 |> join(:inner, [u], p in subquery(unread_query))
901 |> update([u, p],
902 set: [unread_conversation_count: p.count]
903 )
904 |> where([u], u.id == ^user.id)
905 |> select([u], u)
906 |> Repo.update_all([])
907 |> case do
908 {1, [user]} -> set_cache(user)
909 _ -> {:error, user}
910 end
911 end
912
913 def set_unread_conversation_count(user), do: {:ok, user}
914
915 def increment_unread_conversation_count(conversation, %User{local: true} = user) do
916 unread_query =
917 Participation.unread_conversation_count_for_user(user)
918 |> where([p], p.conversation_id == ^conversation.id)
919
920 User
921 |> join(:inner, [u], p in subquery(unread_query))
922 |> update([u, p],
923 inc: [unread_conversation_count: 1]
924 )
925 |> where([u], u.id == ^user.id)
926 |> where([u, p], p.count == 0)
927 |> select([u], u)
928 |> Repo.update_all([])
929 |> case do
930 {1, [user]} -> set_cache(user)
931 _ -> {:error, user}
932 end
933 end
934
935 def increment_unread_conversation_count(_, user), do: {:ok, user}
936
937 @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
938 def get_users_from_set(ap_ids, local_only \\ true) do
939 criteria = %{ap_id: ap_ids, deactivated: false}
940 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
941
942 User.Query.build(criteria)
943 |> Repo.all()
944 end
945
946 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
947 def get_recipients_from_activity(%Activity{recipients: to}) do
948 User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
949 |> Repo.all()
950 end
951
952 @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
953 def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
954 add_to_mutes(muter, ap_id, notifications?)
955 end
956
957 def unmute(muter, %{ap_id: ap_id}) do
958 remove_from_mutes(muter, ap_id)
959 end
960
961 def subscribe(subscriber, %{ap_id: ap_id}) do
962 with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
963 deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
964
965 if blocks?(subscribed, subscriber) and deny_follow_blocked do
966 {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
967 else
968 User.add_to_subscribers(subscribed, subscriber.ap_id)
969 end
970 end
971 end
972
973 def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
974 with %User{} = user <- get_cached_by_ap_id(ap_id) do
975 User.remove_from_subscribers(user, unsubscriber.ap_id)
976 end
977 end
978
979 def block(blocker, %User{ap_id: ap_id} = blocked) do
980 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
981 blocker =
982 if following?(blocker, blocked) do
983 {:ok, blocker, _} = unfollow(blocker, blocked)
984 blocker
985 else
986 blocker
987 end
988
989 # clear any requested follows as well
990 blocked =
991 case CommonAPI.reject_follow_request(blocked, blocker) do
992 {:ok, %User{} = updated_blocked} -> updated_blocked
993 nil -> blocked
994 end
995
996 blocker =
997 if subscribed_to?(blocked, blocker) do
998 {:ok, blocker} = unsubscribe(blocked, blocker)
999 blocker
1000 else
1001 blocker
1002 end
1003
1004 if following?(blocked, blocker), do: unfollow(blocked, blocker)
1005
1006 {:ok, blocker} = update_follower_count(blocker)
1007 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1008 add_to_block(blocker, ap_id)
1009 end
1010
1011 # helper to handle the block given only an actor's AP id
1012 def block(blocker, %{ap_id: ap_id}) do
1013 block(blocker, get_cached_by_ap_id(ap_id))
1014 end
1015
1016 def unblock(blocker, %{ap_id: ap_id}) do
1017 remove_from_block(blocker, ap_id)
1018 end
1019
1020 def mutes?(nil, _), do: false
1021 def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
1022
1023 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1024 def muted_notifications?(nil, _), do: false
1025
1026 def muted_notifications?(user, %{ap_id: ap_id}),
1027 do: Enum.member?(user.muted_notifications, ap_id)
1028
1029 def blocks?(%User{} = user, %User{} = target) do
1030 blocks_ap_id?(user, target) ||
1031 (!User.following?(user, target) && blocks_domain?(user, target))
1032 end
1033
1034 def blocks?(nil, _), do: false
1035
1036 def blocks_ap_id?(%User{} = user, %User{} = target) do
1037 Enum.member?(user.blocks, target.ap_id)
1038 end
1039
1040 def blocks_ap_id?(_, _), do: false
1041
1042 def blocks_domain?(%User{} = user, %User{} = target) do
1043 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1044 %{host: host} = URI.parse(target.ap_id)
1045 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1046 end
1047
1048 def blocks_domain?(_, _), do: false
1049
1050 def subscribed_to?(user, %{ap_id: ap_id}) do
1051 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1052 Enum.member?(target.subscribers, user.ap_id)
1053 end
1054 end
1055
1056 @spec muted_users(User.t()) :: [User.t()]
1057 def muted_users(user) do
1058 User.Query.build(%{ap_id: user.mutes, deactivated: false})
1059 |> Repo.all()
1060 end
1061
1062 @spec blocked_users(User.t()) :: [User.t()]
1063 def blocked_users(user) do
1064 User.Query.build(%{ap_id: user.blocks, deactivated: false})
1065 |> Repo.all()
1066 end
1067
1068 @spec subscribers(User.t()) :: [User.t()]
1069 def subscribers(user) do
1070 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1071 |> Repo.all()
1072 end
1073
1074 def deactivate_async(user, status \\ true) do
1075 BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
1076 end
1077
1078 def deactivate(user, status \\ true)
1079
1080 def deactivate(users, status) when is_list(users) do
1081 Repo.transaction(fn ->
1082 for user <- users, do: deactivate(user, status)
1083 end)
1084 end
1085
1086 def deactivate(%User{} = user, status) do
1087 with {:ok, user} <- set_activation_status(user, status) do
1088 user
1089 |> get_followers()
1090 |> Enum.filter(& &1.local)
1091 |> Enum.each(fn follower ->
1092 follower |> update_following_count() |> set_cache()
1093 end)
1094
1095 # Only update local user counts, remote will be update during the next pull.
1096 user
1097 |> get_friends()
1098 |> Enum.filter(& &1.local)
1099 |> Enum.each(&update_follower_count/1)
1100
1101 {:ok, user}
1102 end
1103 end
1104
1105 def update_notification_settings(%User{} = user, settings) do
1106 settings =
1107 settings
1108 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1109 |> Map.new()
1110
1111 notification_settings =
1112 user.notification_settings
1113 |> Map.merge(settings)
1114 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1115
1116 params = %{notification_settings: notification_settings}
1117
1118 user
1119 |> cast(params, [:notification_settings])
1120 |> validate_required([:notification_settings])
1121 |> update_and_set_cache()
1122 end
1123
1124 def delete(users) when is_list(users) do
1125 for user <- users, do: delete(user)
1126 end
1127
1128 def delete(%User{} = user) do
1129 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1130 end
1131
1132 def perform(:force_password_reset, user), do: force_password_reset(user)
1133
1134 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1135 def perform(:delete, %User{} = user) do
1136 {:ok, _user} = ActivityPub.delete(user)
1137
1138 # Remove all relationships
1139 user
1140 |> get_followers()
1141 |> Enum.each(fn follower ->
1142 ActivityPub.unfollow(follower, user)
1143 unfollow(follower, user)
1144 end)
1145
1146 user
1147 |> get_friends()
1148 |> Enum.each(fn followed ->
1149 ActivityPub.unfollow(user, followed)
1150 unfollow(user, followed)
1151 end)
1152
1153 delete_user_activities(user)
1154 invalidate_cache(user)
1155 Repo.delete(user)
1156 end
1157
1158 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1159 def perform(:fetch_initial_posts, %User{} = user) do
1160 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1161
1162 # Insert all the posts in reverse order, so they're in the right order on the timeline
1163 user.source_data["outbox"]
1164 |> Utils.fetch_ordered_collection(pages)
1165 |> Enum.reverse()
1166 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1167 end
1168
1169 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1170
1171 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1172 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1173 when is_list(blocked_identifiers) do
1174 Enum.map(
1175 blocked_identifiers,
1176 fn blocked_identifier ->
1177 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1178 {:ok, blocker} <- block(blocker, blocked),
1179 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1180 blocked
1181 else
1182 err ->
1183 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1184 err
1185 end
1186 end
1187 )
1188 end
1189
1190 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1191 def perform(:follow_import, %User{} = follower, followed_identifiers)
1192 when is_list(followed_identifiers) do
1193 Enum.map(
1194 followed_identifiers,
1195 fn followed_identifier ->
1196 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1197 {:ok, follower} <- maybe_direct_follow(follower, followed),
1198 {:ok, _} <- ActivityPub.follow(follower, followed) do
1199 followed
1200 else
1201 err ->
1202 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1203 err
1204 end
1205 end
1206 )
1207 end
1208
1209 @spec external_users_query() :: Ecto.Query.t()
1210 def external_users_query do
1211 User.Query.build(%{
1212 external: true,
1213 active: true,
1214 order_by: :id
1215 })
1216 end
1217
1218 @spec external_users(keyword()) :: [User.t()]
1219 def external_users(opts \\ []) do
1220 query =
1221 external_users_query()
1222 |> select([u], struct(u, [:id, :ap_id, :info]))
1223
1224 query =
1225 if opts[:max_id],
1226 do: where(query, [u], u.id > ^opts[:max_id]),
1227 else: query
1228
1229 query =
1230 if opts[:limit],
1231 do: limit(query, ^opts[:limit]),
1232 else: query
1233
1234 Repo.all(query)
1235 end
1236
1237 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1238 BackgroundWorker.enqueue("blocks_import", %{
1239 "blocker_id" => blocker.id,
1240 "blocked_identifiers" => blocked_identifiers
1241 })
1242 end
1243
1244 def follow_import(%User{} = follower, followed_identifiers)
1245 when is_list(followed_identifiers) do
1246 BackgroundWorker.enqueue("follow_import", %{
1247 "follower_id" => follower.id,
1248 "followed_identifiers" => followed_identifiers
1249 })
1250 end
1251
1252 def delete_user_activities(%User{ap_id: ap_id}) do
1253 ap_id
1254 |> Activity.Queries.by_actor()
1255 |> RepoStreamer.chunk_stream(50)
1256 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1257 |> Stream.run()
1258 end
1259
1260 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1261 activity
1262 |> Object.normalize()
1263 |> ActivityPub.delete()
1264 end
1265
1266 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1267 object = Object.normalize(activity)
1268
1269 activity.actor
1270 |> get_cached_by_ap_id()
1271 |> ActivityPub.unlike(object)
1272 end
1273
1274 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1275 object = Object.normalize(activity)
1276
1277 activity.actor
1278 |> get_cached_by_ap_id()
1279 |> ActivityPub.unannounce(object)
1280 end
1281
1282 defp delete_activity(_activity), do: "Doing nothing"
1283
1284 def html_filter_policy(%User{no_rich_text: true}) do
1285 Pleroma.HTML.Scrubber.TwitterText
1286 end
1287
1288 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1289
1290 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1291
1292 def get_or_fetch_by_ap_id(ap_id) do
1293 user = get_cached_by_ap_id(ap_id)
1294
1295 if !is_nil(user) and !needs_update?(user) do
1296 {:ok, user}
1297 else
1298 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1299 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1300
1301 resp = fetch_by_ap_id(ap_id)
1302
1303 if should_fetch_initial do
1304 with {:ok, %User{} = user} <- resp do
1305 fetch_initial_posts(user)
1306 end
1307 end
1308
1309 resp
1310 end
1311 end
1312
1313 @doc """
1314 Creates an internal service actor by URI if missing.
1315 Optionally takes nickname for addressing.
1316 """
1317 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1318 with user when is_nil(user) <- get_cached_by_ap_id(uri) do
1319 {:ok, user} =
1320 %User{
1321 invisible: true,
1322 local: true,
1323 ap_id: uri,
1324 nickname: nickname,
1325 follower_address: uri <> "/followers"
1326 }
1327 |> Repo.insert()
1328
1329 user
1330 end
1331 end
1332
1333 # AP style
1334 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1335 key =
1336 public_key_pem
1337 |> :public_key.pem_decode()
1338 |> hd()
1339 |> :public_key.pem_entry_decode()
1340
1341 {:ok, key}
1342 end
1343
1344 def public_key(_), do: {:error, "not found key"}
1345
1346 def get_public_key_for_ap_id(ap_id) do
1347 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1348 {:ok, public_key} <- public_key(user) do
1349 {:ok, public_key}
1350 else
1351 _ -> :error
1352 end
1353 end
1354
1355 defp blank?(""), do: nil
1356 defp blank?(n), do: n
1357
1358 def insert_or_update_user(data) do
1359 data
1360 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1361 |> remote_user_creation()
1362 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1363 |> set_cache()
1364 end
1365
1366 def ap_enabled?(%User{local: true}), do: true
1367 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1368 def ap_enabled?(_), do: false
1369
1370 @doc "Gets or fetch a user by uri or nickname."
1371 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1372 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1373 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1374
1375 # wait a period of time and return newest version of the User structs
1376 # this is because we have synchronous follow APIs and need to simulate them
1377 # with an async handshake
1378 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1379 with %User{} = a <- get_cached_by_id(a.id),
1380 %User{} = b <- get_cached_by_id(b.id) do
1381 {:ok, a, b}
1382 else
1383 nil -> :error
1384 end
1385 end
1386
1387 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1388 with :ok <- :timer.sleep(timeout),
1389 %User{} = a <- get_cached_by_id(a.id),
1390 %User{} = b <- get_cached_by_id(b.id) do
1391 {:ok, a, b}
1392 else
1393 nil -> :error
1394 end
1395 end
1396
1397 def parse_bio(bio) when is_binary(bio) and bio != "" do
1398 bio
1399 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1400 |> elem(0)
1401 end
1402
1403 def parse_bio(_), do: ""
1404
1405 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1406 # TODO: get profile URLs other than user.ap_id
1407 profile_urls = [user.ap_id]
1408
1409 bio
1410 |> CommonUtils.format_input("text/plain",
1411 mentions_format: :full,
1412 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1413 )
1414 |> elem(0)
1415 end
1416
1417 def parse_bio(_, _), do: ""
1418
1419 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1420 Repo.transaction(fn ->
1421 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1422 end)
1423 end
1424
1425 def tag(nickname, tags) when is_binary(nickname),
1426 do: tag(get_by_nickname(nickname), tags)
1427
1428 def tag(%User{} = user, tags),
1429 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1430
1431 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1432 Repo.transaction(fn ->
1433 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1434 end)
1435 end
1436
1437 def untag(nickname, tags) when is_binary(nickname),
1438 do: untag(get_by_nickname(nickname), tags)
1439
1440 def untag(%User{} = user, tags),
1441 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1442
1443 defp update_tags(%User{} = user, new_tags) do
1444 {:ok, updated_user} =
1445 user
1446 |> change(%{tags: new_tags})
1447 |> update_and_set_cache()
1448
1449 updated_user
1450 end
1451
1452 defp normalize_tags(tags) do
1453 [tags]
1454 |> List.flatten()
1455 |> Enum.map(&String.downcase/1)
1456 end
1457
1458 defp local_nickname_regex do
1459 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1460 @extended_local_nickname_regex
1461 else
1462 @strict_local_nickname_regex
1463 end
1464 end
1465
1466 def local_nickname(nickname_or_mention) do
1467 nickname_or_mention
1468 |> full_nickname()
1469 |> String.split("@")
1470 |> hd()
1471 end
1472
1473 def full_nickname(nickname_or_mention),
1474 do: String.trim_leading(nickname_or_mention, "@")
1475
1476 def error_user(ap_id) do
1477 %User{
1478 name: ap_id,
1479 ap_id: ap_id,
1480 nickname: "erroruser@example.com",
1481 inserted_at: NaiveDateTime.utc_now()
1482 }
1483 end
1484
1485 @spec all_superusers() :: [User.t()]
1486 def all_superusers do
1487 User.Query.build(%{super_users: true, local: true, deactivated: false})
1488 |> Repo.all()
1489 end
1490
1491 def showing_reblogs?(%User{} = user, %User{} = target) do
1492 target.ap_id not in user.muted_reblogs
1493 end
1494
1495 @doc """
1496 The function returns a query to get users with no activity for given interval of days.
1497 Inactive users are those who didn't read any notification, or had any activity where
1498 the user is the activity's actor, during `inactivity_threshold` days.
1499 Deactivated users will not appear in this list.
1500
1501 ## Examples
1502
1503 iex> Pleroma.User.list_inactive_users()
1504 %Ecto.Query{}
1505 """
1506 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1507 def list_inactive_users_query(inactivity_threshold \\ 7) do
1508 negative_inactivity_threshold = -inactivity_threshold
1509 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1510 # Subqueries are not supported in `where` clauses, join gets too complicated.
1511 has_read_notifications =
1512 from(n in Pleroma.Notification,
1513 where: n.seen == true,
1514 group_by: n.id,
1515 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1516 select: n.user_id
1517 )
1518 |> Pleroma.Repo.all()
1519
1520 from(u in Pleroma.User,
1521 left_join: a in Pleroma.Activity,
1522 on: u.ap_id == a.actor,
1523 where: not is_nil(u.nickname),
1524 where: u.deactivated != ^true,
1525 where: u.id not in ^has_read_notifications,
1526 group_by: u.id,
1527 having:
1528 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1529 is_nil(max(a.inserted_at))
1530 )
1531 end
1532
1533 @doc """
1534 Enable or disable email notifications for user
1535
1536 ## Examples
1537
1538 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1539 Pleroma.User{email_notifications: %{"digest" => true}}
1540
1541 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1542 Pleroma.User{email_notifications: %{"digest" => false}}
1543 """
1544 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1545 {:ok, t()} | {:error, Ecto.Changeset.t()}
1546 def switch_email_notifications(user, type, status) do
1547 User.update_email_notifications(user, %{type => status})
1548 end
1549
1550 @doc """
1551 Set `last_digest_emailed_at` value for the user to current time
1552 """
1553 @spec touch_last_digest_emailed_at(t()) :: t()
1554 def touch_last_digest_emailed_at(user) do
1555 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1556
1557 {:ok, updated_user} =
1558 user
1559 |> change(%{last_digest_emailed_at: now})
1560 |> update_and_set_cache()
1561
1562 updated_user
1563 end
1564
1565 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1566 def toggle_confirmation(%User{} = user) do
1567 user
1568 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1569 |> update_and_set_cache()
1570 end
1571
1572 @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
1573 def toggle_confirmation(users) do
1574 Enum.map(users, &toggle_confirmation/1)
1575 end
1576
1577 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
1578 mascot
1579 end
1580
1581 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
1582 # use instance-default
1583 config = Pleroma.Config.get([:assets, :mascots])
1584 default_mascot = Pleroma.Config.get([:assets, :default_mascot])
1585 mascot = Keyword.get(config, default_mascot)
1586
1587 %{
1588 "id" => "default-mascot",
1589 "url" => mascot[:url],
1590 "preview_url" => mascot[:url],
1591 "pleroma" => %{
1592 "mime_type" => mascot[:mime_type]
1593 }
1594 }
1595 end
1596
1597 def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
1598
1599 def ensure_keys_present(%User{} = user) do
1600 with {:ok, pem} <- Keys.generate_rsa_pem() do
1601 user
1602 |> cast(%{keys: pem}, [:keys])
1603 |> validate_required([:keys])
1604 |> update_and_set_cache()
1605 end
1606 end
1607
1608 def get_ap_ids_by_nicknames(nicknames) do
1609 from(u in User,
1610 where: u.nickname in ^nicknames,
1611 select: u.ap_id
1612 )
1613 |> Repo.all()
1614 end
1615
1616 defdelegate search(query, opts \\ []), to: User.Search
1617
1618 defp put_password_hash(
1619 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
1620 ) do
1621 change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
1622 end
1623
1624 defp put_password_hash(changeset), do: changeset
1625
1626 def is_internal_user?(%User{nickname: nil}), do: true
1627 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
1628 def is_internal_user?(_), do: false
1629
1630 # A hack because user delete activities have a fake id for whatever reason
1631 # TODO: Get rid of this
1632 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
1633
1634 def get_delivered_users_by_object_id(object_id) do
1635 from(u in User,
1636 inner_join: delivery in assoc(u, :deliveries),
1637 where: delivery.object_id == ^object_id
1638 )
1639 |> Repo.all()
1640 end
1641
1642 def change_email(user, email) do
1643 user
1644 |> cast(%{email: email}, [:email])
1645 |> validate_required([:email])
1646 |> unique_constraint(:email)
1647 |> validate_format(:email, @email_regex)
1648 |> update_and_set_cache()
1649 end
1650
1651 # Internal function; public one is `deactivate/2`
1652 defp set_activation_status(user, deactivated) do
1653 user
1654 |> cast(%{deactivated: deactivated}, [:deactivated])
1655 |> update_and_set_cache()
1656 end
1657
1658 def update_banner(user, banner) do
1659 user
1660 |> cast(%{banner: banner}, [:banner])
1661 |> update_and_set_cache()
1662 end
1663
1664 def update_background(user, background) do
1665 user
1666 |> cast(%{background: background}, [:background])
1667 |> update_and_set_cache()
1668 end
1669
1670 def update_source_data(user, source_data) do
1671 user
1672 |> cast(%{source_data: source_data}, [:source_data])
1673 |> update_and_set_cache()
1674 end
1675
1676 def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
1677 %{
1678 admin: is_admin,
1679 moderator: is_moderator
1680 }
1681 end
1682
1683 # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
1684 # For example: [{"name": "Pronoun", "value": "she/her"}, …]
1685 def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
1686 limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
1687
1688 attachment
1689 |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
1690 |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
1691 |> Enum.take(limit)
1692 end
1693
1694 def fields(%{fields: nil}), do: []
1695
1696 def fields(%{fields: fields}), do: fields
1697
1698 def validate_fields(changeset, remote? \\ false) do
1699 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
1700 limit = Pleroma.Config.get([:instance, limit_name], 0)
1701
1702 changeset
1703 |> validate_length(:fields, max: limit)
1704 |> validate_change(:fields, fn :fields, fields ->
1705 if Enum.all?(fields, &valid_field?/1) do
1706 []
1707 else
1708 [fields: "invalid"]
1709 end
1710 end)
1711 end
1712
1713 defp valid_field?(%{"name" => name, "value" => value}) do
1714 name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
1715 value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
1716
1717 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
1718 String.length(value) <= value_limit
1719 end
1720
1721 defp valid_field?(_), do: false
1722
1723 defp truncate_field(%{"name" => name, "value" => value}) do
1724 {name, _chopped} =
1725 String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
1726
1727 {value, _chopped} =
1728 String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
1729
1730 %{"name" => name, "value" => value}
1731 end
1732
1733 def admin_api_update(user, params) do
1734 user
1735 |> cast(params, [
1736 :is_moderator,
1737 :is_admin,
1738 :show_role
1739 ])
1740 |> update_and_set_cache()
1741 end
1742
1743 def mascot_update(user, url) do
1744 user
1745 |> cast(%{mascot: url}, [:mascot])
1746 |> validate_required([:mascot])
1747 |> update_and_set_cache()
1748 end
1749
1750 def mastodon_settings_update(user, settings) do
1751 user
1752 |> cast(%{settings: settings}, [:settings])
1753 |> validate_required([:settings])
1754 |> update_and_set_cache()
1755 end
1756
1757 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
1758 def confirmation_changeset(user, need_confirmation: need_confirmation?) do
1759 params =
1760 if need_confirmation? do
1761 %{
1762 confirmation_pending: true,
1763 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
1764 }
1765 else
1766 %{
1767 confirmation_pending: false,
1768 confirmation_token: nil
1769 }
1770 end
1771
1772 cast(user, params, [:confirmation_pending, :confirmation_token])
1773 end
1774
1775 def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1776 if id not in user.pinned_activities do
1777 max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
1778 params = %{pinned_activities: user.pinned_activities ++ [id]}
1779
1780 user
1781 |> cast(params, [:pinned_activities])
1782 |> validate_length(:pinned_activities,
1783 max: max_pinned_statuses,
1784 message: "You have already pinned the maximum number of statuses"
1785 )
1786 else
1787 change(user)
1788 end
1789 |> update_and_set_cache()
1790 end
1791
1792 def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
1793 params = %{pinned_activities: List.delete(user.pinned_activities, id)}
1794
1795 user
1796 |> cast(params, [:pinned_activities])
1797 |> update_and_set_cache()
1798 end
1799
1800 def update_email_notifications(user, settings) do
1801 email_notifications =
1802 user.email_notifications
1803 |> Map.merge(settings)
1804 |> Map.take(["digest"])
1805
1806 params = %{email_notifications: email_notifications}
1807 fields = [:email_notifications]
1808
1809 user
1810 |> cast(params, fields)
1811 |> validate_required(fields)
1812 |> update_and_set_cache()
1813 end
1814
1815 defp set_subscribers(user, subscribers) do
1816 params = %{subscribers: subscribers}
1817
1818 user
1819 |> cast(params, [:subscribers])
1820 |> validate_required([:subscribers])
1821 |> update_and_set_cache()
1822 end
1823
1824 def add_to_subscribers(user, subscribed) do
1825 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1826 end
1827
1828 def remove_from_subscribers(user, subscribed) do
1829 set_subscribers(user, List.delete(user.subscribers, subscribed))
1830 end
1831
1832 defp set_domain_blocks(user, domain_blocks) do
1833 params = %{domain_blocks: domain_blocks}
1834
1835 user
1836 |> cast(params, [:domain_blocks])
1837 |> validate_required([:domain_blocks])
1838 |> update_and_set_cache()
1839 end
1840
1841 def block_domain(user, domain_blocked) do
1842 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1843 end
1844
1845 def unblock_domain(user, domain_blocked) do
1846 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1847 end
1848
1849 defp set_blocks(user, blocks) do
1850 params = %{blocks: blocks}
1851
1852 user
1853 |> cast(params, [:blocks])
1854 |> validate_required([:blocks])
1855 |> update_and_set_cache()
1856 end
1857
1858 def add_to_block(user, blocked) do
1859 set_blocks(user, Enum.uniq([blocked | user.blocks]))
1860 end
1861
1862 def remove_from_block(user, blocked) do
1863 set_blocks(user, List.delete(user.blocks, blocked))
1864 end
1865
1866 defp set_mutes(user, mutes) do
1867 params = %{mutes: mutes}
1868
1869 user
1870 |> cast(params, [:mutes])
1871 |> validate_required([:mutes])
1872 |> update_and_set_cache()
1873 end
1874
1875 def add_to_mutes(user, muted, notifications?) do
1876 with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
1877 set_notification_mutes(
1878 user,
1879 Enum.uniq([muted | user.muted_notifications]),
1880 notifications?
1881 )
1882 end
1883 end
1884
1885 def remove_from_mutes(user, muted) do
1886 with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
1887 set_notification_mutes(
1888 user,
1889 List.delete(user.muted_notifications, muted),
1890 true
1891 )
1892 end
1893 end
1894
1895 defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
1896 {:ok, user}
1897 end
1898
1899 defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
1900 params = %{muted_notifications: muted_notifications}
1901
1902 user
1903 |> cast(params, [:muted_notifications])
1904 |> validate_required([:muted_notifications])
1905 |> update_and_set_cache()
1906 end
1907
1908 def add_reblog_mute(user, ap_id) do
1909 params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
1910
1911 user
1912 |> cast(params, [:muted_reblogs])
1913 |> update_and_set_cache()
1914 end
1915
1916 def remove_reblog_mute(user, ap_id) do
1917 params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
1918
1919 user
1920 |> cast(params, [:muted_reblogs])
1921 |> update_and_set_cache()
1922 end
1923
1924 def set_invisible(user, invisible) do
1925 params = %{invisible: invisible}
1926
1927 user
1928 |> cast(params, [:invisible])
1929 |> validate_required([:invisible])
1930 |> update_and_set_cache()
1931 end
1932 end