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