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