Remerge of hashtag following (#341)
[akkoma] / lib / pleroma / user.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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 Ecto.Multi
13 alias Pleroma.Activity
14 alias Pleroma.Config
15 alias Pleroma.Conversation.Participation
16 alias Pleroma.Delivery
17 alias Pleroma.EctoType.ActivityPub.ObjectValidators
18 alias Pleroma.Emoji
19 alias Pleroma.FollowingRelationship
20 alias Pleroma.Formatter
21 alias Pleroma.Hashtag
22 alias Pleroma.User.HashtagFollow
23 alias Pleroma.HTML
24 alias Pleroma.Keys
25 alias Pleroma.MFA
26 alias Pleroma.Notification
27 alias Pleroma.Object
28 alias Pleroma.Registration
29 alias Pleroma.Repo
30 alias Pleroma.User
31 alias Pleroma.UserRelationship
32 alias Pleroma.Web.ActivityPub.ActivityPub
33 alias Pleroma.Web.ActivityPub.Builder
34 alias Pleroma.Web.ActivityPub.Pipeline
35 alias Pleroma.Web.ActivityPub.Utils
36 alias Pleroma.Web.CommonAPI
37 alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
38 alias Pleroma.Web.Endpoint
39 alias Pleroma.Web.OAuth
40 alias Pleroma.Web.RelMe
41 alias Pleroma.Workers.BackgroundWorker
42
43 require Logger
44
45 @type t :: %__MODULE__{}
46 @type account_status ::
47 :active
48 | :deactivated
49 | :password_reset_pending
50 | :confirmation_pending
51 | :approval_pending
52 @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
53
54 # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
55 @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])?)*$/
56
57 @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
58 @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
59
60 # AP ID user relationships (blocks, mutes etc.)
61 # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
62 @user_relationships_config [
63 block: [
64 blocker_blocks: :blocked_users,
65 blockee_blocks: :blocker_users
66 ],
67 mute: [
68 muter_mutes: :muted_users,
69 mutee_mutes: :muter_users
70 ],
71 reblog_mute: [
72 reblog_muter_mutes: :reblog_muted_users,
73 reblog_mutee_mutes: :reblog_muter_users
74 ],
75 notification_mute: [
76 notification_muter_mutes: :notification_muted_users,
77 notification_mutee_mutes: :notification_muter_users
78 ],
79 # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
80 inverse_subscription: [
81 subscribee_subscriptions: :subscriber_users,
82 subscriber_subscriptions: :subscribee_users
83 ]
84 ]
85
86 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
87
88 schema "users" do
89 field(:bio, :string, default: "")
90 field(:raw_bio, :string)
91 field(:email, :string)
92 field(:name, :string)
93 field(:nickname, :string)
94 field(:password_hash, :string)
95 field(:password, :string, virtual: true)
96 field(:password_confirmation, :string, virtual: true)
97 field(:keys, :string)
98 field(:public_key, :string)
99 field(:ap_id, :string)
100 field(:avatar, :map, default: %{})
101 field(:local, :boolean, default: true)
102 field(:follower_address, :string)
103 field(:following_address, :string)
104 field(:featured_address, :string)
105 field(:search_rank, :float, virtual: true)
106 field(:search_type, :integer, virtual: true)
107 field(:tags, {:array, :string}, default: [])
108 field(:last_refreshed_at, :naive_datetime_usec)
109 field(:last_digest_emailed_at, :naive_datetime)
110 field(:banner, :map, default: %{})
111 field(:background, :map, default: %{})
112 field(:note_count, :integer, default: 0)
113 field(:follower_count, :integer, default: 0)
114 field(:following_count, :integer, default: 0)
115 field(:is_locked, :boolean, default: false)
116 field(:is_confirmed, :boolean, default: true)
117 field(:password_reset_pending, :boolean, default: false)
118 field(:is_approved, :boolean, default: true)
119 field(:registration_reason, :string, default: nil)
120 field(:confirmation_token, :string, default: nil)
121 field(:default_scope, :string, default: "public")
122 field(:domain_blocks, {:array, :string}, default: [])
123 field(:is_active, :boolean, default: true)
124 field(:no_rich_text, :boolean, default: false)
125 field(:ap_enabled, :boolean, default: false)
126 field(:is_moderator, :boolean, default: false)
127 field(:is_admin, :boolean, default: false)
128 field(:show_role, :boolean, default: true)
129 field(:mastofe_settings, :map, default: nil)
130 field(:uri, ObjectValidators.Uri, default: nil)
131 field(:hide_followers_count, :boolean, default: false)
132 field(:hide_follows_count, :boolean, default: false)
133 field(:hide_followers, :boolean, default: false)
134 field(:hide_follows, :boolean, default: false)
135 field(:hide_favorites, :boolean, default: true)
136 field(:email_notifications, :map, default: %{"digest" => false})
137 field(:mascot, :map, default: nil)
138 field(:emoji, :map, default: %{})
139 field(:pleroma_settings_store, :map, default: %{})
140 field(:fields, {:array, :map}, default: [])
141 field(:raw_fields, {:array, :map}, default: [])
142 field(:is_discoverable, :boolean, default: false)
143 field(:invisible, :boolean, default: false)
144 field(:allow_following_move, :boolean, default: true)
145 field(:skip_thread_containment, :boolean, default: false)
146 field(:actor_type, :string, default: "Person")
147 field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
148 field(:inbox, :string)
149 field(:shared_inbox, :string)
150 field(:last_active_at, :naive_datetime)
151 field(:disclose_client, :boolean, default: true)
152 field(:pinned_objects, :map, default: %{})
153 field(:is_suggested, :boolean, default: false)
154 field(:last_status_at, :naive_datetime)
155 field(:language, :string)
156 field(:status_ttl_days, :integer, default: nil)
157
158 embeds_one(
159 :notification_settings,
160 Pleroma.User.NotificationSetting,
161 on_replace: :update
162 )
163
164 has_many(:notifications, Notification)
165 has_many(:registrations, Registration)
166 has_many(:deliveries, Delivery)
167
168 has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
169 has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
170
171 has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile)
172
173 many_to_many(:followed_hashtags, Hashtag,
174 on_replace: :delete,
175 on_delete: :delete_all,
176 join_through: HashtagFollow
177 )
178
179 for {relationship_type,
180 [
181 {outgoing_relation, outgoing_relation_target},
182 {incoming_relation, incoming_relation_source}
183 ]} <- @user_relationships_config do
184 # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
185 # :notification_muter_mutes, :subscribee_subscriptions
186 has_many(outgoing_relation, UserRelationship,
187 foreign_key: :source_id,
188 where: [relationship_type: relationship_type]
189 )
190
191 # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
192 # :notification_mutee_mutes, :subscriber_subscriptions
193 has_many(incoming_relation, UserRelationship,
194 foreign_key: :target_id,
195 where: [relationship_type: relationship_type]
196 )
197
198 # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
199 # :notification_muted_users, :subscriber_users
200 has_many(outgoing_relation_target, through: [outgoing_relation, :target])
201
202 # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
203 # :notification_muter_users, :subscribee_users
204 has_many(incoming_relation_source, through: [incoming_relation, :source])
205 end
206
207 embeds_one(
208 :multi_factor_authentication_settings,
209 MFA.Settings,
210 on_replace: :delete
211 )
212
213 timestamps()
214 end
215
216 for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
217 @user_relationships_config do
218 # `def blocked_users_relation/2`, `def muted_users_relation/2`,
219 # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
220 # `def subscriber_users/2`
221 def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
222 target_users_query = assoc(user, unquote(outgoing_relation_target))
223
224 if restrict_deactivated? do
225 target_users_query
226 |> User.Query.build(%{deactivated: false})
227 else
228 target_users_query
229 end
230 end
231
232 # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
233 # `def notification_muted_users/2`, `def subscriber_users/2`
234 def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
235 __MODULE__
236 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
237 user,
238 restrict_deactivated?
239 ])
240 |> Repo.all()
241 end
242
243 # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
244 # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
245 def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
246 __MODULE__
247 |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
248 user,
249 restrict_deactivated?
250 ])
251 |> select([u], u.ap_id)
252 |> Repo.all()
253 end
254 end
255
256 def cached_blocked_users_ap_ids(user) do
257 @cachex.fetch!(:user_cache, "blocked_users_ap_ids:#{user.ap_id}", fn _ ->
258 blocked_users_ap_ids(user)
259 end)
260 end
261
262 def cached_muted_users_ap_ids(user) do
263 @cachex.fetch!(:user_cache, "muted_users_ap_ids:#{user.ap_id}", fn _ ->
264 muted_users_ap_ids(user)
265 end)
266 end
267
268 defdelegate following_count(user), to: FollowingRelationship
269 defdelegate following(user), to: FollowingRelationship
270 defdelegate following?(follower, followed), to: FollowingRelationship
271 defdelegate following_ap_ids(user), to: FollowingRelationship
272 defdelegate get_follow_requests(user), to: FollowingRelationship
273 defdelegate search(query, opts \\ []), to: User.Search
274
275 @doc """
276 Dumps Flake Id to SQL-compatible format (16-byte UUID).
277 E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
278 """
279 def binary_id(source_id) when is_binary(source_id) do
280 with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
281 dumped_id
282 else
283 _ -> source_id
284 end
285 end
286
287 def binary_id(source_ids) when is_list(source_ids) do
288 Enum.map(source_ids, &binary_id/1)
289 end
290
291 def binary_id(%User{} = user), do: binary_id(user.id)
292
293 @doc "Returns status account"
294 @spec account_status(User.t()) :: account_status()
295 def account_status(%User{is_active: false}), do: :deactivated
296 def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
297 def account_status(%User{local: true, is_approved: false}), do: :approval_pending
298 def account_status(%User{local: true, is_confirmed: false}), do: :confirmation_pending
299 def account_status(%User{}), do: :active
300
301 @spec visible_for(User.t(), User.t() | nil) ::
302 :visible
303 | :invisible
304 | :restricted_unauthenticated
305 | :deactivated
306 | :confirmation_pending
307 def visible_for(user, for_user \\ nil)
308
309 def visible_for(%User{invisible: true}, _), do: :invisible
310
311 def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
312
313 def visible_for(%User{} = user, nil) do
314 if restrict_unauthenticated?(user) do
315 :restrict_unauthenticated
316 else
317 visible_account_status(user)
318 end
319 end
320
321 def visible_for(%User{} = user, for_user) do
322 if superuser?(for_user) do
323 :visible
324 else
325 visible_account_status(user)
326 end
327 end
328
329 def visible_for(_, _), do: :invisible
330
331 defp restrict_unauthenticated?(%User{local: true}) do
332 Config.restrict_unauthenticated_access?(:profiles, :local)
333 end
334
335 defp restrict_unauthenticated?(%User{local: _}) do
336 Config.restrict_unauthenticated_access?(:profiles, :remote)
337 end
338
339 defp visible_account_status(user) do
340 status = account_status(user)
341
342 if status in [:active, :password_reset_pending] do
343 :visible
344 else
345 status
346 end
347 end
348
349 @spec superuser?(User.t()) :: boolean()
350 def superuser?(%User{local: true, is_admin: true}), do: true
351 def superuser?(%User{local: true, is_moderator: true}), do: true
352 def superuser?(_), do: false
353
354 @spec invisible?(User.t()) :: boolean()
355 def invisible?(%User{invisible: true}), do: true
356 def invisible?(_), do: false
357
358 def avatar_url(user, options \\ []) do
359 case user.avatar do
360 %{"url" => [%{"href" => href} | _]} ->
361 href
362
363 _ ->
364 unless options[:no_default] do
365 Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png")
366 end
367 end
368 end
369
370 def banner_url(user, options \\ []) do
371 case user.banner do
372 %{"url" => [%{"href" => href} | _]} -> href
373 _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png"
374 end
375 end
376
377 # Should probably be renamed or removed
378 @spec ap_id(User.t()) :: String.t()
379 def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
380
381 @spec ap_followers(User.t()) :: String.t()
382 def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
383 def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
384
385 @spec ap_following(User.t()) :: String.t()
386 def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
387 def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
388
389 @spec ap_featured_collection(User.t()) :: String.t()
390 def ap_featured_collection(%User{featured_address: fa}) when is_binary(fa), do: fa
391
392 def ap_featured_collection(%User{} = user), do: "#{ap_id(user)}/collections/featured"
393
394 defp truncate_fields_param(params) do
395 if Map.has_key?(params, :fields) do
396 Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
397 else
398 params
399 end
400 end
401
402 defp truncate_if_exists(params, key, max_length) do
403 if Map.has_key?(params, key) and is_binary(params[key]) do
404 {value, _chopped} = String.split_at(params[key], max_length)
405 Map.put(params, key, value)
406 else
407 params
408 end
409 end
410
411 defp fix_follower_address(%{follower_address: _, following_address: _} = params), do: params
412
413 defp fix_follower_address(%{nickname: nickname} = params),
414 do: Map.put(params, :follower_address, ap_followers(%User{nickname: nickname}))
415
416 defp fix_follower_address(params), do: params
417
418 def remote_user_changeset(struct \\ %User{local: false}, params) do
419 bio_limit = Config.get([:instance, :user_bio_length], 5000)
420 name_limit = Config.get([:instance, :user_name_length], 100)
421
422 name =
423 case params[:name] do
424 name when is_binary(name) and byte_size(name) > 0 -> name
425 _ -> params[:nickname]
426 end
427
428 params =
429 params
430 |> Map.put(:name, name)
431 |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now())
432 |> truncate_if_exists(:name, name_limit)
433 |> truncate_if_exists(:bio, bio_limit)
434 |> truncate_fields_param()
435 |> fix_follower_address()
436
437 struct
438 |> cast(
439 params,
440 [
441 :bio,
442 :emoji,
443 :ap_id,
444 :inbox,
445 :shared_inbox,
446 :nickname,
447 :public_key,
448 :avatar,
449 :ap_enabled,
450 :banner,
451 :is_locked,
452 :last_refreshed_at,
453 :uri,
454 :follower_address,
455 :following_address,
456 :featured_address,
457 :hide_followers,
458 :hide_follows,
459 :hide_followers_count,
460 :hide_follows_count,
461 :follower_count,
462 :fields,
463 :following_count,
464 :is_discoverable,
465 :invisible,
466 :actor_type,
467 :also_known_as,
468 :pinned_objects
469 ]
470 )
471 |> cast(params, [:name], empty_values: [])
472 |> validate_required([:ap_id])
473 |> validate_required([:name], trim: false)
474 |> unique_constraint(:nickname)
475 |> validate_format(:nickname, @email_regex)
476 |> validate_length(:bio, max: bio_limit)
477 |> validate_length(:name, max: name_limit)
478 |> validate_fields(true)
479 |> validate_non_local()
480 end
481
482 defp validate_non_local(cng) do
483 local? = get_field(cng, :local)
484
485 if local? do
486 cng
487 |> add_error(:local, "User is local, can't update with this changeset.")
488 else
489 cng
490 end
491 end
492
493 def update_changeset(struct, params \\ %{}) do
494 bio_limit = Config.get([:instance, :user_bio_length], 5000)
495 name_limit = Config.get([:instance, :user_name_length], 100)
496
497 struct
498 |> cast(
499 params,
500 [
501 :bio,
502 :raw_bio,
503 :name,
504 :emoji,
505 :avatar,
506 :public_key,
507 :inbox,
508 :shared_inbox,
509 :is_locked,
510 :no_rich_text,
511 :default_scope,
512 :banner,
513 :hide_follows,
514 :hide_followers,
515 :hide_followers_count,
516 :hide_follows_count,
517 :hide_favorites,
518 :allow_following_move,
519 :also_known_as,
520 :background,
521 :show_role,
522 :skip_thread_containment,
523 :fields,
524 :raw_fields,
525 :pleroma_settings_store,
526 :is_discoverable,
527 :actor_type,
528 :disclose_client,
529 :status_ttl_days
530 ]
531 )
532 |> unique_constraint(:nickname)
533 |> validate_format(:nickname, local_nickname_regex())
534 |> validate_length(:bio, max: bio_limit)
535 |> validate_length(:name, min: 1, max: name_limit)
536 |> validate_inclusion(:actor_type, ["Person", "Service"])
537 |> validate_number(:status_ttl_days, greater_than: 0)
538 |> put_fields()
539 |> put_emoji()
540 |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
541 |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
542 |> put_change_if_present(:banner, &put_upload(&1, :banner))
543 |> put_change_if_present(:background, &put_upload(&1, :background))
544 |> put_change_if_present(
545 :pleroma_settings_store,
546 &{:ok, Map.merge(struct.pleroma_settings_store, &1)}
547 )
548 |> validate_fields(false)
549 end
550
551 defp put_fields(changeset) do
552 if raw_fields = get_change(changeset, :raw_fields) do
553 raw_fields =
554 raw_fields
555 |> Enum.filter(fn %{"name" => n} -> n != "" end)
556
557 fields =
558 raw_fields
559 |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
560
561 changeset
562 |> put_change(:raw_fields, raw_fields)
563 |> put_change(:fields, fields)
564 else
565 changeset
566 end
567 end
568
569 defp parse_fields(value) do
570 value
571 |> Formatter.linkify(mentions_format: :full)
572 |> elem(0)
573 end
574
575 defp put_emoji(changeset) do
576 emojified_fields = [:bio, :name, :raw_fields]
577
578 if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
579 bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
580 name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
581
582 emoji = Map.merge(bio, name)
583
584 emoji =
585 changeset
586 |> get_field(:raw_fields)
587 |> Enum.reduce(emoji, fn x, acc ->
588 Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
589 end)
590
591 put_change(changeset, :emoji, emoji)
592 else
593 changeset
594 end
595 end
596
597 defp put_change_if_present(changeset, map_field, value_function) do
598 with {:ok, value} <- fetch_change(changeset, map_field),
599 {:ok, new_value} <- value_function.(value) do
600 put_change(changeset, map_field, new_value)
601 else
602 _ -> changeset
603 end
604 end
605
606 defp put_upload(value, type) do
607 with %Plug.Upload{} <- value,
608 {:ok, object} <- ActivityPub.upload(value, type: type) do
609 {:ok, object.data}
610 end
611 end
612
613 def update_as_admin_changeset(struct, params) do
614 struct
615 |> update_changeset(params)
616 |> cast(params, [:email])
617 |> delete_change(:also_known_as)
618 |> unique_constraint(:email)
619 |> validate_format(:email, @email_regex)
620 |> validate_inclusion(:actor_type, ["Person", "Service"])
621 end
622
623 @spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
624 def update_as_admin(user, params) do
625 params = Map.put(params, "password_confirmation", params["password"])
626 changeset = update_as_admin_changeset(user, params)
627
628 if params["password"] do
629 reset_password(user, changeset, params)
630 else
631 User.update_and_set_cache(changeset)
632 end
633 end
634
635 def password_update_changeset(struct, params) do
636 struct
637 |> cast(params, [:password, :password_confirmation])
638 |> validate_required([:password, :password_confirmation])
639 |> validate_confirmation(:password)
640 |> put_password_hash()
641 |> put_change(:password_reset_pending, false)
642 end
643
644 @spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
645 def reset_password(%User{} = user, params) do
646 reset_password(user, user, params)
647 end
648
649 def reset_password(%User{id: user_id} = user, struct, params) do
650 multi =
651 Multi.new()
652 |> Multi.update(:user, password_update_changeset(struct, params))
653 |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
654 |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
655
656 case Repo.transaction(multi) do
657 {:ok, %{user: user} = _} -> set_cache(user)
658 {:error, _, changeset, _} -> {:error, changeset}
659 end
660 end
661
662 def update_password_reset_pending(user, value) do
663 user
664 |> change()
665 |> put_change(:password_reset_pending, value)
666 |> update_and_set_cache()
667 end
668
669 def force_password_reset_async(user) do
670 BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
671 end
672
673 @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
674 def force_password_reset(user), do: update_password_reset_pending(user, true)
675
676 # Used to auto-register LDAP accounts which won't have a password hash stored locally
677 def register_changeset_ldap(struct, params = %{password: password})
678 when is_nil(password) do
679 params =
680 if Map.has_key?(params, :email) do
681 Map.put_new(params, :email, params[:email])
682 else
683 params
684 end
685
686 struct
687 |> cast(params, [
688 :name,
689 :nickname,
690 :email
691 ])
692 |> validate_required([:name, :nickname])
693 |> unique_constraint(:nickname)
694 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
695 |> validate_format(:nickname, local_nickname_regex())
696 |> put_ap_id()
697 |> unique_constraint(:ap_id)
698 |> put_following_and_follower_and_featured_address()
699 |> put_private_key()
700 end
701
702 def register_changeset(struct, params \\ %{}, opts \\ []) do
703 bio_limit = Config.get([:instance, :user_bio_length], 5000)
704 name_limit = Config.get([:instance, :user_name_length], 100)
705 reason_limit = Config.get([:instance, :registration_reason_length], 500)
706
707 confirmed? =
708 if is_nil(opts[:confirmed]) do
709 !Config.get([:instance, :account_activation_required])
710 else
711 opts[:confirmed]
712 end
713
714 approved? =
715 if is_nil(opts[:approved]) do
716 !Config.get([:instance, :account_approval_required])
717 else
718 opts[:approved]
719 end
720
721 struct
722 |> confirmation_changeset(set_confirmation: confirmed?)
723 |> approval_changeset(set_approval: approved?)
724 |> cast(params, [
725 :bio,
726 :raw_bio,
727 :email,
728 :name,
729 :nickname,
730 :password,
731 :password_confirmation,
732 :emoji,
733 :registration_reason,
734 :language
735 ])
736 |> validate_required([:name, :nickname, :password, :password_confirmation])
737 |> validate_confirmation(:password)
738 |> unique_constraint(:email)
739 |> validate_format(:email, @email_regex)
740 |> validate_change(:email, fn :email, email ->
741 valid? =
742 Config.get([User, :email_blacklist])
743 |> Enum.all?(fn blacklisted_domain ->
744 !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
745 end)
746
747 if valid?, do: [], else: [email: "Invalid email"]
748 end)
749 |> unique_constraint(:nickname)
750 |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
751 |> validate_format(:nickname, local_nickname_regex())
752 |> validate_length(:bio, max: bio_limit)
753 |> validate_length(:name, min: 1, max: name_limit)
754 |> validate_length(:registration_reason, max: reason_limit)
755 |> maybe_validate_required_email(opts[:external])
756 |> put_password_hash
757 |> put_ap_id()
758 |> unique_constraint(:ap_id)
759 |> put_following_and_follower_and_featured_address()
760 |> put_private_key()
761 end
762
763 def maybe_validate_required_email(changeset, true), do: changeset
764
765 def maybe_validate_required_email(changeset, _) do
766 if Config.get([:instance, :account_activation_required]) do
767 validate_required(changeset, [:email])
768 else
769 changeset
770 end
771 end
772
773 def put_ap_id(changeset) do
774 ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
775 put_change(changeset, :ap_id, ap_id)
776 end
777
778 def put_following_and_follower_and_featured_address(changeset) do
779 user = %User{nickname: get_field(changeset, :nickname)}
780 followers = ap_followers(user)
781 following = ap_following(user)
782 featured = ap_featured_collection(user)
783
784 changeset
785 |> put_change(:follower_address, followers)
786 |> put_change(:following_address, following)
787 |> put_change(:featured_address, featured)
788 end
789
790 defp put_private_key(changeset) do
791 {:ok, pem} = Keys.generate_rsa_pem()
792 put_change(changeset, :keys, pem)
793 end
794
795 defp autofollow_users(user) do
796 candidates = Config.get([:instance, :autofollowed_nicknames])
797
798 autofollowed_users =
799 User.Query.build(%{nickname: candidates, local: true, is_active: true})
800 |> Repo.all()
801
802 follow_all(user, autofollowed_users)
803 end
804
805 defp autofollowing_users(user) do
806 candidates = Config.get([:instance, :autofollowing_nicknames])
807
808 User.Query.build(%{nickname: candidates, local: true, deactivated: false})
809 |> Repo.all()
810 |> Enum.each(&follow(&1, user, :follow_accept))
811
812 {:ok, :success}
813 end
814
815 @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
816 def register(%Ecto.Changeset{} = changeset) do
817 with {:ok, user} <- Repo.insert(changeset) do
818 post_register_action(user)
819 end
820 end
821
822 def post_register_action(%User{is_confirmed: false} = user) do
823 with {:ok, _} <- maybe_send_confirmation_email(user) do
824 {:ok, user}
825 end
826 end
827
828 def post_register_action(%User{is_approved: false} = user) do
829 with {:ok, _} <- send_user_approval_email(user),
830 {:ok, _} <- send_admin_approval_emails(user) do
831 {:ok, user}
832 end
833 end
834
835 def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
836 with {:ok, user} <- autofollow_users(user),
837 {:ok, _} <- autofollowing_users(user),
838 {:ok, user} <- set_cache(user),
839 {:ok, _} <- maybe_send_registration_email(user),
840 {:ok, _} <- maybe_send_welcome_email(user),
841 {:ok, _} <- maybe_send_welcome_message(user) do
842 {:ok, user}
843 end
844 end
845
846 defp send_user_approval_email(user) do
847 user
848 |> Pleroma.Emails.UserEmail.approval_pending_email()
849 |> Pleroma.Emails.Mailer.deliver_async()
850
851 {:ok, :enqueued}
852 end
853
854 defp send_admin_approval_emails(user) do
855 all_superusers()
856 |> Enum.filter(fn user -> not is_nil(user.email) end)
857 |> Enum.each(fn superuser ->
858 superuser
859 |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user)
860 |> Pleroma.Emails.Mailer.deliver_async()
861 end)
862
863 {:ok, :enqueued}
864 end
865
866 defp maybe_send_welcome_message(user) do
867 if User.WelcomeMessage.enabled?() do
868 User.WelcomeMessage.post_message(user)
869 {:ok, :enqueued}
870 else
871 {:ok, :noop}
872 end
873 end
874
875 defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do
876 if User.WelcomeEmail.enabled?() do
877 User.WelcomeEmail.send_email(user)
878 {:ok, :enqueued}
879 else
880 {:ok, :noop}
881 end
882 end
883
884 defp maybe_send_welcome_email(_), do: {:ok, :noop}
885
886 @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
887 def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user)
888 when is_binary(email) do
889 if Config.get([:instance, :account_activation_required]) do
890 send_confirmation_email(user)
891 {:ok, :enqueued}
892 else
893 {:ok, :noop}
894 end
895 end
896
897 def maybe_send_confirmation_email(_), do: {:ok, :noop}
898
899 @spec send_confirmation_email(Uset.t()) :: User.t()
900 def send_confirmation_email(%User{} = user) do
901 user
902 |> Pleroma.Emails.UserEmail.account_confirmation_email()
903 |> Pleroma.Emails.Mailer.deliver_async()
904
905 user
906 end
907
908 @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop}
909 defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do
910 with false <- User.WelcomeEmail.enabled?(),
911 false <- Config.get([:instance, :account_activation_required], false),
912 false <- Config.get([:instance, :account_approval_required], false) do
913 user
914 |> Pleroma.Emails.UserEmail.successful_registration_email()
915 |> Pleroma.Emails.Mailer.deliver_async()
916
917 {:ok, :enqueued}
918 else
919 _ ->
920 {:ok, :noop}
921 end
922 end
923
924 defp maybe_send_registration_email(_), do: {:ok, :noop}
925
926 def needs_update?(%User{local: true}), do: false
927
928 def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
929
930 def needs_update?(%User{local: false} = user) do
931 NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
932 end
933
934 def needs_update?(_), do: true
935
936 @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
937
938 # "Locked" (self-locked) users demand explicit authorization of follow requests
939 def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
940 follow(follower, followed, :follow_pending)
941 end
942
943 def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
944 follow(follower, followed)
945 end
946
947 def maybe_direct_follow(%User{} = follower, %User{} = followed) do
948 if not ap_enabled?(followed) do
949 follow(follower, followed)
950 else
951 {:ok, follower, followed}
952 end
953 end
954
955 @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
956 @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
957 def follow_all(follower, followeds) do
958 followeds
959 |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
960 |> Enum.each(&follow(follower, &1, :follow_accept))
961
962 set_cache(follower)
963 end
964
965 def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
966 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
967
968 cond do
969 not followed.is_active ->
970 {:error, "Could not follow user: #{followed.nickname} is deactivated."}
971
972 deny_follow_blocked and blocks?(followed, follower) ->
973 {:error, "Could not follow user: #{followed.nickname} blocked you."}
974
975 true ->
976 FollowingRelationship.follow(follower, followed, state)
977 end
978 end
979
980 def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
981 {:error, "Not subscribed!"}
982 end
983
984 @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
985 def unfollow(%User{} = follower, %User{} = followed) do
986 case do_unfollow(follower, followed) do
987 {:ok, follower, followed} ->
988 {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
989
990 error ->
991 error
992 end
993 end
994
995 @spec do_unfollow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()}
996 defp do_unfollow(%User{} = follower, %User{} = followed) do
997 case get_follow_state(follower, followed) do
998 state when state in [:follow_pending, :follow_accept] ->
999 FollowingRelationship.unfollow(follower, followed)
1000
1001 nil ->
1002 {:error, "Not subscribed!"}
1003 end
1004 end
1005
1006 @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
1007 def get_follow_state(%User{} = follower, %User{} = following) do
1008 following_relationship = FollowingRelationship.get(follower, following)
1009 get_follow_state(follower, following, following_relationship)
1010 end
1011
1012 def get_follow_state(
1013 %User{} = follower,
1014 %User{} = following,
1015 following_relationship
1016 ) do
1017 case {following_relationship, following.local} do
1018 {nil, false} ->
1019 case Utils.fetch_latest_follow(follower, following) do
1020 %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
1021 FollowingRelationship.state_to_enum(state)
1022
1023 _ ->
1024 nil
1025 end
1026
1027 {%{state: state}, _} ->
1028 state
1029
1030 {nil, _} ->
1031 nil
1032 end
1033 end
1034
1035 def locked?(%User{} = user) do
1036 user.is_locked || false
1037 end
1038
1039 def get_by_id(id) do
1040 Repo.get_by(User, id: id)
1041 end
1042
1043 def get_by_ap_id(ap_id) do
1044 Repo.get_by(User, ap_id: ap_id)
1045 end
1046
1047 def get_all_by_ap_id(ap_ids) do
1048 from(u in __MODULE__,
1049 where: u.ap_id in ^ap_ids
1050 )
1051 |> Repo.all()
1052 end
1053
1054 def get_all_by_ids(ids) do
1055 from(u in __MODULE__, where: u.id in ^ids)
1056 |> Repo.all()
1057 end
1058
1059 # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
1060 # of the ap_id and the domain and tries to get that user
1061 def get_by_guessed_nickname(ap_id) do
1062 domain = URI.parse(ap_id).host
1063 name = List.last(String.split(ap_id, "/"))
1064 nickname = "#{name}@#{domain}"
1065
1066 get_cached_by_nickname(nickname)
1067 end
1068
1069 def set_cache({:ok, user}), do: set_cache(user)
1070 def set_cache({:error, err}), do: {:error, err}
1071
1072 def set_cache(%User{} = user) do
1073 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1074 @cachex.put(:user_cache, "nickname:#{user.nickname}", user)
1075 @cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
1076 {:ok, user}
1077 end
1078
1079 def update_and_set_cache(struct, params) do
1080 struct
1081 |> update_changeset(params)
1082 |> update_and_set_cache()
1083 end
1084
1085 def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
1086 was_superuser_before_update = User.superuser?(user)
1087
1088 with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
1089 set_cache(user)
1090 end
1091 |> maybe_remove_report_notifications(was_superuser_before_update)
1092 end
1093
1094 defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
1095 if not User.superuser?(user),
1096 do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
1097
1098 result
1099 end
1100
1101 defp maybe_remove_report_notifications(result, _) do
1102 result
1103 end
1104
1105 def get_user_friends_ap_ids(user) do
1106 from(u in User.get_friends_query(user), select: u.ap_id)
1107 |> Repo.all()
1108 end
1109
1110 @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
1111 def get_cached_user_friends_ap_ids(user) do
1112 @cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
1113 get_user_friends_ap_ids(user)
1114 end)
1115 end
1116
1117 def invalidate_cache(user) do
1118 @cachex.del(:user_cache, "ap_id:#{user.ap_id}")
1119 @cachex.del(:user_cache, "nickname:#{user.nickname}")
1120 @cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
1121 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
1122 @cachex.del(:user_cache, "muted_users_ap_ids:#{user.ap_id}")
1123 end
1124
1125 @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
1126 def get_cached_by_ap_id(ap_id) do
1127 key = "ap_id:#{ap_id}"
1128
1129 with {:ok, nil} <- @cachex.get(:user_cache, key),
1130 user when not is_nil(user) <- get_by_ap_id(ap_id),
1131 {:ok, true} <- @cachex.put(:user_cache, key, user) do
1132 user
1133 else
1134 {:ok, user} -> user
1135 nil -> nil
1136 end
1137 end
1138
1139 def get_cached_by_id(id) do
1140 key = "id:#{id}"
1141
1142 ap_id =
1143 @cachex.fetch!(:user_cache, key, fn _ ->
1144 user = get_by_id(id)
1145
1146 if user do
1147 @cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
1148 {:commit, user.ap_id}
1149 else
1150 {:ignore, ""}
1151 end
1152 end)
1153
1154 get_cached_by_ap_id(ap_id)
1155 end
1156
1157 def get_cached_by_nickname(nickname) do
1158 key = "nickname:#{nickname}"
1159
1160 @cachex.fetch!(:user_cache, key, fn _ ->
1161 case get_or_fetch_by_nickname(nickname) do
1162 {:ok, user} -> {:commit, user}
1163 {:error, _error} -> {:ignore, nil}
1164 end
1165 end)
1166 end
1167
1168 def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
1169 restrict_to_local = Config.get([:instance, :limit_to_local_content])
1170
1171 cond do
1172 is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
1173 get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
1174
1175 restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
1176 get_cached_by_nickname(nickname_or_id)
1177
1178 restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
1179 get_cached_by_nickname(nickname_or_id)
1180
1181 true ->
1182 nil
1183 end
1184 end
1185
1186 @spec get_by_nickname(String.t()) :: User.t() | nil
1187 def get_by_nickname(nickname) do
1188 Repo.get_by(User, nickname: nickname) ||
1189 if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
1190 Repo.get_by(User, nickname: local_nickname(nickname))
1191 end
1192 end
1193
1194 def get_by_email(email), do: Repo.get_by(User, email: email)
1195
1196 def get_by_nickname_or_email(nickname_or_email) do
1197 get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
1198 end
1199
1200 def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
1201
1202 def get_or_fetch_by_nickname(nickname) do
1203 with %User{} = user <- get_by_nickname(nickname) do
1204 {:ok, user}
1205 else
1206 _e ->
1207 with [_nick, _domain] <- String.split(nickname, "@"),
1208 {:ok, user} <- fetch_by_nickname(nickname) do
1209 {:ok, user}
1210 else
1211 _e -> {:error, "not found " <> nickname}
1212 end
1213 end
1214 end
1215
1216 @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1217 def get_followers_query(%User{} = user, nil) do
1218 User.Query.build(%{followers: user, is_active: true})
1219 end
1220
1221 def get_followers_query(%User{} = user, page) do
1222 user
1223 |> get_followers_query(nil)
1224 |> User.Query.paginate(page, 20)
1225 end
1226
1227 @spec get_followers_query(User.t()) :: Ecto.Query.t()
1228 def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
1229
1230 @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1231 def get_followers(%User{} = user, page \\ nil) do
1232 user
1233 |> get_followers_query(page)
1234 |> Repo.all()
1235 end
1236
1237 @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
1238 def get_external_followers(%User{} = user, page \\ nil) do
1239 user
1240 |> get_followers_query(page)
1241 |> User.Query.build(%{external: true})
1242 |> Repo.all()
1243 end
1244
1245 def get_followers_ids(%User{} = user, page \\ nil) do
1246 user
1247 |> get_followers_query(page)
1248 |> select([u], u.id)
1249 |> Repo.all()
1250 end
1251
1252 @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
1253 def get_friends_query(%User{} = user, nil) do
1254 User.Query.build(%{friends: user, deactivated: false})
1255 end
1256
1257 def get_friends_query(%User{} = user, page) do
1258 user
1259 |> get_friends_query(nil)
1260 |> User.Query.paginate(page, 20)
1261 end
1262
1263 @spec get_friends_query(User.t()) :: Ecto.Query.t()
1264 def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
1265
1266 def get_friends(%User{} = user, page \\ nil) do
1267 user
1268 |> get_friends_query(page)
1269 |> Repo.all()
1270 end
1271
1272 def get_friends_ap_ids(%User{} = user) do
1273 user
1274 |> get_friends_query(nil)
1275 |> select([u], u.ap_id)
1276 |> Repo.all()
1277 end
1278
1279 def get_friends_ids(%User{} = user, page \\ nil) do
1280 user
1281 |> get_friends_query(page)
1282 |> select([u], u.id)
1283 |> Repo.all()
1284 end
1285
1286 def increase_note_count(%User{} = user) do
1287 User
1288 |> where(id: ^user.id)
1289 |> update([u], inc: [note_count: 1])
1290 |> select([u], u)
1291 |> Repo.update_all([])
1292 |> case do
1293 {1, [user]} -> set_cache(user)
1294 _ -> {:error, user}
1295 end
1296 end
1297
1298 def decrease_note_count(%User{} = user) do
1299 User
1300 |> where(id: ^user.id)
1301 |> update([u],
1302 set: [
1303 note_count: fragment("greatest(0, note_count - 1)")
1304 ]
1305 )
1306 |> select([u], u)
1307 |> Repo.update_all([])
1308 |> case do
1309 {1, [user]} -> set_cache(user)
1310 _ -> {:error, user}
1311 end
1312 end
1313
1314 def update_note_count(%User{} = user, note_count \\ nil) do
1315 note_count =
1316 note_count ||
1317 from(
1318 a in Object,
1319 where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
1320 select: count(a.id)
1321 )
1322 |> Repo.one()
1323
1324 user
1325 |> cast(%{note_count: note_count}, [:note_count])
1326 |> update_and_set_cache()
1327 end
1328
1329 @spec maybe_fetch_follow_information(User.t()) :: User.t()
1330 def maybe_fetch_follow_information(user) do
1331 with {:ok, user} <- fetch_follow_information(user) do
1332 user
1333 else
1334 e ->
1335 Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
1336
1337 user
1338 end
1339 end
1340
1341 def fetch_follow_information(user) do
1342 with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
1343 user
1344 |> follow_information_changeset(info)
1345 |> update_and_set_cache()
1346 end
1347 end
1348
1349 defp follow_information_changeset(user, params) do
1350 user
1351 |> cast(params, [
1352 :hide_followers,
1353 :hide_follows,
1354 :follower_count,
1355 :following_count,
1356 :hide_followers_count,
1357 :hide_follows_count
1358 ])
1359 end
1360
1361 @spec update_follower_count(User.t()) :: {:ok, User.t()}
1362 def update_follower_count(%User{} = user) do
1363 if user.local or !Config.get([:instance, :external_user_synchronization]) do
1364 follower_count = FollowingRelationship.follower_count(user)
1365
1366 user
1367 |> follow_information_changeset(%{follower_count: follower_count})
1368 |> update_and_set_cache
1369 else
1370 {:ok, maybe_fetch_follow_information(user)}
1371 end
1372 end
1373
1374 @spec update_following_count(User.t()) :: {:ok, User.t()}
1375 def update_following_count(%User{local: false} = user) do
1376 if Config.get([:instance, :external_user_synchronization]) do
1377 {:ok, maybe_fetch_follow_information(user)}
1378 else
1379 {:ok, user}
1380 end
1381 end
1382
1383 def update_following_count(%User{local: true} = user) do
1384 following_count = FollowingRelationship.following_count(user)
1385
1386 user
1387 |> follow_information_changeset(%{following_count: following_count})
1388 |> update_and_set_cache()
1389 end
1390
1391 @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
1392 def get_users_from_set(ap_ids, opts \\ []) do
1393 local_only = Keyword.get(opts, :local_only, true)
1394 criteria = %{ap_id: ap_ids, is_active: true}
1395 criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
1396
1397 User.Query.build(criteria)
1398 |> Repo.all()
1399 end
1400
1401 @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
1402 def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
1403 to = [actor | to]
1404
1405 query = User.Query.build(%{recipients_from_activity: to, local: true, is_active: true})
1406
1407 query
1408 |> Repo.all()
1409 end
1410
1411 @spec mute(User.t(), User.t(), map()) ::
1412 {:ok, list(UserRelationship.t())} | {:error, String.t()}
1413 def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
1414 notifications? = Map.get(params, :notifications, true)
1415 expires_in = Map.get(params, :expires_in, 0)
1416
1417 with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
1418 {:ok, user_notification_mute} <-
1419 (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
1420 {:ok, nil} do
1421 if expires_in > 0 do
1422 Pleroma.Workers.MuteExpireWorker.enqueue(
1423 "unmute_user",
1424 %{"muter_id" => muter.id, "mutee_id" => mutee.id},
1425 schedule_in: expires_in
1426 )
1427 end
1428
1429 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1430
1431 {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
1432 end
1433 end
1434
1435 def unmute(%User{} = muter, %User{} = mutee) do
1436 with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
1437 {:ok, user_notification_mute} <-
1438 UserRelationship.delete_notification_mute(muter, mutee) do
1439 @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
1440 {:ok, [user_mute, user_notification_mute]}
1441 end
1442 end
1443
1444 def unmute(muter_id, mutee_id) do
1445 with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
1446 {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
1447 unmute(muter, mutee)
1448 else
1449 {who, result} = error ->
1450 Logger.warn(
1451 "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
1452 )
1453
1454 {:error, error}
1455 end
1456 end
1457
1458 def subscribe(%User{} = subscriber, %User{} = target) do
1459 deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
1460
1461 if blocks?(target, subscriber) and deny_follow_blocked do
1462 {:error, "Could not subscribe: #{target.nickname} is blocking you"}
1463 else
1464 # Note: the relationship is inverse: subscriber acts as relationship target
1465 UserRelationship.create_inverse_subscription(target, subscriber)
1466 end
1467 end
1468
1469 def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
1470 with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
1471 subscribe(subscriber, subscribee)
1472 end
1473 end
1474
1475 def unsubscribe(%User{} = unsubscriber, %User{} = target) do
1476 # Note: the relationship is inverse: subscriber acts as relationship target
1477 UserRelationship.delete_inverse_subscription(target, unsubscriber)
1478 end
1479
1480 def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
1481 with %User{} = user <- get_cached_by_ap_id(ap_id) do
1482 unsubscribe(unsubscriber, user)
1483 end
1484 end
1485
1486 def block(%User{} = blocker, %User{} = blocked) do
1487 # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
1488 blocker =
1489 if following?(blocker, blocked) do
1490 {:ok, blocker, _} = unfollow(blocker, blocked)
1491 blocker
1492 else
1493 blocker
1494 end
1495
1496 # clear any requested follows from both sides as well
1497 blocked =
1498 case CommonAPI.reject_follow_request(blocked, blocker) do
1499 {:ok, %User{} = updated_blocked} -> updated_blocked
1500 nil -> blocked
1501 end
1502
1503 blocker =
1504 case CommonAPI.reject_follow_request(blocker, blocked) do
1505 {:ok, %User{} = updated_blocker} -> updated_blocker
1506 nil -> blocker
1507 end
1508
1509 unsubscribe(blocked, blocker)
1510
1511 unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
1512 if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
1513
1514 {:ok, blocker} = update_follower_count(blocker)
1515 {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
1516 add_to_block(blocker, blocked)
1517 end
1518
1519 # helper to handle the block given only an actor's AP id
1520 def block(%User{} = blocker, %{ap_id: ap_id}) do
1521 block(blocker, get_cached_by_ap_id(ap_id))
1522 end
1523
1524 def unblock(%User{} = blocker, %User{} = blocked) do
1525 remove_from_block(blocker, blocked)
1526 end
1527
1528 # helper to handle the block given only an actor's AP id
1529 def unblock(%User{} = blocker, %{ap_id: ap_id}) do
1530 unblock(blocker, get_cached_by_ap_id(ap_id))
1531 end
1532
1533 def mutes?(nil, _), do: false
1534 def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
1535
1536 def mutes_user?(%User{} = user, %User{} = target) do
1537 UserRelationship.mute_exists?(user, target)
1538 end
1539
1540 @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
1541 def muted_notifications?(nil, _), do: false
1542
1543 def muted_notifications?(%User{} = user, %User{} = target),
1544 do: UserRelationship.notification_mute_exists?(user, target)
1545
1546 def blocks?(nil, _), do: false
1547
1548 def blocks?(%User{} = user, %User{} = target) do
1549 blocks_user?(user, target) ||
1550 (blocks_domain?(user, target) and not User.following?(user, target))
1551 end
1552
1553 def blocks_user?(%User{} = user, %User{} = target) do
1554 UserRelationship.block_exists?(user, target)
1555 end
1556
1557 def blocks_user?(_, _), do: false
1558
1559 def blocks_domain?(%User{} = user, %User{} = target) do
1560 domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
1561 %{host: host} = URI.parse(target.ap_id)
1562 Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
1563 end
1564
1565 def blocks_domain?(_, _), do: false
1566
1567 def subscribed_to?(%User{} = user, %User{} = target) do
1568 # Note: the relationship is inverse: subscriber acts as relationship target
1569 UserRelationship.inverse_subscription_exists?(target, user)
1570 end
1571
1572 def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
1573 with %User{} = target <- get_cached_by_ap_id(ap_id) do
1574 subscribed_to?(user, target)
1575 end
1576 end
1577
1578 @doc """
1579 Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
1580 E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
1581 """
1582 @spec outgoing_relationships_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
1583 def outgoing_relationships_ap_ids(_user, []), do: %{}
1584
1585 def outgoing_relationships_ap_ids(nil, _relationship_types), do: %{}
1586
1587 def outgoing_relationships_ap_ids(%User{} = user, relationship_types)
1588 when is_list(relationship_types) do
1589 db_result =
1590 user
1591 |> assoc(:outgoing_relationships)
1592 |> join(:inner, [user_rel], u in assoc(user_rel, :target))
1593 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1594 |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
1595 |> group_by([user_rel, u], user_rel.relationship_type)
1596 |> Repo.all()
1597 |> Enum.into(%{}, fn [k, v] -> {k, v} end)
1598
1599 Enum.into(
1600 relationship_types,
1601 %{},
1602 fn rel_type -> {rel_type, db_result[rel_type] || []} end
1603 )
1604 end
1605
1606 def incoming_relationships_ungrouped_ap_ids(user, relationship_types, ap_ids \\ nil)
1607
1608 def incoming_relationships_ungrouped_ap_ids(_user, [], _ap_ids), do: []
1609
1610 def incoming_relationships_ungrouped_ap_ids(nil, _relationship_types, _ap_ids), do: []
1611
1612 def incoming_relationships_ungrouped_ap_ids(%User{} = user, relationship_types, ap_ids)
1613 when is_list(relationship_types) do
1614 user
1615 |> assoc(:incoming_relationships)
1616 |> join(:inner, [user_rel], u in assoc(user_rel, :source))
1617 |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
1618 |> maybe_filter_on_ap_id(ap_ids)
1619 |> select([user_rel, u], u.ap_id)
1620 |> distinct(true)
1621 |> Repo.all()
1622 end
1623
1624 defp maybe_filter_on_ap_id(query, ap_ids) when is_list(ap_ids) do
1625 where(query, [user_rel, u], u.ap_id in ^ap_ids)
1626 end
1627
1628 defp maybe_filter_on_ap_id(query, _ap_ids), do: query
1629
1630 def set_activation_async(user, status \\ true) do
1631 BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
1632 end
1633
1634 @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1635 def set_activation(users, status) when is_list(users) do
1636 Repo.transaction(fn ->
1637 for user <- users, do: set_activation(user, status)
1638 end)
1639 end
1640
1641 @spec set_activation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
1642 def set_activation(%User{} = user, status) do
1643 with {:ok, user} <- set_activation_status(user, status) do
1644 user
1645 |> get_followers()
1646 |> Enum.filter(& &1.local)
1647 |> Enum.each(&set_cache(update_following_count(&1)))
1648
1649 # Only update local user counts, remote will be update during the next pull.
1650 user
1651 |> get_friends()
1652 |> Enum.filter(& &1.local)
1653 |> Enum.each(&do_unfollow(user, &1))
1654
1655 {:ok, user}
1656 end
1657 end
1658
1659 def approve(users) when is_list(users) do
1660 Repo.transaction(fn ->
1661 Enum.map(users, fn user ->
1662 with {:ok, user} <- approve(user), do: user
1663 end)
1664 end)
1665 end
1666
1667 def approve(%User{is_approved: false} = user) do
1668 with chg <- change(user, is_approved: true),
1669 {:ok, user} <- update_and_set_cache(chg) do
1670 post_register_action(user)
1671 {:ok, user}
1672 end
1673 end
1674
1675 def approve(%User{} = user), do: {:ok, user}
1676
1677 def confirm(users) when is_list(users) do
1678 Repo.transaction(fn ->
1679 Enum.map(users, fn user ->
1680 with {:ok, user} <- confirm(user), do: user
1681 end)
1682 end)
1683 end
1684
1685 def confirm(%User{is_confirmed: false} = user) do
1686 with chg <- confirmation_changeset(user, set_confirmation: true),
1687 {:ok, user} <- update_and_set_cache(chg) do
1688 post_register_action(user)
1689 {:ok, user}
1690 end
1691 end
1692
1693 def confirm(%User{} = user), do: {:ok, user}
1694
1695 def set_suggestion(users, is_suggested) when is_list(users) do
1696 Repo.transaction(fn ->
1697 Enum.map(users, fn user ->
1698 with {:ok, user} <- set_suggestion(user, is_suggested), do: user
1699 end)
1700 end)
1701 end
1702
1703 def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
1704
1705 def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
1706 user
1707 |> change(is_suggested: is_suggested)
1708 |> update_and_set_cache()
1709 end
1710
1711 def update_notification_settings(%User{} = user, settings) do
1712 user
1713 |> cast(%{notification_settings: settings}, [])
1714 |> cast_embed(:notification_settings)
1715 |> validate_required([:notification_settings])
1716 |> update_and_set_cache()
1717 end
1718
1719 @spec purge_user_changeset(User.t()) :: Changeset.t()
1720 def purge_user_changeset(user) do
1721 # "Right to be forgotten"
1722 # https://gdpr.eu/right-to-be-forgotten/
1723 change(user, %{
1724 bio: "",
1725 raw_bio: nil,
1726 email: nil,
1727 name: nil,
1728 password_hash: nil,
1729 avatar: %{},
1730 tags: [],
1731 last_refreshed_at: nil,
1732 last_digest_emailed_at: nil,
1733 banner: %{},
1734 background: %{},
1735 note_count: 0,
1736 follower_count: 0,
1737 following_count: 0,
1738 is_locked: false,
1739 password_reset_pending: false,
1740 registration_reason: nil,
1741 confirmation_token: nil,
1742 domain_blocks: [],
1743 is_active: false,
1744 ap_enabled: false,
1745 is_moderator: false,
1746 is_admin: false,
1747 mastofe_settings: nil,
1748 mascot: nil,
1749 emoji: %{},
1750 pleroma_settings_store: %{},
1751 fields: [],
1752 raw_fields: [],
1753 is_discoverable: false,
1754 also_known_as: []
1755 # id: preserved
1756 # ap_id: preserved
1757 # nickname: preserved
1758 })
1759 end
1760
1761 # Purge doesn't delete the user from the database.
1762 # It just nulls all its fields and deactivates it.
1763 # See `User.purge_user_changeset/1` above.
1764 defp purge(%User{} = user) do
1765 user
1766 |> purge_user_changeset()
1767 |> update_and_set_cache()
1768 end
1769
1770 def delete(users) when is_list(users) do
1771 for user <- users, do: delete(user)
1772 end
1773
1774 def delete(%User{} = user) do
1775 # Purge the user immediately
1776 purge(user)
1777 BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
1778 end
1779
1780 # *Actually* delete the user from the DB
1781 defp delete_from_db(%User{} = user) do
1782 invalidate_cache(user)
1783 Repo.delete(user)
1784 end
1785
1786 # If the user never finalized their account, it's safe to delete them.
1787 defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user),
1788 do: delete_from_db(user)
1789
1790 defp maybe_delete_from_db(%User{local: true, is_approved: false} = user),
1791 do: delete_from_db(user)
1792
1793 defp maybe_delete_from_db(user), do: {:ok, user}
1794
1795 def perform(:force_password_reset, user), do: force_password_reset(user)
1796
1797 @spec perform(atom(), User.t()) :: {:ok, User.t()}
1798 def perform(:delete, %User{} = user) do
1799 # Purge the user again, in case perform/2 is called directly
1800 purge(user)
1801
1802 # Remove all relationships
1803 user
1804 |> get_followers()
1805 |> Enum.each(fn follower ->
1806 ActivityPub.unfollow(follower, user)
1807 unfollow(follower, user)
1808 end)
1809
1810 user
1811 |> get_friends()
1812 |> Enum.each(fn followed ->
1813 ActivityPub.unfollow(user, followed)
1814 unfollow(user, followed)
1815 end)
1816
1817 delete_user_activities(user)
1818 delete_notifications_from_user_activities(user)
1819 delete_outgoing_pending_follow_requests(user)
1820
1821 maybe_delete_from_db(user)
1822 end
1823
1824 def perform(:set_activation_async, user, status), do: set_activation(user, status)
1825
1826 @spec external_users_query() :: Ecto.Query.t()
1827 def external_users_query do
1828 User.Query.build(%{
1829 external: true,
1830 active: true,
1831 order_by: :id
1832 })
1833 end
1834
1835 @spec external_users(keyword()) :: [User.t()]
1836 def external_users(opts \\ []) do
1837 query =
1838 external_users_query()
1839 |> select([u], struct(u, [:id, :ap_id]))
1840
1841 query =
1842 if opts[:max_id],
1843 do: where(query, [u], u.id > ^opts[:max_id]),
1844 else: query
1845
1846 query =
1847 if opts[:limit],
1848 do: limit(query, ^opts[:limit]),
1849 else: query
1850
1851 Repo.all(query)
1852 end
1853
1854 def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
1855 Notification
1856 |> join(:inner, [n], activity in assoc(n, :activity))
1857 |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
1858 |> Repo.delete_all()
1859 end
1860
1861 def delete_user_activities(%User{ap_id: ap_id} = user) do
1862 ap_id
1863 |> Activity.Queries.by_actor()
1864 |> Repo.chunk_stream(50, :batches)
1865 |> Stream.each(fn activities ->
1866 Enum.each(activities, fn activity -> delete_activity(activity, user) end)
1867 end)
1868 |> Stream.run()
1869 end
1870
1871 defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
1872 with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
1873 {:ok, delete_data, _} <- Builder.delete(user, object) do
1874 Pipeline.common_pipeline(delete_data, local: user.local)
1875 else
1876 {:find_object, nil} ->
1877 # We have the create activity, but not the object, it was probably pruned.
1878 # Insert a tombstone and try again
1879 with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
1880 {:ok, _tombstone} <- Object.create(tombstone_data) do
1881 delete_activity(activity, user)
1882 end
1883
1884 e ->
1885 Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
1886 Logger.error("Error: #{inspect(e)}")
1887 end
1888 end
1889
1890 defp delete_activity(%{data: %{"type" => type}} = activity, user)
1891 when type in ["Like", "Announce"] do
1892 {:ok, undo, _} = Builder.undo(user, activity)
1893 Pipeline.common_pipeline(undo, local: user.local)
1894 end
1895
1896 defp delete_activity(_activity, _user), do: "Doing nothing"
1897
1898 defp delete_outgoing_pending_follow_requests(user) do
1899 user
1900 |> FollowingRelationship.outgoing_pending_follow_requests_query()
1901 |> Repo.delete_all()
1902 end
1903
1904 def html_filter_policy(%User{no_rich_text: true}) do
1905 Pleroma.HTML.Scrubber.TwitterText
1906 end
1907
1908 def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
1909
1910 def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
1911
1912 def get_or_fetch_by_ap_id(ap_id) do
1913 cached_user = get_cached_by_ap_id(ap_id)
1914
1915 maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
1916
1917 case {cached_user, maybe_fetched_user} do
1918 {_, {:ok, %User{} = user}} ->
1919 {:ok, user}
1920
1921 {%User{} = user, _} ->
1922 {:ok, user}
1923
1924 e ->
1925 Logger.error("Could not fetch user, #{inspect(e)}")
1926 {:error, :not_found}
1927 end
1928 end
1929
1930 @doc """
1931 Creates an internal service actor by URI if missing.
1932 Optionally takes nickname for addressing.
1933 """
1934 @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
1935 def get_or_create_service_actor_by_ap_id(uri, nickname) do
1936 {_, user} =
1937 case get_cached_by_ap_id(uri) do
1938 nil ->
1939 with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
1940 Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
1941 {:error, nil}
1942 end
1943
1944 %User{invisible: false} = user ->
1945 set_invisible(user)
1946
1947 user ->
1948 {:ok, user}
1949 end
1950
1951 user
1952 end
1953
1954 @spec set_invisible(User.t()) :: {:ok, User.t()}
1955 defp set_invisible(user) do
1956 user
1957 |> change(%{invisible: true})
1958 |> update_and_set_cache()
1959 end
1960
1961 @spec create_service_actor(String.t(), String.t()) ::
1962 {:ok, User.t()} | {:error, Ecto.Changeset.t()}
1963 defp create_service_actor(uri, nickname) do
1964 %User{
1965 invisible: true,
1966 local: true,
1967 ap_id: uri,
1968 nickname: nickname,
1969 follower_address: uri <> "/followers"
1970 }
1971 |> change
1972 |> put_private_key()
1973 |> unique_constraint(:nickname)
1974 |> Repo.insert()
1975 |> set_cache()
1976 end
1977
1978 def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
1979 key =
1980 public_key_pem
1981 |> :public_key.pem_decode()
1982 |> hd()
1983 |> :public_key.pem_entry_decode()
1984
1985 {:ok, key}
1986 end
1987
1988 def public_key(_), do: {:error, "key not found"}
1989
1990 def get_public_key_for_ap_id(ap_id) do
1991 with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
1992 {:ok, public_key} <- public_key(user) do
1993 {:ok, public_key}
1994 else
1995 _ -> :error
1996 end
1997 end
1998
1999 def ap_enabled?(%User{local: true}), do: true
2000 def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
2001 def ap_enabled?(_), do: false
2002
2003 @doc "Gets or fetch a user by uri or nickname."
2004 @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
2005 def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2006 def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
2007 def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
2008
2009 # wait a period of time and return newest version of the User structs
2010 # this is because we have synchronous follow APIs and need to simulate them
2011 # with an async handshake
2012 def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
2013 with %User{} = a <- get_cached_by_id(a.id),
2014 %User{} = b <- get_cached_by_id(b.id) do
2015 {:ok, a, b}
2016 else
2017 nil -> :error
2018 end
2019 end
2020
2021 def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
2022 with :ok <- :timer.sleep(timeout),
2023 %User{} = a <- get_cached_by_id(a.id),
2024 %User{} = b <- get_cached_by_id(b.id) do
2025 {:ok, a, b}
2026 else
2027 nil -> :error
2028 end
2029 end
2030
2031 def parse_bio(bio) when is_binary(bio) and bio != "" do
2032 bio
2033 |> CommonUtils.format_input("text/plain", mentions_format: :full)
2034 |> elem(0)
2035 end
2036
2037 def parse_bio(_), do: ""
2038
2039 def parse_bio(bio, user) when is_binary(bio) and bio != "" do
2040 # TODO: get profile URLs other than user.ap_id
2041 profile_urls = [user.ap_id]
2042
2043 bio
2044 |> CommonUtils.format_input("text/plain",
2045 mentions_format: :full,
2046 rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
2047 )
2048 |> elem(0)
2049 end
2050
2051 def parse_bio(_, _), do: ""
2052
2053 def tag(user_identifiers, tags) when is_list(user_identifiers) do
2054 Repo.transaction(fn ->
2055 for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
2056 end)
2057 end
2058
2059 def tag(nickname, tags) when is_binary(nickname),
2060 do: tag(get_by_nickname(nickname), tags)
2061
2062 def tag(%User{} = user, tags),
2063 do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
2064
2065 def untag(user_identifiers, tags) when is_list(user_identifiers) do
2066 Repo.transaction(fn ->
2067 for user_identifier <- user_identifiers, do: untag(user_identifier, tags)
2068 end)
2069 end
2070
2071 def untag(nickname, tags) when is_binary(nickname),
2072 do: untag(get_by_nickname(nickname), tags)
2073
2074 def untag(%User{} = user, tags),
2075 do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
2076
2077 defp update_tags(%User{} = user, new_tags) do
2078 {:ok, updated_user} =
2079 user
2080 |> change(%{tags: new_tags})
2081 |> update_and_set_cache()
2082
2083 updated_user
2084 end
2085
2086 defp normalize_tags(tags) do
2087 [tags]
2088 |> List.flatten()
2089 |> Enum.map(&String.downcase/1)
2090 end
2091
2092 def local_nickname_regex do
2093 if Config.get([:instance, :extended_nickname_format]) do
2094 @extended_local_nickname_regex
2095 else
2096 @strict_local_nickname_regex
2097 end
2098 end
2099
2100 def local_nickname(nickname_or_mention) do
2101 nickname_or_mention
2102 |> full_nickname()
2103 |> String.split("@")
2104 |> hd()
2105 end
2106
2107 def full_nickname(%User{} = user) do
2108 if String.contains?(user.nickname, "@") do
2109 user.nickname
2110 else
2111 %{host: host} = URI.parse(user.ap_id)
2112 user.nickname <> "@" <> host
2113 end
2114 end
2115
2116 def full_nickname(nickname_or_mention),
2117 do: String.trim_leading(nickname_or_mention, "@")
2118
2119 def error_user(ap_id) do
2120 %User{
2121 name: ap_id,
2122 ap_id: ap_id,
2123 nickname: "erroruser@example.com",
2124 inserted_at: NaiveDateTime.utc_now()
2125 }
2126 end
2127
2128 @spec all_superusers() :: [User.t()]
2129 def all_superusers do
2130 User.Query.build(%{super_users: true, local: true, is_active: true})
2131 |> Repo.all()
2132 end
2133
2134 def muting_reblogs?(%User{} = user, %User{} = target) do
2135 UserRelationship.reblog_mute_exists?(user, target)
2136 end
2137
2138 def showing_reblogs?(%User{} = user, %User{} = target) do
2139 not muting_reblogs?(user, target)
2140 end
2141
2142 @doc """
2143 The function returns a query to get users with no activity for given interval of days.
2144 Inactive users are those who didn't read any notification, or had any activity where
2145 the user is the activity's actor, during `inactivity_threshold` days.
2146 Deactivated users will not appear in this list.
2147
2148 ## Examples
2149
2150 iex> Pleroma.User.list_inactive_users()
2151 %Ecto.Query{}
2152 """
2153 @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
2154 def list_inactive_users_query(inactivity_threshold \\ 7) do
2155 negative_inactivity_threshold = -inactivity_threshold
2156 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2157 # Subqueries are not supported in `where` clauses, join gets too complicated.
2158 has_read_notifications =
2159 from(n in Pleroma.Notification,
2160 where: n.seen == true,
2161 group_by: n.id,
2162 having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
2163 select: n.user_id
2164 )
2165 |> Pleroma.Repo.all()
2166
2167 from(u in Pleroma.User,
2168 left_join: a in Pleroma.Activity,
2169 on: u.ap_id == a.actor,
2170 where: not is_nil(u.nickname),
2171 where: u.is_active == ^true,
2172 where: u.id not in ^has_read_notifications,
2173 group_by: u.id,
2174 having:
2175 max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
2176 is_nil(max(a.inserted_at))
2177 )
2178 end
2179
2180 @doc """
2181 Enable or disable email notifications for user
2182
2183 ## Examples
2184
2185 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
2186 Pleroma.User{email_notifications: %{"digest" => true}}
2187
2188 iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
2189 Pleroma.User{email_notifications: %{"digest" => false}}
2190 """
2191 @spec switch_email_notifications(t(), String.t(), boolean()) ::
2192 {:ok, t()} | {:error, Ecto.Changeset.t()}
2193 def switch_email_notifications(user, type, status) do
2194 User.update_email_notifications(user, %{type => status})
2195 end
2196
2197 @doc """
2198 Set `last_digest_emailed_at` value for the user to current time
2199 """
2200 @spec touch_last_digest_emailed_at(t()) :: t()
2201 def touch_last_digest_emailed_at(user) do
2202 now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
2203
2204 {:ok, updated_user} =
2205 user
2206 |> change(%{last_digest_emailed_at: now})
2207 |> update_and_set_cache()
2208
2209 updated_user
2210 end
2211
2212 @spec set_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
2213 def set_confirmation(%User{} = user, bool) do
2214 user
2215 |> confirmation_changeset(set_confirmation: bool)
2216 |> update_and_set_cache()
2217 end
2218
2219 def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
2220 mascot
2221 end
2222
2223 def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
2224 # use instance-default
2225 config = Config.get([:assets, :mascots])
2226 default_mascot = Config.get([:assets, :default_mascot])
2227 mascot = Keyword.get(config, default_mascot)
2228
2229 %{
2230 "id" => "default-mascot",
2231 "url" => mascot[:url],
2232 "preview_url" => mascot[:url],
2233 "pleroma" => %{
2234 "mime_type" => mascot[:mime_type]
2235 }
2236 }
2237 end
2238
2239 def get_ap_ids_by_nicknames(nicknames) do
2240 from(u in User,
2241 where: u.nickname in ^nicknames,
2242 select: u.ap_id
2243 )
2244 |> Repo.all()
2245 end
2246
2247 defp put_password_hash(
2248 %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
2249 ) do
2250 change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
2251 end
2252
2253 defp put_password_hash(changeset), do: changeset
2254
2255 def is_internal_user?(%User{nickname: nil}), do: true
2256 def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
2257 def is_internal_user?(_), do: false
2258
2259 # A hack because user delete activities have a fake id for whatever reason
2260 # TODO: Get rid of this
2261 def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: []
2262
2263 def get_delivered_users_by_object_id(object_id) do
2264 from(u in User,
2265 inner_join: delivery in assoc(u, :deliveries),
2266 where: delivery.object_id == ^object_id
2267 )
2268 |> Repo.all()
2269 end
2270
2271 def change_email(user, email) do
2272 user
2273 |> cast(%{email: email}, [:email])
2274 |> maybe_validate_required_email(false)
2275 |> unique_constraint(:email)
2276 |> validate_format(:email, @email_regex)
2277 |> update_and_set_cache()
2278 end
2279
2280 def alias_users(user) do
2281 user.also_known_as
2282 |> Enum.map(&User.get_cached_by_ap_id/1)
2283 |> Enum.filter(fn user -> user != nil end)
2284 end
2285
2286 def add_alias(user, new_alias_user) do
2287 current_aliases = user.also_known_as || []
2288 new_alias_ap_id = new_alias_user.ap_id
2289
2290 if new_alias_ap_id in current_aliases do
2291 {:ok, user}
2292 else
2293 user
2294 |> cast(%{also_known_as: current_aliases ++ [new_alias_ap_id]}, [:also_known_as])
2295 |> update_and_set_cache()
2296 end
2297 end
2298
2299 def delete_alias(user, alias_user) do
2300 current_aliases = user.also_known_as || []
2301 alias_ap_id = alias_user.ap_id
2302
2303 if alias_ap_id in current_aliases do
2304 user
2305 |> cast(%{also_known_as: current_aliases -- [alias_ap_id]}, [:also_known_as])
2306 |> update_and_set_cache()
2307 else
2308 {:error, :no_such_alias}
2309 end
2310 end
2311
2312 # Internal function; public one is `deactivate/2`
2313 defp set_activation_status(user, status) do
2314 user
2315 |> cast(%{is_active: status}, [:is_active])
2316 |> update_and_set_cache()
2317 end
2318
2319 def update_banner(user, banner) do
2320 user
2321 |> cast(%{banner: banner}, [:banner])
2322 |> update_and_set_cache()
2323 end
2324
2325 def update_background(user, background) do
2326 user
2327 |> cast(%{background: background}, [:background])
2328 |> update_and_set_cache()
2329 end
2330
2331 def validate_fields(changeset, remote? \\ false) do
2332 limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
2333 limit = Config.get([:instance, limit_name], 0)
2334
2335 changeset
2336 |> validate_length(:fields, max: limit)
2337 |> validate_change(:fields, fn :fields, fields ->
2338 if Enum.all?(fields, &valid_field?/1) do
2339 []
2340 else
2341 [fields: "invalid"]
2342 end
2343 end)
2344 end
2345
2346 defp valid_field?(%{"name" => name, "value" => value}) do
2347 name_limit = Config.get([:instance, :account_field_name_length], 255)
2348 value_limit = Config.get([:instance, :account_field_value_length], 255)
2349
2350 is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
2351 String.length(value) <= value_limit
2352 end
2353
2354 defp valid_field?(_), do: false
2355
2356 defp truncate_field(%{"name" => name, "value" => value}) do
2357 {name, _chopped} =
2358 String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
2359
2360 {value, _chopped} =
2361 String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
2362
2363 %{"name" => name, "value" => value}
2364 end
2365
2366 def admin_api_update(user, params) do
2367 user
2368 |> cast(params, [
2369 :is_moderator,
2370 :is_admin,
2371 :show_role
2372 ])
2373 |> update_and_set_cache()
2374 end
2375
2376 @doc "Signs user out of all applications"
2377 def global_sign_out(user) do
2378 OAuth.Authorization.delete_user_authorizations(user)
2379 OAuth.Token.delete_user_tokens(user)
2380 end
2381
2382 def mascot_update(user, url) do
2383 user
2384 |> cast(%{mascot: url}, [:mascot])
2385 |> validate_required([:mascot])
2386 |> update_and_set_cache()
2387 end
2388
2389 def mastodon_settings_update(user, settings) do
2390 user
2391 |> cast(%{mastofe_settings: settings}, [:mastofe_settings])
2392 |> validate_required([:mastofe_settings])
2393 |> update_and_set_cache()
2394 end
2395
2396 @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
2397 def confirmation_changeset(user, set_confirmation: confirmed?) do
2398 params =
2399 if confirmed? do
2400 %{
2401 is_confirmed: true,
2402 confirmation_token: nil
2403 }
2404 else
2405 %{
2406 is_confirmed: false,
2407 confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
2408 }
2409 end
2410
2411 cast(user, params, [:is_confirmed, :confirmation_token])
2412 end
2413
2414 @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
2415 def approval_changeset(user, set_approval: approved?) do
2416 cast(user, %{is_approved: approved?}, [:is_approved])
2417 end
2418
2419 @spec add_pinned_object_id(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
2420 def add_pinned_object_id(%User{} = user, object_id) do
2421 if !user.pinned_objects[object_id] do
2422 params = %{pinned_objects: Map.put(user.pinned_objects, object_id, NaiveDateTime.utc_now())}
2423
2424 user
2425 |> cast(params, [:pinned_objects])
2426 |> validate_change(:pinned_objects, fn :pinned_objects, pinned_objects ->
2427 max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
2428
2429 if Enum.count(pinned_objects) <= max_pinned_statuses do
2430 []
2431 else
2432 [pinned_objects: "You have already pinned the maximum number of statuses"]
2433 end
2434 end)
2435 else
2436 change(user)
2437 end
2438 |> update_and_set_cache()
2439 end
2440
2441 @spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
2442 def remove_pinned_object_id(%User{} = user, object_id) do
2443 user
2444 |> cast(
2445 %{pinned_objects: Map.delete(user.pinned_objects, object_id)},
2446 [:pinned_objects]
2447 )
2448 |> update_and_set_cache()
2449 end
2450
2451 def update_email_notifications(user, settings) do
2452 email_notifications =
2453 user.email_notifications
2454 |> Map.merge(settings)
2455 |> Map.take(["digest"])
2456
2457 params = %{email_notifications: email_notifications}
2458 fields = [:email_notifications]
2459
2460 user
2461 |> cast(params, fields)
2462 |> validate_required(fields)
2463 |> update_and_set_cache()
2464 end
2465
2466 defp set_domain_blocks(user, domain_blocks) do
2467 params = %{domain_blocks: domain_blocks}
2468
2469 user
2470 |> cast(params, [:domain_blocks])
2471 |> validate_required([:domain_blocks])
2472 |> update_and_set_cache()
2473 end
2474
2475 def block_domain(user, domain_blocked) do
2476 set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
2477 end
2478
2479 def unblock_domain(user, domain_blocked) do
2480 set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
2481 end
2482
2483 @spec add_to_block(User.t(), User.t()) ::
2484 {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
2485 defp add_to_block(%User{} = user, %User{} = blocked) do
2486 with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
2487 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2488 {:ok, relationship}
2489 end
2490 end
2491
2492 @spec add_to_block(User.t(), User.t()) ::
2493 {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
2494 defp remove_from_block(%User{} = user, %User{} = blocked) do
2495 with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
2496 @cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
2497 {:ok, relationship}
2498 end
2499 end
2500
2501 def set_invisible(user, invisible) do
2502 params = %{invisible: invisible}
2503
2504 user
2505 |> cast(params, [:invisible])
2506 |> validate_required([:invisible])
2507 |> update_and_set_cache()
2508 end
2509
2510 def sanitize_html(%User{} = user) do
2511 sanitize_html(user, nil)
2512 end
2513
2514 # User data that mastodon isn't filtering (treated as plaintext):
2515 # - field name
2516 # - display name
2517 def sanitize_html(%User{} = user, filter) do
2518 fields =
2519 Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
2520 %{
2521 "name" => name,
2522 "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
2523 }
2524 end)
2525
2526 user
2527 |> Map.put(:bio, HTML.filter_tags(user.bio, filter))
2528 |> Map.put(:fields, fields)
2529 end
2530
2531 def get_host(%User{ap_id: ap_id} = _user) do
2532 URI.parse(ap_id).host
2533 end
2534
2535 def update_last_active_at(%__MODULE__{local: true} = user) do
2536 user
2537 |> cast(%{last_active_at: NaiveDateTime.utc_now()}, [:last_active_at])
2538 |> update_and_set_cache()
2539 end
2540
2541 def active_user_count(days \\ 30) do
2542 active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
2543
2544 __MODULE__
2545 |> where([u], u.last_active_at >= ^active_after)
2546 |> where([u], u.local == true)
2547 |> Repo.aggregate(:count)
2548 end
2549
2550 def update_last_status_at(user) do
2551 User
2552 |> where(id: ^user.id)
2553 |> update([u], set: [last_status_at: fragment("NOW()")])
2554 |> select([u], u)
2555 |> Repo.update_all([])
2556 |> case do
2557 {1, [user]} -> set_cache(user)
2558 _ -> {:error, user}
2559 end
2560 end
2561
2562 defp maybe_load_followed_hashtags(%User{followed_hashtags: follows} = user)
2563 when is_list(follows),
2564 do: user
2565
2566 defp maybe_load_followed_hashtags(%User{} = user) do
2567 followed_hashtags = HashtagFollow.get_by_user(user)
2568 %{user | followed_hashtags: followed_hashtags}
2569 end
2570
2571 def followed_hashtags(%User{followed_hashtags: follows})
2572 when is_list(follows),
2573 do: follows
2574
2575 def followed_hashtags(%User{} = user) do
2576 {:ok, user} =
2577 user
2578 |> maybe_load_followed_hashtags()
2579 |> set_cache()
2580
2581 user.followed_hashtags
2582 end
2583
2584 def follow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
2585 Logger.debug("Follow hashtag #{hashtag.name} for user #{user.nickname}")
2586 user = maybe_load_followed_hashtags(user)
2587
2588 with {:ok, _} <- HashtagFollow.new(user, hashtag),
2589 follows <- HashtagFollow.get_by_user(user),
2590 %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
2591 user
2592 |> set_cache()
2593 end
2594 end
2595
2596 def unfollow_hashtag(%User{} = user, %Hashtag{} = hashtag) do
2597 Logger.debug("Unfollow hashtag #{hashtag.name} for user #{user.nickname}")
2598 user = maybe_load_followed_hashtags(user)
2599
2600 with {:ok, _} <- HashtagFollow.delete(user, hashtag),
2601 follows <- HashtagFollow.get_by_user(user),
2602 %User{} = user <- user |> Map.put(:followed_hashtags, follows) do
2603 user
2604 |> set_cache()
2605 end
2606 end
2607
2608 def following_hashtag?(%User{} = user, %Hashtag{} = hashtag) do
2609 not is_nil(HashtagFollow.get(user, hashtag))
2610 end
2611 end