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