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