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