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