Merge branch 'length-limit-bio' into 'develop'
authorrinpatch <rinpatch@sdf.org>
Tue, 13 Aug 2019 22:40:13 +0000 (22:40 +0000)
committerrinpatch <rinpatch@sdf.org>
Tue, 13 Aug 2019 22:40:13 +0000 (22:40 +0000)
Add configurable length limits for `User.bio` and `User.name`

See merge request pleroma/pleroma!1515

1  2 
CHANGELOG.md
config/config.exs
docs/config.md
lib/pleroma/user.ex
test/user_test.exs

diff --combined CHANGELOG.md
index b4229bd503306438d3c2209b28170e7a640e6687,e9d4e17102fc9685e179136981b7b11b3b11909d..f0783d48970f1cfc59ed6b3db2084c08b9221733
@@@ -21,14 -21,10 +21,14 @@@ The format is based on [Keep a Changelo
  
  ### Fixed
  - Not being able to pin unlisted posts
 +- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
  - Metadata rendering errors resulting in the entire page being inaccessible
 +- `federation_incoming_replies_max_depth` option being ignored in certain cases
  - Federation/MediaProxy not working with instances that have wrong certificate order
  - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
  - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
 +- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
 +- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
  - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
  - Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
  - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
  - ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
  - ActivityPub S2S: remote user deletions now work the same as local user deletions.
  - Not being able to access the Mastodon FE login page on private instances
 +- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
 +- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
 +- Report email not being sent to admins when the reporter is a remote user
 +- MRF: ensure that subdomain_match calls are case-insensitive
  
  ### Added
  - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
  - Added synchronization of following/followers counters for external users
  - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
  - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
+ - Configuration: `user_bio_length` and `user_name_length` options.
  - Addressable lists
  - Twitter API: added rate limit for `/api/account/password_reset` endpoint.
  - ActivityPub: Add an internal service actor for fetching ActivityPub objects.
  - ActivityPub: Optional signing of ActivityPub object fetches.
  - Admin API: Endpoint for fetching latest user's statuses
  - Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
 +- Relays: Added a task to list relay subscriptions.
 +- Mix Tasks: `mix pleroma.database fix_likes_collections`
 +- Federation: Remove `likes` from objects.
  
  ### Changed
  - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
  - RichMedia: parsers and their order are configured in `rich_media` config.
  - RichMedia: add the rich media ttl based on image expiration time.
  
 +### Removed
 +- Emoji: Remove longfox emojis.
 +- Remove `Reply-To` header from report emails for admins.
 +- ActivityPub: The `accept_blocks` configuration setting.
 +
  ## [1.0.1] - 2019-07-14
  ### Security
  - OStatus: fix an object spoofing vulnerability.
  - Rich media: Do not crawl private IP ranges
  
  ### Added
 +- Digest email for inactive users
  - Add a generic settings store for frontends / clients to use.
  - Explicit addressing option for posting.
  - Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
  - Configuration: `notify_email` option
  - Configuration: Media proxy `whitelist` option
  - Configuration: `report_uri` option
 +- Configuration: `email_notifications` option
  - Configuration: `limit_to_local_content` option
  - Pleroma API: User subscriptions
  - Pleroma API: Healthcheck endpoint
diff --combined config/config.exs
index bf497031408fd52b33c964801e000855a8e5e1a0,aa4cdf409d312c79430689f6d8354b8fc099a742..b7047a039afb06a63c639f265a20dc28a0a40b65
@@@ -253,6 -253,8 +253,8 @@@ config :pleroma, :instance
    skip_thread_containment: true,
    limit_to_local_content: :unauthenticated,
    dynamic_configuration: false,
+   user_bio_length: 5000,
+   user_name_length: 100,
    external_user_synchronization: true
  
  config :pleroma, :markup,
@@@ -302,6 -304,7 +304,6 @@@ config :pleroma, :assets
    default_mascot: :pleroma_fox_tan
  
  config :pleroma, :activitypub,
 -  accept_blocks: true,
    unfollow_blocked: true,
    outgoing_blocks: true,
    follow_handshake_timeout: 500,
@@@ -513,14 -516,6 +515,14 @@@ config :pleroma, Pleroma.ScheduledActiv
    total_user_limit: 300,
    enabled: true
  
 +config :pleroma, :email_notifications,
 +  digest: %{
 +    active: false,
 +    schedule: "0 0 * * 0",
 +    interval: 7,
 +    inactivity_threshold: 7
 +  }
 +
  config :pleroma, :oauth2,
    token_expires_in: 600,
    issue_new_refresh_token: true,
diff --combined docs/config.md
index d0247ef9cc0b7447071344aa1869a769e9934f78,8f58eaf06b72de85b8b3554e4814fc648688056b..e5c3f08faedad5bfe7a413fa3a7438eea44dfe9d
@@@ -18,7 -18,6 +18,7 @@@ Note: `strip_exif` has been replaced b
  
  ## Pleroma.Uploaders.S3
  * `bucket`: S3 bucket name
 +* `bucket_namespace`: S3 bucket namespace
  * `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
  * `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
  For example, when using CDN to S3 virtual host format, set "".
@@@ -26,7 -25,7 +26,7 @@@ At this time, write CNAME to CDN in pub
  
  ## Pleroma.Upload.Filter.Mogrify
  
 -* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"impode", "1"}]`.
 +* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
  
  ## Pleroma.Upload.Filter.Dedupe
  
@@@ -126,6 -125,8 +126,8 @@@ config :pleroma, Pleroma.Emails.Mailer
  * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
  * `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
  * `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
+ * `user_bio_length`: A user bio maximum length (default: `5000`)
+ * `user_name_length`: A user name maximum length (default: `100`)
  * `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
  * `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
  * `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
@@@ -329,6 -330,7 +331,6 @@@ config :pleroma, Pleroma.Web.Endpoint
  This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
  
  ## :activitypub
 -* ``accept_blocks``: Whether to accept incoming block activities from other instances
  * ``unfollow_blocked``: Whether blocks result in people getting unfollowed
  * ``outgoing_blocks``: Whether to federate blocks to other instances
  * ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
@@@ -536,18 -538,6 +538,18 @@@ Authentication / authorization settings
  * `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
  * `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
  
 +## :email_notifications
 +
 +Email notifications settings.
 +
 +  - digest - emails of "what you've missed" for users who have been
 +    inactive for a while.
 +    - active: globally enable or disable digest emails
 +    - schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
 +      "0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
 +    - interval: Minimum interval between digest emails to one user
 +    - inactivity_threshold: Minimum user inactivity threshold
 +
  ## OAuth consumer mode
  
  OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
diff --combined lib/pleroma/user.ex
index 7d18f099e8914b3964f480b49d0be9d99fe3a5b3,776dbbe6d1ab09ab856b8a4a4f502cd1839fdcc5..b67743846877596a4fffe812f3c76e173f7ea236
@@@ -57,7 -57,6 +57,7 @@@ defmodule Pleroma.User d
      field(:search_type, :integer, virtual: true)
      field(:tags, {:array, :string}, default: [])
      field(:last_refreshed_at, :naive_datetime_usec)
 +    field(:last_digest_emailed_at, :naive_datetime)
      has_many(:notifications, Notification)
      has_many(:registrations, Registration)
      embeds_one(:info, User.Info)
  
    def user_info(%User{} = user, args \\ %{}) do
      following_count =
 -      if args[:following_count], do: args[:following_count], else: following_count(user)
 +      if args[:following_count],
 +        do: args[:following_count],
 +        else: user.info.following_count || following_count(user)
  
      follower_count =
        if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
    end
  
    def remote_user_creation(params) do
-     params =
-       params
-       |> Map.put(:info, params[:info] || %{})
+     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
  
+     params = Map.put(params, :info, params[:info] || %{})
      info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
  
      changes =
        |> validate_required([:name, :ap_id])
        |> unique_constraint(:nickname)
        |> validate_format(:nickname, @email_regex)
-       |> validate_length(:bio, max: 5000)
-       |> validate_length(:name, max: 100)
+       |> validate_length(:bio, max: bio_limit)
+       |> validate_length(:name, max: name_limit)
        |> put_change(:local, false)
        |> put_embed(:info, info_cng)
  
    end
  
    def update_changeset(struct, params \\ %{}) do
+     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
      struct
      |> cast(params, [:bio, :name, :avatar, :following])
      |> unique_constraint(:nickname)
      |> validate_format(:nickname, local_nickname_regex())
-     |> validate_length(:bio, max: 5000)
-     |> validate_length(:name, min: 1, max: 100)
+     |> validate_length(:bio, max: bio_limit)
+     |> validate_length(:name, min: 1, max: name_limit)
    end
  
    def upgrade_changeset(struct, params \\ %{}) do
-     params =
-       params
-       |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
+     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
  
-     info_cng =
-       struct.info
-       |> User.Info.user_upgrade(params[:info])
+     params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
+     info_cng = User.Info.user_upgrade(struct.info, params[:info])
  
      struct
      |> cast(params, [
      ])
      |> unique_constraint(:nickname)
      |> validate_format(:nickname, local_nickname_regex())
-     |> validate_length(:bio, max: 5000)
-     |> validate_length(:name, max: 100)
+     |> validate_length(:bio, max: bio_limit)
+     |> validate_length(:name, max: name_limit)
      |> put_embed(:info, info_cng)
    end
  
    end
  
    def register_changeset(struct, params \\ %{}, opts \\ []) do
+     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
      need_confirmation? =
        if is_nil(opts[:need_confirmation]) do
          Pleroma.Config.get([:instance, :account_activation_required])
        |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
        |> validate_format(:nickname, local_nickname_regex())
        |> validate_format(:email, @email_regex)
-       |> validate_length(:bio, max: 1000)
-       |> validate_length(:name, min: 1, max: 100)
+       |> validate_length(:bio, max: bio_limit)
+       |> validate_length(:name, min: 1, max: name_limit)
        |> put_change(:info, info_change)
  
      changeset =
  
          {1, [follower]} = Repo.update_all(q, [])
  
 +        follower = maybe_update_following_count(follower)
 +
          {:ok, _} = update_follower_count(followed)
  
          set_cache(follower)
  
        {1, [follower]} = Repo.update_all(q, [])
  
 +      follower = maybe_update_following_count(follower)
 +
        {:ok, followed} = update_follower_count(followed)
  
        set_cache(follower)
      |> update_and_set_cache()
    end
  
 +  def maybe_fetch_follow_information(user) do
 +    with {:ok, user} <- fetch_follow_information(user) do
 +      user
 +    else
 +      e ->
 +        Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
 +
 +        user
 +    end
 +  end
 +
 +  def fetch_follow_information(user) do
 +    with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
 +      info_cng = User.Info.follow_information_update(user.info, info)
 +
 +      changeset =
 +        user
 +        |> change()
 +        |> put_embed(:info, info_cng)
 +
 +      update_and_set_cache(changeset)
 +    else
 +      {:error, _} = e -> e
 +      e -> {:error, e}
 +    end
 +  end
 +
    def update_follower_count(%User{} = user) do
 -    follower_count_query =
 -      User.Query.build(%{followers: user, deactivated: false})
 -      |> select([u], %{count: count(u.id)})
 +    if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
 +      follower_count_query =
 +        User.Query.build(%{followers: user, deactivated: false})
 +        |> select([u], %{count: count(u.id)})
 +
 +      User
 +      |> where(id: ^user.id)
 +      |> join(:inner, [u], s in subquery(follower_count_query))
 +      |> update([u, s],
 +        set: [
 +          info:
 +            fragment(
 +              "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
 +              u.info,
 +              s.count
 +            )
 +        ]
 +      )
 +      |> select([u], u)
 +      |> Repo.update_all([])
 +      |> case do
 +        {1, [user]} -> set_cache(user)
 +        _ -> {:error, user}
 +      end
 +    else
 +      {:ok, maybe_fetch_follow_information(user)}
 +    end
 +  end
  
 -    User
 -    |> where(id: ^user.id)
 -    |> join(:inner, [u], s in subquery(follower_count_query))
 -    |> update([u, s],
 -      set: [
 -        info:
 -          fragment(
 -            "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
 -            u.info,
 -            s.count
 -          )
 -      ]
 -    )
 -    |> select([u], u)
 -    |> Repo.update_all([])
 -    |> case do
 -      {1, [user]} -> set_cache(user)
 -      _ -> {:error, user}
 +  def maybe_update_following_count(%User{local: false} = user) do
 +    if Pleroma.Config.get([:instance, :external_user_synchronization]) do
 +      {:ok, maybe_fetch_follow_information(user)}
 +    else
 +      user
      end
    end
  
 +  def maybe_update_following_count(user), do: user
 +
    def remove_duplicated_following(%User{following: following} = user) do
      uniq_following = Enum.uniq(following)
  
      target.ap_id not in user.info.muted_reblogs
    end
  
 +  @doc """
 +  The function returns a query to get users with no activity for given interval of days.
 +  Inactive users are those who didn't read any notification, or had any activity where
 +  the user is the activity's actor, during `inactivity_threshold` days.
 +  Deactivated users will not appear in this list.
 +
 +  ## Examples
 +
 +      iex> Pleroma.User.list_inactive_users()
 +      %Ecto.Query{}
 +  """
 +  @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
 +  def list_inactive_users_query(inactivity_threshold \\ 7) do
 +    negative_inactivity_threshold = -inactivity_threshold
 +    now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 +    # Subqueries are not supported in `where` clauses, join gets too complicated.
 +    has_read_notifications =
 +      from(n in Pleroma.Notification,
 +        where: n.seen == true,
 +        group_by: n.id,
 +        having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
 +        select: n.user_id
 +      )
 +      |> Pleroma.Repo.all()
 +
 +    from(u in Pleroma.User,
 +      left_join: a in Pleroma.Activity,
 +      on: u.ap_id == a.actor,
 +      where: not is_nil(u.nickname),
 +      where: fragment("not (?->'deactivated' @> 'true')", u.info),
 +      where: u.id not in ^has_read_notifications,
 +      group_by: u.id,
 +      having:
 +        max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
 +          is_nil(max(a.inserted_at))
 +    )
 +  end
 +
 +  @doc """
 +  Enable or disable email notifications for user
 +
 +  ## Examples
 +
 +      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
 +      Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
 +
 +      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
 +      Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
 +  """
 +  @spec switch_email_notifications(t(), String.t(), boolean()) ::
 +          {:ok, t()} | {:error, Ecto.Changeset.t()}
 +  def switch_email_notifications(user, type, status) do
 +    info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
 +
 +    change(user)
 +    |> put_embed(:info, info)
 +    |> update_and_set_cache()
 +  end
 +
 +  @doc """
 +  Set `last_digest_emailed_at` value for the user to current time
 +  """
 +  @spec touch_last_digest_emailed_at(t()) :: t()
 +  def touch_last_digest_emailed_at(user) do
 +    now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 +
 +    {:ok, updated_user} =
 +      user
 +      |> change(%{last_digest_emailed_at: now})
 +      |> update_and_set_cache()
 +
 +    updated_user
 +  end
 +
    @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
    def toggle_confirmation(%User{} = user) do
      need_confirmation? = !user.info.confirmation_pending
diff --combined test/user_test.exs
index 8440d456df40ace3079378a7e6ff719ab82393df,dfa91a106396cf43911a39e172478abe74e92d19..b363b322c28590daac9a09375a9d11c7ed66d72c
@@@ -525,7 -525,10 +525,10 @@@ defmodule Pleroma.UserTest d
      end
  
      test "it restricts some sizes" do
-       [bio: 5000, name: 100]
+       bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+       name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+       [bio: bio_limit, name: name_limit]
        |> Enum.each(fn {field, size} ->
          string = String.pad_leading(".", size)
          cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
      assert Map.get(user_show, "followers_count") == 2
    end
  
 +  describe "list_inactive_users_query/1" do
 +    defp days_ago(days) do
 +      NaiveDateTime.add(
 +        NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
 +        -days * 60 * 60 * 24,
 +        :second
 +      )
 +    end
 +
 +    test "Users are inactive by default" do
 +      total = 10
 +
 +      users =
 +        Enum.map(1..total, fn _ ->
 +          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
 +        end)
 +
 +      inactive_users_ids =
 +        Pleroma.User.list_inactive_users_query()
 +        |> Pleroma.Repo.all()
 +        |> Enum.map(& &1.id)
 +
 +      Enum.each(users, fn user ->
 +        assert user.id in inactive_users_ids
 +      end)
 +    end
 +
 +    test "Only includes users who has no recent activity" do
 +      total = 10
 +
 +      users =
 +        Enum.map(1..total, fn _ ->
 +          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
 +        end)
 +
 +      {inactive, active} = Enum.split(users, trunc(total / 2))
 +
 +      Enum.map(active, fn user ->
 +        to = Enum.random(users -- [user])
 +
 +        {:ok, _} =
 +          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
 +            "status" => "hey @#{to.nickname}"
 +          })
 +      end)
 +
 +      inactive_users_ids =
 +        Pleroma.User.list_inactive_users_query()
 +        |> Pleroma.Repo.all()
 +        |> Enum.map(& &1.id)
 +
 +      Enum.each(active, fn user ->
 +        refute user.id in inactive_users_ids
 +      end)
 +
 +      Enum.each(inactive, fn user ->
 +        assert user.id in inactive_users_ids
 +      end)
 +    end
 +
 +    test "Only includes users with no read notifications" do
 +      total = 10
 +
 +      users =
 +        Enum.map(1..total, fn _ ->
 +          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
 +        end)
 +
 +      [sender | recipients] = users
 +      {inactive, active} = Enum.split(recipients, trunc(total / 2))
 +
 +      Enum.each(recipients, fn to ->
 +        {:ok, _} =
 +          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
 +            "status" => "hey @#{to.nickname}"
 +          })
 +
 +        {:ok, _} =
 +          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
 +            "status" => "hey again @#{to.nickname}"
 +          })
 +      end)
 +
 +      Enum.each(active, fn user ->
 +        [n1, _n2] = Pleroma.Notification.for_user(user)
 +        {:ok, _} = Pleroma.Notification.read_one(user, n1.id)
 +      end)
 +
 +      inactive_users_ids =
 +        Pleroma.User.list_inactive_users_query()
 +        |> Pleroma.Repo.all()
 +        |> Enum.map(& &1.id)
 +
 +      Enum.each(active, fn user ->
 +        refute user.id in inactive_users_ids
 +      end)
 +
 +      Enum.each(inactive, fn user ->
 +        assert user.id in inactive_users_ids
 +      end)
 +    end
 +  end
 +
    describe "toggle_confirmation/1" do
      test "if user is confirmed" do
        user = insert(:user, info: %{confirmation_pending: false})
        assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
      end
    end
 +
 +  describe "following/followers synchronization" do
 +    setup do
 +      sync = Pleroma.Config.get([:instance, :external_user_synchronization])
 +      on_exit(fn -> Pleroma.Config.put([:instance, :external_user_synchronization], sync) end)
 +    end
 +
 +    test "updates the counters normally on following/getting a follow when disabled" do
 +      Pleroma.Config.put([:instance, :external_user_synchronization], false)
 +      user = insert(:user)
 +
 +      other_user =
 +        insert(:user,
 +          local: false,
 +          follower_address: "http://localhost:4001/users/masto_closed/followers",
 +          following_address: "http://localhost:4001/users/masto_closed/following",
 +          info: %{ap_enabled: true}
 +        )
 +
 +      assert User.user_info(other_user).following_count == 0
 +      assert User.user_info(other_user).follower_count == 0
 +
 +      {:ok, user} = Pleroma.User.follow(user, other_user)
 +      other_user = Pleroma.User.get_by_id(other_user.id)
 +
 +      assert User.user_info(user).following_count == 1
 +      assert User.user_info(other_user).follower_count == 1
 +    end
 +
 +    test "syncronizes the counters with the remote instance for the followed when enabled" do
 +      Pleroma.Config.put([:instance, :external_user_synchronization], false)
 +
 +      user = insert(:user)
 +
 +      other_user =
 +        insert(:user,
 +          local: false,
 +          follower_address: "http://localhost:4001/users/masto_closed/followers",
 +          following_address: "http://localhost:4001/users/masto_closed/following",
 +          info: %{ap_enabled: true}
 +        )
 +
 +      assert User.user_info(other_user).following_count == 0
 +      assert User.user_info(other_user).follower_count == 0
 +
 +      Pleroma.Config.put([:instance, :external_user_synchronization], true)
 +      {:ok, _user} = User.follow(user, other_user)
 +      other_user = User.get_by_id(other_user.id)
 +
 +      assert User.user_info(other_user).follower_count == 437
 +    end
 +
 +    test "syncronizes the counters with the remote instance for the follower when enabled" do
 +      Pleroma.Config.put([:instance, :external_user_synchronization], false)
 +
 +      user = insert(:user)
 +
 +      other_user =
 +        insert(:user,
 +          local: false,
 +          follower_address: "http://localhost:4001/users/masto_closed/followers",
 +          following_address: "http://localhost:4001/users/masto_closed/following",
 +          info: %{ap_enabled: true}
 +        )
 +
 +      assert User.user_info(other_user).following_count == 0
 +      assert User.user_info(other_user).follower_count == 0
 +
 +      Pleroma.Config.put([:instance, :external_user_synchronization], true)
 +      {:ok, other_user} = User.follow(other_user, user)
 +
 +      assert User.user_info(other_user).following_count == 152
 +    end
 +  end
  end