615f1b72575e7b51996d7f3096b1333fe4aa7cd8
[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 related AP IDs list by relation type.
1162 E.g. `related_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1163 """
1164 @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1165 def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do
1166 db_result =
1167 user
1168 |> assoc(:outgoing_relationships)
1169 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1170 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1171 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1172 |> group_by([user_rel, u], user_rel.relationship_type)
1173 |> Repo.all()
1174 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1175
1176 Enum.into(
1177 relationship_types,
1178 %{},
1179 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1180 )
1181 end
1182
1183 @spec subscribers(User.t()) :: [User.t()]
1184 def subscribers(user) do
1185 User.Query.build(%{ap_id: user.subscribers, deactivated: false})
1186 |> Repo.all()
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 Enum.each(get_followers(user), &invalidate_cache/1)
1204
1205 # Only update local user counts, remote will be update during the next pull.
1206 user
1207 |> get_friends()
1208 |> Enum.filter(& &1.local)
1209 |> Enum.each(&update_follower_count/1)
1210
1211 {:ok, user}
1212 end
1213 end
1214
1215 def update_notification_settings(%User{} = user, settings) do
1216 settings =
1217 settings
1218 |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
1219 |> Map.new()
1220
1221 notification_settings =
1222 user.notification_settings
1223 |> Map.merge(settings)
1224 |> Map.take(["followers", "follows", "non_follows", "non_followers"])
1225
1226 params = %{notification_settings: notification_settings}
1227
1228 user
1229 |> cast(params, [:notification_settings])
1230 |> validate_required([:notification_settings])
1231 |> update_and_set_cache()
1232 end
1233
1234 def delete(users) when is_list(users) do
1235 for user <- users, do: delete(user)
1236 end
1237
1238 def delete(%User{} = user) do
1239 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1240 end
1241
1242 def perform(:force_password_reset, user), do: force_password_reset(user)
1243
1244 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1245 def perform(:delete, %User{} = user) do
1246 {:ok, _user} = ActivityPub.delete(user)
1247
1248 # Remove all relationships
1249 user
1250 |> get_followers()
1251 |> Enum.each(fn follower ->
1252 ActivityPub.unfollow(follower, user)
1253 unfollow(follower, user)
1254 end)
1255
1256 user
1257 |> get_friends()
1258 |> Enum.each(fn followed ->
1259 ActivityPub.unfollow(user, followed)
1260 unfollow(user, followed)
1261 end)
1262
1263 delete_user_activities(user)
1264 invalidate_cache(user)
1265 Repo.delete(user)
1266 end
1267
1268 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1269 def perform(:fetch_initial_posts, %User{} = user) do
1270 pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
1271
1272 # Insert all the posts in reverse order, so they're in the right order on the timeline
1273 user.source_data["outbox"]
1274 |> Utils.fetch_ordered_collection(pages)
1275 |> Enum.reverse()
1276 |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
1277 end
1278
1279 def perform(:deactivate_async, user, status), do: deactivate(user, status)
1280
1281 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1282 def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
1283 when is_list(blocked_identifiers) do
1284 Enum.map(
1285 blocked_identifiers,
1286 fn blocked_identifier ->
1287 with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
1288 {:ok, _user_block} <- block(blocker, blocked),
1289 {:ok, _} <- ActivityPub.block(blocker, blocked) do
1290 blocked
1291 else
1292 err ->
1293 Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
1294 err
1295 end
1296 end
1297 )
1298 end
1299
1300 @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
1301 def perform(:follow_import, %User{} = follower, followed_identifiers)
1302 when is_list(followed_identifiers) do
1303 Enum.map(
1304 followed_identifiers,
1305 fn followed_identifier ->
1306 with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
1307 {:ok, follower} <- maybe_direct_follow(follower, followed),
1308 {:ok, _} <- ActivityPub.follow(follower, followed) do
1309 followed
1310 else
1311 err ->
1312 Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
1313 err
1314 end
1315 end
1316 )
1317 end
1318
1319 @spec external_users_query() :: Ecto.Query.t()
1320 def external_users_query do
1321 User.Query.build(%{
1322 external: true,
1323 active: true,
1324 order_by: :id
1325 })
1326 end
1327
1328 @spec external_users(keyword()) :: [User.t()]
1329 def external_users(opts \\ []) do
1330 query =
1331 external_users_query()
1332 |> select([u], struct(u, [:id, :ap_id, :info]))
1333
1334 query =
1335 if opts[:max_id],
1336 do: where(query, [u], u.id > ^opts[:max_id]),
1337 else: query
1338
1339 query =
1340 if opts[:limit],
1341 do: limit(query, ^opts[:limit]),
1342 else: query
1343
1344 Repo.all(query)
1345 end
1346
1347 def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
1348 BackgroundWorker.enqueue("blocks_import", %{
1349 "blocker_id" => blocker.id,
1350 "blocked_identifiers" => blocked_identifiers
1351 })
1352 end
1353
1354 def follow_import(%User{} = follower, followed_identifiers)
1355 when is_list(followed_identifiers) do
1356 BackgroundWorker.enqueue("follow_import", %{
1357 "follower_id" => follower.id,
1358 "followed_identifiers" => followed_identifiers
1359 })
1360 end
1361
1362 def delete_user_activities(%User{ap_id: ap_id}) do
1363 ap_id
1364 |> Activity.Queries.by_actor()
1365 |> RepoStreamer.chunk_stream(50)
1366 |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
1367 |> Stream.run()
1368 end
1369
1370 defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
1371 activity
1372 |> Object.normalize()
1373 |> ActivityPub.delete()
1374 end
1375
1376 defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
1377 object = Object.normalize(activity)
1378
1379 activity.actor
1380 |> get_cached_by_ap_id()
1381 |> ActivityPub.unlike(object)
1382 end
1383
1384 defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
1385 object = Object.normalize(activity)
1386
1387 activity.actor
1388 |> get_cached_by_ap_id()
1389 |> ActivityPub.unannounce(object)
1390 end
1391
1392 defp delete_activity(_activity), do: "Doing nothing"
1393
1394 def html_filter_policy(%User{no_rich_text: true}) do
1395 Pleroma.HTML.Scrubber.TwitterText
1396 end
1397
1398 def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
1399
1400 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1401
1402 def get_or_fetch_by_ap_id(ap_id) do
1403 user = get_cached_by_ap_id(ap_id)
1404
1405 if !is_nil(user) and !needs_update?(user) do
1406 {:ok, user}
1407 else
1408 # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
1409 should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
1410
1411 resp = fetch_by_ap_id(ap_id)
1412
1413 if should_fetch_initial do
1414 with {:ok, %User{} = user} <- resp do
1415 fetch_initial_posts(user)
1416 end
1417 end
1418
1419 resp
1420 end
1421 end
1422
1423 @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
1424 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
1425 with %User{} = user <- get_cached_by_ap_id(uri) do
1426 user
1427 else
1428 _ ->
1429 {:ok, user} =
1430 %User{}
1431 |> cast(%{}, [:ap_id, :nickname, :local])
1432 |> put_change(:ap_id, uri)
1433 |> put_change(:nickname, nickname)
1434 |> put_change(:local, true)
1435 |> put_change(:follower_address, uri <> "/followers")
1436 |> Repo.insert()
1437
1438 user
1439 end
1440 end
1441
1442 # AP style
1443 def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
1444 key =
1445 public_key_pem
1446 |> :public_key.pem_decode()
1447 |> hd()
1448 |> :public_key.pem_entry_decode()
1449
1450 {:ok, key}
1451 end
1452
1453 def public_key(_), do: {:error, "not found key"}
1454
1455 def get_public_key_for_ap_id(ap_id) do
1456 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1457 {:ok, public_key} <- public_key(user) do
1458 {:ok, public_key}
1459 else
1460 _ -> :error
1461 end
1462 end
1463
1464 defp blank?(""), do: nil
1465 defp blank?(n), do: n
1466
1467 def insert_or_update_user(data) do
1468 data
1469 |> Map.put(:name, blank?(data[:name]) || data[:nickname])
1470 |> remote_user_creation()
1471 |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
1472 |> set_cache()
1473 end
1474
1475 def ap_enabled?(%User{local: true}), do: true
1476 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
1477 def ap_enabled?(_), do: false
1478
1479 @doc "Gets or fetch a user by uri or nickname."
1480 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
1481 def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
1482 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
1483
1484 # wait a period of time and return newest version of the User structs
1485 # this is because we have synchronous follow APIs and need to simulate them
1486 # with an async handshake
1487 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
1488 with %User{} = a <- get_cached_by_id(a.id),
1489 %User{} = b <- get_cached_by_id(b.id) do
1490 {:ok, a, b}
1491 else
1492 nil -> :error
1493 end
1494 end
1495
1496 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
1497 with :ok <- :timer.sleep(timeout),
1498 %User{} = a <- get_cached_by_id(a.id),
1499 %User{} = b <- get_cached_by_id(b.id) do
1500 {:ok, a, b}
1501 else
1502 nil -> :error
1503 end
1504 end
1505
1506 def parse_bio(bio) when is_binary(bio) and bio != "" do
1507 bio
1508 |> CommonUtils.format_input("text/plain", mentions_format: :full)
1509 |> elem(0)
1510 end
1511
1512 def parse_bio(_), do: ""
1513
1514 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
1515 # TODO: get profile URLs other than user.ap_id
1516 profile_urls = [user.ap_id]
1517
1518 bio
1519 |> CommonUtils.format_input("text/plain",
1520 mentions_format: :full,
1521 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
1522 )
1523 |> elem(0)
1524 end
1525
1526 def parse_bio(_, _), do: ""
1527
1528 def tag(user_identifiers, tags) when is_list(user_identifiers) do
1529 Repo.transaction(fn ->
1530 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
1531 end)
1532 end
1533
1534 def tag(nickname, tags) when is_binary(nickname),
1535 do: tag(get_by_nickname(nickname), tags)
1536
1537 def tag(%User{} = user, tags),
1538 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
1539
1540 def untag(user_identifiers, tags) when is_list(user_identifiers) do
1541 Repo.transaction(fn ->
1542 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
1543 end)
1544 end
1545
1546 def untag(nickname, tags) when is_binary(nickname),
1547 do: untag(get_by_nickname(nickname), tags)
1548
1549 def untag(%User{} = user, tags),
1550 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
1551
1552 defp update_tags(%User{} = user, new_tags) do
1553 {:ok, updated_user} =
1554 user
1555 |> change(%{tags: new_tags})
1556 |> update_and_set_cache()
1557
1558 updated_user
1559 end
1560
1561 defp normalize_tags(tags) do
1562 [tags]
1563 |> List.flatten()
1564 |> Enum.map(&String.downcase/1)
1565 end
1566
1567 defp local_nickname_regex do
1568 if Pleroma.Config.get([:instance, :extended_nickname_format]) do
1569 @extended_local_nickname_regex
1570 else
1571 @strict_local_nickname_regex
1572 end
1573 end
1574
1575 def local_nickname(nickname_or_mention) do
1576 nickname_or_mention
1577 |> full_nickname()
1578 |> String.split("@")
1579 |> hd()
1580 end
1581
1582 def full_nickname(nickname_or_mention),
1583 do: String.trim_leading(nickname_or_mention, "@")
1584
1585 def error_user(ap_id) do
1586 %User{
1587 name: ap_id,
1588 ap_id: ap_id,
1589 nickname: "erroruser@example.com",
1590 inserted_at: NaiveDateTime.utc_now()
1591 }
1592 end
1593
1594 @spec all_superusers() :: [User.t()]
1595 def all_superusers do
1596 User.Query.build(%{super_users: true, local: true, deactivated: false})
1597 |> Repo.all()
1598 end
1599
1600 def showing_reblogs?(%User{} = user, %User{} = target) do
1601 not UserRelationship.reblog_mute_exists?(user, target)
1602 end
1603
1604 @doc """
1605 The function returns a query to get users with no activity for given interval of days.
1606 Inactive users are those who didn't read any notification, or had any activity where
1607 the user is the activity's actor, during `inactivity_threshold` days.
1608 Deactivated users will not appear in this list.
1609
1610 ## Examples
1611
1612 iex> Pleroma.User.list_inactive_users()
1613 %Ecto.Query{}
1614 """
1615 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1616 def list_inactive_users_query(inactivity_threshold \\ 7) do
1617 negative_inactivity_threshold = -inactivity_threshold
1618 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1619 # Subqueries are not supported in `where` clauses, join gets too complicated.
1620 has_read_notifications =
1621 from(n in Pleroma.Notification,
1622 where: n.seen == true,
1623 group_by: n.id,
1624 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1625 select: n.user_id
1626 )
1627 |> Pleroma.Repo.all()
1628
1629 from(u in Pleroma.User,
1630 left_join: a in Pleroma.Activity,
1631 on: u.ap_id == a.actor,
1632 where: not is_nil(u.nickname),
1633 where: u.deactivated != ^true,
1634 where: u.id not in ^has_read_notifications,
1635 group_by: u.id,
1636 having:
1637 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1638 is_nil(max(a.inserted_at))
1639 )
1640 end
1641
1642 @doc """
1643 Enable or disable email notifications for user
1644
1645 ## Examples
1646
1647 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
1648 Pleroma.User{email_notifications: %{"digest" => true}}
1649
1650 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
1651 Pleroma.User{email_notifications: %{"digest" => false}}
1652 """
1653 @spec switch_email_notifications(t(), String.t(), boolean()) ::
1654 {:ok, t()} | {:error, Ecto.Changeset.t()}
1655 def switch_email_notifications(user, type, status) do
1656 User.update_email_notifications(user, %{type => status})
1657 end
1658
1659 @doc """
1660 Set `last_digest_emailed_at` value for the user to current time
1661 """
1662 @spec touch_last_digest_emailed_at(t()) :: t()
1663 def touch_last_digest_emailed_at(user) do
1664 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1665
1666 {:ok, updated_user} =
1667 user
1668 |> change(%{last_digest_emailed_at: now})
1669 |> update_and_set_cache()
1670
1671 updated_user
1672 end
1673
1674 @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1675 def toggle_confirmation(%User{} = user) do
1676 user
1677 |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
1678 |> update_and_set_cache()
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_subscribers(user, subscribers) do
1920 params = %{subscribers: subscribers}
1921
1922 user
1923 |> cast(params, [:subscribers])
1924 |> validate_required([:subscribers])
1925 |> update_and_set_cache()
1926 end
1927
1928 def add_to_subscribers(user, subscribed) do
1929 set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
1930 end
1931
1932 def remove_from_subscribers(user, subscribed) do
1933 set_subscribers(user, List.delete(user.subscribers, subscribed))
1934 end
1935
1936 defp set_domain_blocks(user, domain_blocks) do
1937 params = %{domain_blocks: domain_blocks}
1938
1939 user
1940 |> cast(params, [:domain_blocks])
1941 |> validate_required([:domain_blocks])
1942 |> update_and_set_cache()
1943 end
1944
1945 def block_domain(user, domain_blocked) do
1946 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
1947 end
1948
1949 def unblock_domain(user, domain_blocked) do
1950 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
1951 end
1952
1953 @spec add_to_block(User.t(), User.t()) ::
1954 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
1955 defp add_to_block(%User{} = user, %User{} = blocked) do
1956 UserRelationship.create_block(user, blocked)
1957 end
1958
1959 @spec add_to_block(User.t(), User.t()) ::
1960 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
1961 defp remove_from_block(%User{} = user, %User{} = blocked) do
1962 UserRelationship.delete_block(user, blocked)
1963 end
1964
1965 defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
1966 with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
1967 {:ok, user_notification_mute} <-
1968 (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
1969 {:ok, nil} do
1970 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1971 end
1972 end
1973
1974 defp remove_from_mutes(user, %User{} = muted_user) do
1975 with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
1976 {:ok, user_notification_mute} <-
1977 UserRelationship.delete_notification_mute(user, muted_user) do
1978 {:ok, [user_mute, user_notification_mute]}
1979 end
1980 end
1981
1982 def set_invisible(user, invisible) do
1983 params = %{invisible: invisible}
1984
1985 user
1986 |> cast(params, [:invisible])
1987 |> validate_required([:invisible])
1988 |> update_and_set_cache()
1989 end
1990 end