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