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