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