## Unreleased
+### Added
+- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
+- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
+- Mix task option for force-unfollowing relays
+
### Changed
+- Search: Users are now findable by their urls.
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
### Unfollow a Relay
-Params:
-
-* `relay_url`
+- Params:
+ - `relay_url`
+ - *optional* `force`: forcefully unfollow a relay even when the relay is not available. (default is `false`)
Response:
-# Managing emails
+# EMail administration tasks
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
```sh
mix pleroma.email test --to root@example.org
```
+
+## Send confirmation emails to all unconfirmed user accounts
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl email send_confirmation_mails
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.email send_confirmation_mails
+ ```
```
### Options
+- `--admin`/`--no-admin` - whether the user should be an admin
+- `--confirmed`/`--no-confirmed` - whether the user account is confirmed
- `--locked`/`--no-locked` - whether the user should be locked
- `--moderator`/`--no-moderator` - whether the user should be a moderator
-- `--admin`/`--no-admin` - whether the user should be an admin
## Add tags to a user
```sh
mix pleroma.user toggle_confirmed <nickname>
```
+
+## Set confirmation status for all regular active users
+*Admins and moderators are excluded*
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl user confirm_all
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.user confirm_all
+ ```
+
+## Revoke confirmation status for all regular active users
+*Admins and moderators are excluded*
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl user unconfirm_all
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.user unconfirm_all
+ ```
use Mix.Task
import Mix.Pleroma
- @shortdoc "Simple Email test"
+ @shortdoc "Email administrative tasks"
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
def run(["test" | args]) do
- Mix.Pleroma.start_pleroma()
+ start_pleroma()
{options, [], []} =
OptionParser.parse(
shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
end
+
+ def run(["resend_confirmation_emails"]) do
+ start_pleroma()
+
+ shell_info("Sending emails to all unconfirmed users")
+
+ Pleroma.User.Query.build(%{
+ local: true,
+ deactivated: false,
+ confirmation_pending: true,
+ invisible: false
+ })
+ |> Pleroma.Repo.chunk_stream(500)
+ |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1))
+ |> Stream.run()
+ end
end
end
end
- def run(["unfollow", target]) do
+ def run(["unfollow", target | rest]) do
start_pleroma()
- with {:ok, _activity} <- Relay.unfollow(target) do
+ {options, [], []} =
+ OptionParser.parse(
+ rest,
+ strict: [force: :boolean],
+ aliases: [f: :force]
+ )
+
+ force = Keyword.get(options, :force, false)
+
+ with {:ok, _activity} <- Relay.unfollow(target, %{force: force}) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
OptionParser.parse(
rest,
strict: [
- moderator: :boolean,
admin: :boolean,
- locked: :boolean
+ confirmed: :boolean,
+ locked: :boolean,
+ moderator: :boolean
]
)
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user =
- case Keyword.get(options, :moderator) do
+ case Keyword.get(options, :admin) do
nil -> user
- value -> set_moderator(user, value)
+ value -> set_admin(user, value)
+ end
+
+ user =
+ case Keyword.get(options, :confirmed) do
+ nil -> user
+ value -> set_confirmed(user, value)
end
user =
end
_user =
- case Keyword.get(options, :admin) do
+ case Keyword.get(options, :moderator) do
nil -> user
- value -> set_admin(user, value)
+ value -> set_moderator(user, value)
end
else
_ ->
end
end
+ def run(["confirm_all"]) do
+ start_pleroma()
+
+ Pleroma.User.Query.build(%{
+ local: true,
+ deactivated: false,
+ is_moderator: false,
+ is_admin: false,
+ invisible: false
+ })
+ |> Pleroma.Repo.chunk_stream(500, :batches)
+ |> Stream.each(fn users ->
+ users
+ |> Enum.each(fn user -> User.need_confirmation(user, false) end)
+ end)
+ |> Stream.run()
+ end
+
+ def run(["unconfirm_all"]) do
+ start_pleroma()
+
+ Pleroma.User.Query.build(%{
+ local: true,
+ deactivated: false,
+ is_moderator: false,
+ is_admin: false,
+ invisible: false
+ })
+ |> Pleroma.Repo.chunk_stream(500, :batches)
+ |> Stream.each(fn users ->
+ users
+ |> Enum.each(fn user -> User.need_confirmation(user, true) end)
+ end)
+ |> Stream.run()
+ end
+
def run(["sign_out", nickname]) do
start_pleroma()
shell_info("Locked status of #{user.nickname}: #{user.locked}")
user
end
+
+ defp set_confirmed(user, value) do
+ {:ok, user} = User.need_confirmation(user, !value)
+
+ shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")
+ user
+ end
end
end
end
- def mrf_user_allowlist do
- config = Config.get(:mrf_user_allowlist)
-
- if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do
- rewritten =
- Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc ->
- Map.put(acc, to_string(k), v)
- end)
-
- Config.put(:mrf_user_allowlist, rewritten)
-
- Logger.error("""
- !!!DEPRECATION WARNING!!!
- As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format.
- Pleroma 2.1 will remove support for the old format. Please change your configuration to match this:
-
- config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
- """)
-
- :error
- else
- :ok
- end
- end
-
def warn do
with :ok <- check_hellthread_threshold(),
- :ok <- mrf_user_allowlist(),
:ok <- check_old_mrf_config(),
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
if use_old_config do
Logger.error("""
!!!DEPRECATION WARNING!!!
- Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace:
- \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname`
- \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message`
+ Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g.,
+ \n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to:
+ \n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`"
""")
:error
if timeout = pool_config[:await_up_timeout] do
Logger.warn("""
!!!DEPRECATION WARNING!!!
- Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
+ Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases.
""")
Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout))
def send_welcome_email(_), do: {:ok, :noop}
@spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
- def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
+ def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
+ when is_binary(email) do
if Config.get([:instance, :account_activation_required]) do
send_confirmation_email(user)
{:ok, :enqueued}
FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
- {:ok, follower} =
- follower
- |> update_following_count()
+ {:ok, follower} = update_following_count(follower)
{:ok, follower, followed}
Enum.map(users, &toggle_confirmation/1)
end
+ @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def need_confirmation(%User{} = user, bool) do
+ user
+ |> confirmation_changeset(need_confirmation: bool)
+ |> update_and_set_cache()
+ end
+
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
mascot
end
where(query, [u], fragment("? && ?", u.tags, ^tags))
end
- defp compose_query({:is_admin, _}, query) do
- where(query, [u], u.is_admin)
+ defp compose_query({:is_admin, bool}, query) do
+ where(query, [u], u.is_admin == ^bool)
end
- defp compose_query({:is_moderator, _}, query) do
- where(query, [u], u.is_moderator)
+ defp compose_query({:is_moderator, bool}, query) do
+ where(query, [u], u.is_moderator == ^bool)
end
defp compose_query({:super_users, _}, query) do
where(query, [u], u.deactivated == ^true)
end
+ defp compose_query({:confirmation_pending, bool}, query) do
+ where(query, [u], u.confirmation_pending == ^bool)
+ end
+
defp compose_query({:need_approval, _}, query) do
where(query, [u], u.approval_pending)
end
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType
alias Pleroma.Pagination
alias Pleroma.User
+
import Ecto.Query
@limit 20
query_string = format_query(query_string)
- maybe_resolve(resolve, for_user, query_string)
+ # If this returns anything, it should bounce to the top
+ maybe_resolved = maybe_resolve(resolve, for_user, query_string)
+
+ top_user_ids =
+ []
+ |> maybe_add_resolved(maybe_resolved)
+ |> maybe_add_ap_id_match(query_string)
+ |> maybe_add_uri_match(query_string)
results =
query_string
- |> search_query(for_user, following)
+ |> search_query(for_user, following, top_user_ids)
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
results
end
+ defp maybe_add_resolved(list, {:ok, %User{} = user}) do
+ [user.id | list]
+ end
+
+ defp maybe_add_resolved(list, _), do: list
+
+ defp maybe_add_ap_id_match(list, query) do
+ if user = User.get_cached_by_ap_id(query) do
+ [user.id | list]
+ else
+ list
+ end
+ end
+
+ defp maybe_add_uri_match(list, query) do
+ with {:ok, query} <- UriType.cast(query),
+ %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do
+ [user.id | list]
+ else
+ _ -> list
+ end
+ end
+
defp format_query(query_string) do
# Strip the beginning @ off if there is a query
query_string = String.trim_leading(query_string, "@")
end
end
- defp search_query(query_string, for_user, following) do
+ defp search_query(query_string, for_user, following, top_user_ids) do
for_user
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
+ |> select_top_users(top_user_ids)
|> trigram_rank(query_string)
- |> boost_search_rank(for_user)
+ |> boost_search_rank(for_user, top_user_ids)
|> subquery()
|> order_by(desc: :search_rank)
|> maybe_restrict_local(for_user)
end
+ defp select_top_users(query, top_user_ids) do
+ from(u in query,
+ or_where: u.id in ^top_user_ids
+ )
+ end
+
defp fts_search(query, query_string) do
query_string = to_tsquery(query_string)
defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
- defp boost_search_rank(query, %User{} = for_user) do
+ defp boost_search_rank(query, %User{} = for_user, top_user_ids) do
friends_ids = User.get_friends_ids(for_user)
followers_ids = User.get_followers_ids(for_user)
CASE WHEN (?) THEN (?) * 1.5
WHEN (?) THEN (?) * 1.3
WHEN (?) THEN (?) * 1.1
+ WHEN (?) THEN 9001
ELSE (?) END
""",
u.id in ^friends_ids and u.id in ^followers_ids,
u.search_rank,
u.id in ^followers_ids,
u.search_rank,
+ u.id in ^top_user_ids,
u.search_rank
)
}
)
end
- defp boost_search_rank(query, _for_user), do: query
+ defp boost_search_rank(query, _for_user, top_user_ids) do
+ from(u in subquery(query),
+ select_merge: %{
+ search_rank:
+ fragment(
+ """
+ CASE WHEN (?) THEN 9001
+ ELSE (?) END
+ """,
+ u.id in ^top_user_ids,
+ u.search_rank
+ )
+ }
+ )
+ end
end
end
end
- @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
- def unfollow(target_instance) do
+ @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+ def unfollow(target_instance, opts \\ %{}) do
with %User{} = local_user <- get_actor(),
- {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
+ {:ok, target_user} <- fetch_target_user(target_instance, opts),
{:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
- User.unfollow(local_user, target_user)
+ case target_user.id do
+ nil -> User.update_following_count(local_user)
+ _ -> User.unfollow(local_user, target_user)
+ end
+
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity}
else
end
end
+ defp fetch_target_user(ap_id, opts) do
+ case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do
+ {_, {:ok, %User{} = user}} -> {:ok, user}
+ {true, _} -> {:ok, %User{ap_id: ap_id}}
+ {_, error} -> error
+ end
+ end
+
@spec publish(any()) :: {:ok, Activity.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(),
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
_options
)
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
data = Map.put(data, "object", strip_internal_fields(data["object"]))
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ nil <- Activity.get_create_by_object_ap_id(obj_id),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
+ else
+ %Activity{} = activity -> {:ok, activity}
+ e -> e
end
end
def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
with {:ok, _message} <- Relay.follow(target) do
- ModerationLog.insert_log(%{
- action: "relay_follow",
- actor: admin,
- target: target
- })
+ ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target})
json(conn, %{actor: target, followed_back: target in Relay.following()})
else
end
end
- def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
- with {:ok, _message} <- Relay.unfollow(target) do
- ModerationLog.insert_log(%{
- action: "relay_unfollow",
- actor: admin,
- target: target
- })
+ def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do
+ with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do
+ ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target})
json(conn, target)
else
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
- requestBody: request_body("Parameters", relay_url()),
+ requestBody: request_body("Parameters", relay_unfollow()),
responses: %{
200 =>
Operation.response("Status", "application/json", %Schema{
}
}
end
+
+ defp relay_unfollow do
+ %Schema{
+ type: :object,
+ properties: %{
+ relay_url: %Schema{type: :string, format: :uri},
+ force: %Schema{type: :boolean, default: false}
+ }
+ }
+ end
end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.MakeUserIdsCI do
+ use Ecto.Migration
+
+ def change do
+ # Migration retired, see
+ # https://git.pleroma.social/pleroma/pleroma/-/issues/2188
+ :noop
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.RevertCitextChange do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ modify(:uri, :text)
+ end
+
+ create_if_not_exists(unique_index(:users, :uri))
+ end
+end
defmodule Pleroma.Config.DeprecationWarningsTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case
use Pleroma.Tests.Helpers
import ExUnit.CaptureLog
end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option"
end
+ test "check_welcome_message_config/0" do
+ clear_config([:instance, :welcome_user_nickname], "LainChan")
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_welcome_message_config()
+ end) =~ "Your config is using the old namespace for Welcome messages configuration."
+ end
+
+ test "check_hellthread_threshold/0" do
+ clear_config([:mrf_hellthread, :threshold], 16)
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_hellthread_threshold()
+ end) =~ "You are using the old configuration mechanism for the hellthread filter."
+ end
+
+ test "check_activity_expiration_config/0" do
+ clear_config([Pleroma.ActivityExpiration, :enabled], true)
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_activity_expiration_config()
+ end) =~ "Your config is using old namespace for activity expiration configuration."
+ end
+
describe "check_gun_pool_options/0" do
test "await_up_timeout" do
config = Config.get(:connections_pool)
assert capture_log(fn ->
DeprecationWarnings.check_gun_pool_options()
end) =~
- "Your config is using old setting name `await_up_timeout` instead of `connect_timeout`"
+ "Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`."
end
test "pool timeout" do
alias Pleroma.Config
alias Pleroma.Tests.ObanHelpers
+ import Pleroma.Factory
+
setup_all do
Mix.shell(Mix.Shell.Process)
end
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
+ setup do: clear_config([:instance, :account_activation_required], true)
describe "pleroma.email test" do
test "Sends test email with no given address" do
html_body: ~r/a test email was requested./i
)
end
+
+ test "Sends confirmation emails" do
+ local_user1 =
+ insert(:user, %{
+ confirmation_pending: true,
+ confirmation_token: "mytoken",
+ deactivated: false,
+ email: "local1@pleroma.com",
+ local: true
+ })
+
+ local_user2 =
+ insert(:user, %{
+ confirmation_pending: true,
+ confirmation_token: "mytoken",
+ deactivated: false,
+ email: "local2@pleroma.com",
+ local: true
+ })
+
+ :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
+
+ ObanHelpers.perform_all()
+
+ assert_email_sent(to: {local_user1.name, local_user1.email})
+ assert_email_sent(to: {local_user2.name, local_user2.email})
+ end
+
+ test "Does not send confirmation email to inappropriate users" do
+ # confirmed user
+ insert(:user, %{
+ confirmation_pending: false,
+ confirmation_token: "mytoken",
+ deactivated: false,
+ email: "confirmed@pleroma.com",
+ local: true
+ })
+
+ # remote user
+ insert(:user, %{
+ deactivated: false,
+ email: "remote@not-pleroma.com",
+ local: false
+ })
+
+ # deactivated user =
+ insert(:user, %{
+ deactivated: true,
+ email: "deactivated@pleroma.com",
+ local: false
+ })
+
+ # invisible user
+ insert(:user, %{
+ deactivated: false,
+ email: "invisible@pleroma.com",
+ local: true,
+ invisible: true
+ })
+
+ :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
+
+ ObanHelpers.perform_all()
+
+ refute_email_sent()
+ end
end
end
assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
refute "#{target_instance}/followers" in User.following(local_user)
end
+
+ test "unfollow when relay is dead" do
+ user = insert(:user)
+ target_instance = user.ap_id
+
+ Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+ %User{ap_id: follower_id} = local_user = Relay.get_actor()
+ target_user = User.get_cached_by_ap_id(target_instance)
+ follow_activity = Utils.fetch_latest_follow(local_user, target_user)
+ User.follow(local_user, target_user)
+
+ assert "#{target_instance}/followers" in User.following(local_user)
+
+ Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} ->
+ %Tesla.Env{status: 404}
+ end)
+
+ Pleroma.Repo.delete(user)
+ Cachex.clear(:user_cache)
+
+ Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
+
+ cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
+ assert cancelled_activity.data["state"] == "accept"
+
+ assert [] ==
+ ActivityPub.fetch_activities(
+ [],
+ %{
+ type: "Undo",
+ actor_id: follower_id,
+ skip_preload: true,
+ invisible_actors: true
+ }
+ )
+ end
+
+ test "force unfollow when relay is dead" do
+ user = insert(:user)
+ target_instance = user.ap_id
+
+ Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+ %User{ap_id: follower_id} = local_user = Relay.get_actor()
+ target_user = User.get_cached_by_ap_id(target_instance)
+ follow_activity = Utils.fetch_latest_follow(local_user, target_user)
+ User.follow(local_user, target_user)
+
+ assert "#{target_instance}/followers" in User.following(local_user)
+
+ Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} ->
+ %Tesla.Env{status: 404}
+ end)
+
+ Pleroma.Repo.delete(user)
+ Cachex.clear(:user_cache)
+
+ Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance, "--force"])
+
+ cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
+ assert cancelled_activity.data["state"] == "cancelled"
+
+ [undo_activity] =
+ ActivityPub.fetch_activities(
+ [],
+ %{type: "Undo", actor_id: follower_id, skip_preload: true, invisible_actors: true}
+ )
+
+ assert undo_activity.data["type"] == "Undo"
+ assert undo_activity.data["actor"] == local_user.ap_id
+ assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
+ refute "#{target_instance}/followers" in User.following(local_user)
+ end
end
describe "mix pleroma.relay list" do
test "All statuses set" do
user = insert(:user)
- Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"])
+ Mix.Tasks.Pleroma.User.run([
+ "set",
+ user.nickname,
+ "--admin",
+ "--confirmed",
+ "--locked",
+ "--moderator"
+ ])
assert_received {:mix_shell, :info, [message]}
- assert message =~ ~r/Moderator status .* true/
+ assert message =~ ~r/Admin status .* true/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Confirmation pending .* false/
assert_received {:mix_shell, :info, [message]}
assert message =~ ~r/Locked status .* true/
assert_received {:mix_shell, :info, [message]}
- assert message =~ ~r/Admin status .* true/
+ assert message =~ ~r/Moderator status .* true/
user = User.get_cached_by_nickname(user.nickname)
assert user.is_moderator
assert user.locked
assert user.is_admin
+ refute user.confirmation_pending
end
test "All statuses unset" do
- user = insert(:user, locked: true, is_moderator: true, is_admin: true)
+ user =
+ insert(:user, locked: true, is_moderator: true, is_admin: true, confirmation_pending: true)
Mix.Tasks.Pleroma.User.run([
"set",
user.nickname,
- "--no-moderator",
"--no-admin",
- "--no-locked"
+ "--no-confirmed",
+ "--no-locked",
+ "--no-moderator"
])
assert_received {:mix_shell, :info, [message]}
- assert message =~ ~r/Moderator status .* false/
+ assert message =~ ~r/Admin status .* false/
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message =~ ~r/Confirmation pending .* true/
assert_received {:mix_shell, :info, [message]}
assert message =~ ~r/Locked status .* false/
assert_received {:mix_shell, :info, [message]}
- assert message =~ ~r/Admin status .* false/
+ assert message =~ ~r/Moderator status .* false/
user = User.get_cached_by_nickname(user.nickname)
refute user.is_moderator
refute user.locked
refute user.is_admin
+ assert user.confirmation_pending
end
test "no user to set status" do
assert message =~ "Could not change user tags"
end
end
+
+ describe "bulk confirm and unconfirm" do
+ test "confirm all" do
+ user1 = insert(:user, confirmation_pending: true)
+ user2 = insert(:user, confirmation_pending: true)
+
+ assert user1.confirmation_pending
+ assert user2.confirmation_pending
+
+ Mix.Tasks.Pleroma.User.run(["confirm_all"])
+
+ user1 = User.get_cached_by_nickname(user1.nickname)
+ user2 = User.get_cached_by_nickname(user2.nickname)
+
+ refute user1.confirmation_pending
+ refute user2.confirmation_pending
+ end
+
+ test "unconfirm all" do
+ user1 = insert(:user, confirmation_pending: false)
+ user2 = insert(:user, confirmation_pending: false)
+ admin = insert(:user, is_admin: true, confirmation_pending: false)
+ mod = insert(:user, is_moderator: true, confirmation_pending: false)
+
+ refute user1.confirmation_pending
+ refute user2.confirmation_pending
+
+ Mix.Tasks.Pleroma.User.run(["unconfirm_all"])
+
+ user1 = User.get_cached_by_nickname(user1.nickname)
+ user2 = User.get_cached_by_nickname(user2.nickname)
+ admin = User.get_cached_by_nickname(admin.nickname)
+ mod = User.get_cached_by_nickname(mod.nickname)
+
+ assert user1.confirmation_pending
+ assert user2.confirmation_pending
+ refute admin.confirmation_pending
+ refute mod.confirmation_pending
+ end
+ end
end
describe "User.search" do
setup do: clear_config([:instance, :limit_to_local_content])
+ test "returns a resolved user as the first result" do
+ Pleroma.Config.put([:instance, :limit_to_local_content], false)
+ user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/users/lain", resolve: true)
+
+ assert first_user.id == user.id
+ end
+
+ test "returns a user with matching ap_id as the first result" do
+ user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/users/lain")
+
+ assert first_user.id == user.id
+ end
+
+ test "returns a user with matching uri as the first result" do
+ user =
+ insert(:user, %{
+ nickname: "no_relation",
+ ap_id: "https://lain.com/users/lain",
+ uri: "https://lain.com/@lain"
+ })
+
+ _user = insert(:user, %{nickname: "com_user"})
+
+ [first_user, _second_user] = User.search("https://lain.com/@lain")
+
+ assert first_user.id == user.id
+ end
+
test "excludes invisible users from results" do
user = insert(:user, %{nickname: "john t1000"})
insert(:user, %{invisible: true, nickname: "john t800"})
describe "describe/0" do
test "it works as expected with noop policy" do
+ clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
+
expected = %{
mrf_policies: ["NoOpPolicy"],
exclusions: false
assert activity.data["to"] == [user.ap_id]
refute "#{user.ap_id}/followers" in User.following(service_actor)
end
+
+ test "force unfollow when target service is dead" do
+ user = insert(:user)
+ user_ap_id = user.ap_id
+ user_id = user.id
+
+ Tesla.Mock.mock(fn %{method: :get, url: ^user_ap_id} ->
+ %Tesla.Env{status: 404}
+ end)
+
+ service_actor = Relay.get_actor()
+ CommonAPI.follow(service_actor, user)
+ assert "#{user.ap_id}/followers" in User.following(service_actor)
+
+ assert Pleroma.Repo.get_by(
+ Pleroma.FollowingRelationship,
+ follower_id: service_actor.id,
+ following_id: user_id
+ )
+
+ Pleroma.Repo.delete(user)
+ Cachex.clear(:user_cache)
+
+ assert {:ok, %Activity{} = activity} = Relay.unfollow(user_ap_id, %{force: true})
+
+ assert refresh_record(service_actor).following_count == 0
+
+ refute Pleroma.Repo.get_by(
+ Pleroma.FollowingRelationship,
+ follower_id: service_actor.id,
+ following_id: user_id
+ )
+
+ assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
+ assert user.ap_id in activity.recipients
+ assert activity.data["type"] == "Undo"
+ assert activity.data["actor"] == service_actor.ap_id
+ assert activity.data["to"] == [user_ap_id]
+ refute "#{user.ap_id}/followers" in User.following(service_actor)
+ end
end
describe "publish/1" do
}
end
- test "returns an error if received a second time" do
+ test "returns same activity if received a second time" do
data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
- assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data)
+ assert {:ok, ^activity} = Transmogrifier.handle_incoming(data)
end
test "accepts a Question with no content" do