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