diff --git a/.dockerignore b/.dockerignore index c5ef89b86..6b1879e62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,6 @@ CC-BY-SA-4.0 COPYING *file elixir_buildpack.config -docs/ test/ # Required to get version diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd835a3d..8db8039b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Authentication: Added rate limit for password-authorized actions / login existence checks - Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app. - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`) +- Mix task to list all users (`mix pleroma.user list`) - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache). - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
@@ -69,6 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations` - Configuration: `feed` option for user atom feed. - Pleroma API: Add Emoji reactions +- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
### Fixed diff --git a/config/config.exs b/config/config.exs index bf2b3f6e2..b60ffef7d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -180,7 +180,8 @@ # Configures Elixir's Logger config :logger, :console, - format: "$time $metadata[$level] $message\n", + level: :debug, + format: "\n$time $metadata[$level] $message\n", metadata: [:request_id] config :logger, :ex_syslogger, @@ -208,6 +209,7 @@ config :pleroma, :http, proxy_url: nil, send_user_agent: true, + user_agent: :default, adapter: [ ssl_options: [ # Workaround for remote server certificate chain issues diff --git a/config/prod.exs b/config/prod.exs index 9c205cbd2..301d2b9cb 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -20,7 +20,7 @@ config :phoenix, serve_endpoints: true # Do not print debug messages in production -config :logger, level: :warn +config :logger, :console, level: :warn # ## SSL Support # diff --git a/config/releases.exs b/config/releases.exs index 36c493673..98c5ceccd 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -1,6 +1,6 @@ import Config -config :pleroma, :instance, static: "/var/lib/pleroma/static" +config :pleroma, :instance, static_dir: "/var/lib/pleroma/static" config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads" config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs" diff --git a/config/test.exs b/config/test.exs index da2778aa7..9b737d4d7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -15,7 +15,9 @@ method: Pleroma.Captcha.Mock # Print only warnings and errors during test -config :logger, level: :warn +config :logger, :console, + level: :warn, + format: "\n[$level] $message\n" config :pleroma, :auth, oauth_consumer_strategies: [] diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index f64983a90..2cac317de 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -870,3 +870,19 @@ Compile time settings (need instance reboot): - Authentication: required - Params: None - Response: JSON, "ok" and 200 status + +## `PATCH /api/pleroma/admin/users/confirm_email` + +### Confirm users' emails + +- Params: + - `nicknames` +- Response: Array of user nicknames + +## `PATCH /api/pleroma/admin/users/resend_confirmation_email` + +### Resend confirmation email + +- Params: + - `nicknames` +- Response: Array of user nicknames diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md index cf120f2c9..96b2d9e6a 100644 --- a/docs/administration/CLI_tasks/user.md +++ b/docs/administration/CLI_tasks/user.md @@ -15,6 +15,11 @@ $PREFIX new [] - `--admin`/`--no-admin` - whether the user should be an admin - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions +## List local users +```sh +$PREFIX list +``` + ## Generate an invite link ```sh $PREFIX invite [] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 07d9a1d45..dc2f55229 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -348,7 +348,17 @@ Available caches: * `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration). * `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds). -## :hackney_pools +## HTTP client + +### :http + +* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`) +* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`) +* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default` +* `adapter`: array of hackney options + + +### :hackney_pools Advanced. Tweaks Hackney (http client) connections pools. diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index faeb30e1d..73a076a53 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -6,6 +6,11 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) + + if Pleroma.Config.get(:env) != :test do + Application.put_env(:logger, :console, level: :debug) + end + {:ok, _} = Application.ensure_all_started(:pleroma) end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 17deda63a..1c37e579e 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -393,6 +393,24 @@ def run(["sign_out", nickname]) do end end + def run(["list"]) do + start_pleroma() + + Pleroma.User.Query.build(%{local: true}) + |> Pleroma.RepoStreamer.chunk_stream(500) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> + shell_info( + "#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{ + user.info.locked + }, deactivated: #{user.info.deactivated}" + ) + end) + end) + |> Stream.run() + end + defp set_moderator(user, value) do {:ok, user} = user diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 2b6a55f98..9dbd1e26b 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -17,8 +17,14 @@ def named_version, do: @name <> " " <> @version def repository, do: @repository def user_agent do - info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>" - named_version() <> "; " <> info + case Pleroma.Config.get([:http, :user_agent], :default) do + :default -> + info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>" + named_version() <> "; " <> info + + custom -> + custom + end end # See http://elixir-lang.org/docs/stable/elixir/Application.html diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 2ffac17ee..3aff9fb76 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -101,7 +101,7 @@ def following(%User{} = user) do |> select([r, u], u.follower_address) |> Repo.all() - if not user.local or user.nickname in [nil, "internal.fetch"] do + if not user.local or user.invisible do following else [user.follower_address | following] diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index ffa5dc25d..706f089dc 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -624,7 +624,31 @@ def get_log_entry_message(%ModerationLog{ "subject" => subjects } }) do - "@#{actor_nickname} force password reset for users: #{users_to_nicknames_string(subjects)}" + "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}" + end + + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "confirm_email", + "subject" => subjects + } + }) do + "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}" + end + + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "resend_confirmation_email", + "subject" => subjects + } + }) do + "@#{actor_nickname} re-sent confirmation email for users: #{ + users_to_nicknames_string(subjects) + }" end defp nicknames_to_string(nicknames) do diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d9b41d710..cde0eddd9 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -63,7 +63,7 @@ def get_by_ap_id(ap_id) do end defp warn_on_no_object_preloaded(ap_id) do - "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object" + "Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object" |> Logger.debug() Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index fcb1d5143..3010fe87f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -67,8 +67,7 @@ defmodule Pleroma.User do field(:source_data, :map, default: %{}) field(:note_count, :integer, default: 0) field(:follower_count, :integer, default: 0) - # Should be filled in only for remote users - field(:following_count, :integer, default: nil) + field(:following_count, :integer, default: 0) field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) @@ -134,6 +133,8 @@ def auth_active?(%User{}), do: true def visible_for?(user, for_user \\ nil) + def visible_for?(%User{invisible: true}, _), do: false + def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true def visible_for?(%User{} = user, for_user) do @@ -177,19 +178,17 @@ def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa def ap_following(%User{} = user), do: "#{ap_id(user)}/following" def user_info(%User{} = user, args \\ %{}) do - following_count = - Map.get(args, :following_count, user.following_count || following_count(user)) - + following_count = Map.get(args, :following_count, user.following_count) follower_count = Map.get(args, :follower_count, user.follower_count) %{ note_count: user.note_count, locked: user.locked, confirmation_pending: user.confirmation_pending, - default_scope: user.default_scope + default_scope: user.default_scope, + follower_count: follower_count, + following_count: following_count } - |> Map.put(:following_count, following_count) - |> Map.put(:follower_count, follower_count) end def follow_state(%User{} = user, %User{} = target) do @@ -492,6 +491,10 @@ def try_send_confirmation_email(%User{} = user) do end end + def try_send_confirmation_email(users) do + Enum.each(users, &try_send_confirmation_email/1) + end + def needs_update?(%User{local: true}), do: false def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true @@ -522,14 +525,9 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do @doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} def follow_all(follower, followeds) do - followeds = - Enum.reject(followeds, fn followed -> - blocks?(follower, followed) || blocks?(followed, follower) - end) - - Enum.each(followeds, &follow(follower, &1, "accept")) - - Enum.each(followeds, &update_follower_count/1) + followeds + |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) + |> Enum.each(&follow(follower, &1, "accept")) set_cache(follower) end @@ -549,11 +547,11 @@ def follow(%User{} = follower, %User{} = followed, state \\ "accept") do true -> FollowingRelationship.follow(follower, followed, state) - follower = maybe_update_following_count(follower) - {:ok, _} = update_follower_count(followed) - set_cache(follower) + follower + |> update_following_count() + |> set_cache() end end @@ -561,11 +559,12 @@ def unfollow(%User{} = follower, %User{} = followed) do if following?(follower, followed) and follower.ap_id != followed.ap_id do FollowingRelationship.unfollow(follower, followed) - follower = maybe_update_following_count(follower) - {:ok, followed} = update_follower_count(followed) - set_cache(follower) + {:ok, follower} = + follower + |> update_following_count() + |> set_cache() {:ok, follower, Utils.fetch_latest_follow(follower, followed)} else @@ -895,8 +894,8 @@ def update_follower_count(%User{} = user) do end end - @spec maybe_update_following_count(User.t()) :: User.t() - def maybe_update_following_count(%User{local: false} = user) do + @spec update_following_count(User.t()) :: User.t() + def update_following_count(%User{local: false} = user) do if Pleroma.Config.get([:instance, :external_user_synchronization]) do maybe_fetch_follow_information(user) else @@ -904,7 +903,13 @@ def maybe_update_following_count(%User{local: false} = user) do end end - def maybe_update_following_count(user), do: user + def update_following_count(%User{local: true} = user) do + following_count = FollowingRelationship.following_count(user) + + user + |> follow_information_changeset(%{following_count: following_count}) + |> Repo.update!() + end def set_unread_conversation_count(%User{local: true} = user) do unread_query = Participation.unread_conversation_count_for_user(user) @@ -1097,7 +1102,12 @@ def deactivate(users, status) when is_list(users) do def deactivate(%User{} = user, status) do with {:ok, user} <- set_activation_status(user, status) do - Enum.each(get_followers(user), &invalidate_cache/1) + user + |> get_followers() + |> Enum.filter(& &1.local) + |> Enum.each(fn follower -> + follower |> update_following_count() |> set_cache() + end) # Only update local user counts, remote will be update during the next pull. user @@ -1317,22 +1327,23 @@ def get_or_fetch_by_ap_id(ap_id) do end end - @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing." + @doc """ + Creates an internal service actor by URI if missing. + Optionally takes nickname for addressing. + """ def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do - with %User{} = user <- get_cached_by_ap_id(uri) do - user - else - _ -> - {:ok, user} = - %User{} - |> cast(%{}, [:ap_id, :nickname, :local]) - |> put_change(:ap_id, uri) - |> put_change(:nickname, nickname) - |> put_change(:local, true) - |> put_change(:follower_address, uri <> "/followers") - |> Repo.insert() + with user when is_nil(user) <- get_cached_by_ap_id(uri) do + {:ok, user} = + %User{ + invisible: true, + local: true, + ap_id: uri, + nickname: nickname, + follower_address: uri <> "/followers" + } + |> Repo.insert() - user + user end end @@ -1575,6 +1586,11 @@ def toggle_confirmation(%User{} = user) do |> update_and_set_cache() end + @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}] + def toggle_confirmation(users) do + Enum.map(users, &toggle_confirmation/1) + end + def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do mascot end diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 09664db76..b1bb9d4da 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -45,6 +45,7 @@ defp search_query(query_string, for_user, following) do for_user |> base_query(following) |> filter_blocked_user(for_user) + |> filter_invisible_users() |> filter_blocked_domains(for_user) |> fts_search(query_string) |> trigram_rank(query_string) @@ -98,6 +99,10 @@ defp trigram_rank(query, query_string) do defp base_query(_user, false), do: User defp base_query(user, true), do: User.get_followers_query(user) + defp filter_invisible_users(query) do + from(q in query, where: q.invisible == false) + end + defp filter_blocked_user(query, %User{blocks: blocks}) when length(blocks) > 0 do from(q in query, where: not (q.ap_id in ^blocks)) diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index fc2619680..99a804568 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -14,7 +14,6 @@ def get_actor do relay_ap_id() |> User.get_or_create_service_actor_by_ap_id() - {:ok, actor} = User.set_invisible(actor, true) actor end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 8c1318d1b..c46ce76da 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -335,7 +335,7 @@ def list_users(conn, params) do } with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), - {:ok, users, count} <- filter_relay_user(users, count), + {:ok, users, count} <- filter_service_users(users, count), do: conn |> json( @@ -347,15 +347,16 @@ def list_users(conn, params) do ) end - defp filter_relay_user(users, count) do - filtered_users = Enum.reject(users, &relay_user?/1) - count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count + defp filter_service_users(users, count) do + filtered_users = Enum.reject(users, &service_user?/1) + count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count {:ok, filtered_users, count} end - defp relay_user?(user) do - user.ap_id == Relay.relay_ap_id() + defp service_user?(user) do + String.match?(user.ap_id, ~r/.*\/relay$/) or + String.match?(user.ap_id, ~r/.*\/internal\/fetch$/) end @filters ~w(local external active deactivated is_admin is_moderator) @@ -799,6 +800,34 @@ def reload_emoji(conn, _params) do conn |> json("ok") end + def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + User.toggle_confirmation(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "confirm_email" + }) + + conn |> json("") + end + + def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + User.try_send_confirmation_email(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "resend_confirmation_email" + }) + + conn |> json("") + end + def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 6aa7257ce..d9dba5c51 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -36,7 +36,8 @@ def render("show.json", %{user: user}) do "deactivated" => user.deactivated, "local" => user.local, "roles" => User.roles(user), - "tags" => user.tags || [] + "tags" => user.tags || [], + "confirmation_pending" => user.confirmation_pending } end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 5b01b964b..5f2544640 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -238,7 +238,7 @@ def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) @doc "GET /api/v1/accounts/:id" def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), - true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do + true <- User.visible_for?(user, for_user) do render(conn, "show.json", user: user, for: for_user) else _e -> render_error(conn, :not_found, "Can't find user") diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 129da422c..0b54f4ab7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -178,6 +178,9 @@ defmodule Pleroma.Web.Router do get("/users/:nickname", AdminAPIController, :user_show) get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) + patch("/users/confirm_email", AdminAPIController, :confirm_email) + patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) + get("/reports", AdminAPIController, :list_reports) get("/grouped_reports", AdminAPIController, :list_grouped_reports) get("/reports/:id", AdminAPIController, :report_show) diff --git a/mix.exs b/mix.exs index 81ce4f25c..60c7fe3f6 100644 --- a/mix.exs +++ b/mix.exs @@ -194,27 +194,21 @@ defp version(version) do identifier_filter = ~r/[^0-9a-z\-]+/i # Pre-release version, denoted from patch version with a hyphen - {git_tag, git_pre_release} = + git_pre_release = with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true), - tag = String.trim(tag), - {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]), - describe = String.trim(describe), - ahead <- String.replace(describe, tag, ""), - ahead <- String.trim_leading(ahead, "-") do - {String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))} + {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]) do + describe + |> String.trim() + |> String.replace(String.trim(tag), "") + |> String.trim_leading("-") + |> String.trim() else _ -> {commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"]) - {nil, "0-g" <> String.trim(commit_hash)} + "0-g" <> String.trim(commit_hash) end - if git_tag && version != git_tag do - Mix.shell().error( - "Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}" - ) - end - # Branch name as pre-release version component, denoted with a dot branch_name = with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]), diff --git a/priv/repo/migrations/20191104133100_set_visible_service_actors.exs b/priv/repo/migrations/20191104133100_set_visible_service_actors.exs new file mode 100644 index 000000000..62907093c --- /dev/null +++ b/priv/repo/migrations/20191104133100_set_visible_service_actors.exs @@ -0,0 +1,22 @@ +defmodule Pleroma.Repo.Migrations.SetVisibleServiceActors do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Repo + + def up do + user_nicknames = ["relay", "internal.fetch"] + + from( + u in "users", + where: u.nickname in ^user_nicknames, + update: [ + set: [invisible: true] + ] + ) + |> Repo.update_all([]) + end + + def down do + :ok + end +end diff --git a/rel/env.sh.eex b/rel/env.sh.eex index a4ce25295..e1b87102d 100644 --- a/rel/env.sh.eex +++ b/rel/env.sh.eex @@ -8,5 +8,5 @@ # fi # Set the release to work across nodes -export RELEASE_DISTRIBUTION=name -export RELEASE_NODE=<%= @release.name %>@127.0.0.1 +export RELEASE_DISTRIBUTION="${RELEASE_DISTRIBUTION:-name}" +export RELEASE_NODE="${RELEASE_NODE:-<%= @release.name %>@127.0.0.1}" diff --git a/test/following_relationship_test.exs b/test/following_relationship_test.exs new file mode 100644 index 000000000..93c079814 --- /dev/null +++ b/test/following_relationship_test.exs @@ -0,0 +1,47 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.FollowingRelationshipTest do + use Pleroma.DataCase + + alias Pleroma.FollowingRelationship + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.ActivityPub.Relay + + import Pleroma.Factory + + describe "following/1" do + test "returns following addresses without internal.fetch" do + user = insert(:user) + fetch_actor = InternalFetchActor.get_actor() + FollowingRelationship.follow(fetch_actor, user, "accept") + assert FollowingRelationship.following(fetch_actor) == [user.follower_address] + end + + test "returns following addresses without relay" do + user = insert(:user) + relay_actor = Relay.get_actor() + FollowingRelationship.follow(relay_actor, user, "accept") + assert FollowingRelationship.following(relay_actor) == [user.follower_address] + end + + test "returns following addresses without remote user" do + user = insert(:user) + actor = insert(:user, local: false) + FollowingRelationship.follow(actor, user, "accept") + assert FollowingRelationship.following(actor) == [user.follower_address] + end + + test "returns following addresses with local user" do + user = insert(:user) + actor = insert(:user, local: true) + FollowingRelationship.follow(actor, user, "accept") + + assert FollowingRelationship.following(actor) == [ + actor.follower_address, + user.follower_address + ] + end + end +end diff --git a/test/http/request_builder_test.exs b/test/http/request_builder_test.exs index 170ca916f..80ef25d7b 100644 --- a/test/http/request_builder_test.exs +++ b/test/http/request_builder_test.exs @@ -16,11 +16,21 @@ test "don't send pleroma user agent" do test "send pleroma user agent" do Pleroma.Config.put([:http, :send_user_agent], true) + Pleroma.Config.put([:http, :user_agent], :default) assert RequestBuilder.headers(%{}, []) == %{ headers: [{"User-Agent", Pleroma.Application.user_agent()}] } end + + test "send custom user agent" do + Pleroma.Config.put([:http, :send_user_agent], true) + Pleroma.Config.put([:http, :user_agent], "totally-not-pleroma") + + assert RequestBuilder.headers(%{}, []) == %{ + headers: [{"User-Agent", "totally-not-pleroma"}] + } + end end describe "add_optional_params/3" do diff --git a/test/user_search_test.exs b/test/user_search_test.exs index 721af1e5b..98841dbbd 100644 --- a/test/user_search_test.exs +++ b/test/user_search_test.exs @@ -15,6 +15,14 @@ defmodule Pleroma.UserSearchTest do end describe "User.search" do + test "excluded invisible users from results" do + user = insert(:user, %{nickname: "john t1000"}) + insert(:user, %{invisible: true, nickname: "john t800"}) + + [found_user] = User.search("john") + assert found_user.id == user.id + end + test "accepts limit parameter" do Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"})) assert length(User.search("john", limit: 3)) == 3 diff --git a/test/user_test.exs b/test/user_test.exs index 8fdb6b25f..e6302b525 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -25,6 +25,25 @@ defmodule Pleroma.UserTest do clear_config([:instance, :account_activation_required]) + describe "service actors" do + test "returns invisible actor" do + uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test" + followers_uri = "#{uri}/followers" + user = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test") + + assert %User{ + nickname: "internal.fetch-test", + invisible: true, + local: true, + ap_id: ^uri, + follower_address: ^followers_uri + } = user + + user2 = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test") + assert user.id == user2.id + end + end + describe "when tags are nil" do test "tagging a user" do user = insert(:user, %{tags: nil}) @@ -148,9 +167,10 @@ test "follow takes a user and another user" do {:ok, user} = User.follow(user, followed) user = User.get_cached_by_id(user.id) - followed = User.get_cached_by_ap_id(followed.ap_id) + assert followed.follower_count == 1 + assert user.following_count == 1 assert User.ap_followers(followed) in User.following(user) end @@ -952,12 +972,14 @@ test "hide a user from friends" do user2 = insert(:user) {:ok, user2} = User.follow(user2, user) + assert user2.following_count == 1 assert User.following_count(user2) == 1 {:ok, _user} = User.deactivate(user) info = User.get_cached_user_info(user2) + assert refresh_record(user2).following_count == 0 assert info.following_count == 0 assert User.following_count(user2) == 0 assert [] = User.get_friends(user2) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 3a4c4d65c..9b4f359dc 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -225,7 +225,8 @@ test "Show", %{conn: conn} do "roles" => %{"admin" => false, "moderator" => false}, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } assert expected == json_response(conn, 200) @@ -634,7 +635,8 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "local" => true, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ "deactivated" => user.deactivated, @@ -644,7 +646,8 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "local" => false, "tags" => ["foo", "bar"], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -685,7 +688,8 @@ test "regular search", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -709,7 +713,8 @@ test "search by domain", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -733,7 +738,8 @@ test "search by full nickname", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -757,7 +763,8 @@ test "search by display name", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -781,7 +788,8 @@ test "search by email", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -805,7 +813,8 @@ test "regular search with page size", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -824,7 +833,8 @@ test "regular search with page size", %{conn: conn} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user2) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user2.name || user2.nickname) + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false } ] } @@ -853,7 +863,8 @@ test "only local users" do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -880,7 +891,8 @@ test "only local users with no query", %{admin: old_admin} do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false }, %{ "deactivated" => admin.deactivated, @@ -890,7 +902,8 @@ test "only local users with no query", %{admin: old_admin} do "local" => true, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -900,7 +913,8 @@ test "only local users with no query", %{admin: old_admin} do "roles" => %{"admin" => true, "moderator" => false}, "tags" => [], "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname) + "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -929,7 +943,8 @@ test "load only admins", %{conn: conn, admin: admin} do "local" => admin.local, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -939,7 +954,8 @@ test "load only admins", %{conn: conn, admin: admin} do "local" => second_admin.local, "tags" => [], "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname) + "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -970,7 +986,8 @@ test "load only moderators", %{conn: conn} do "local" => moderator.local, "tags" => [], "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(moderator.name || moderator.nickname) + "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), + "confirmation_pending" => false } ] } @@ -994,7 +1011,8 @@ test "load users with tags list", %{conn: conn} do "local" => user1.local, "tags" => ["first"], "avatar" => User.avatar_url(user1) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user1.name || user1.nickname) + "display_name" => HTML.strip_tags(user1.name || user1.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -1004,7 +1022,8 @@ test "load users with tags list", %{conn: conn} do "local" => user2.local, "tags" => ["second"], "avatar" => User.avatar_url(user2) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user2.name || user2.nickname) + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -1040,7 +1059,8 @@ test "it works with multiple filters" do "local" => user.local, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -1066,7 +1086,8 @@ test "it omits relay user", %{admin: admin} do "local" => true, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false } ] } @@ -1135,7 +1156,8 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } log_entry = Repo.one(ModerationLog) @@ -2839,6 +2861,68 @@ test "DELETE /relay", %{admin: admin} do "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin" end end + + describe "PATCH /confirm_email" do + setup %{conn: conn} do + admin = insert(:user, is_admin: true) + + %{conn: assign(conn, :user, admin), admin: admin} + end + + test "it confirms emails of two users", %{admin: admin} do + [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + + assert first_user.confirmation_pending == true + assert second_user.confirmation_pending == true + + build_conn() + |> assign(:user, admin) + |> patch("/api/pleroma/admin/users/confirm_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + assert first_user.confirmation_pending == true + assert second_user.confirmation_pending == true + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{ + second_user.nickname + }" + end + end + + describe "PATCH /resend_confirmation_email" do + setup %{conn: conn} do + admin = insert(:user, is_admin: true) + + %{conn: assign(conn, :user, admin), admin: admin} + end + + test "it resend emails for two users", %{admin: admin} do + [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + + build_conn() + |> assign(:user, admin) + |> patch("/api/pleroma/admin/users/resend_confirmation_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{ + second_user.nickname + }" + end + end end # Needed for testing diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 8fc2d9300..585cb8a9e 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.Token @@ -118,6 +119,28 @@ test "accounts fetches correct account for nicknames beginning with numbers", %{ refute acc_one == acc_two assert acc_two == acc_three end + + test "returns 404 when user is invisible", %{conn: conn} do + user = insert(:user, %{invisible: true}) + + resp = + conn + |> get("/api/v1/accounts/#{user.nickname}") + |> json_response(404) + + assert %{"error" => "Can't find user"} = resp + end + + test "returns 404 for internal.fetch actor", %{conn: conn} do + %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + + resp = + conn + |> get("/api/v1/accounts/internal.fetch") + |> json_response(404) + + assert %{"error" => "Can't find user"} = resp + end end describe "user timelines" do diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 5d5b56c8e..550689788 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do - use Pleroma.Web.ConnCase, async: true + use Pleroma.Web.ConnCase alias Pleroma.Web.MastodonAPI.FilterView diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index 9ad6a4fa7..ae5fee2bc 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do - use Pleroma.Web.ConnCase, async: true + use Pleroma.Web.ConnCase alias Pleroma.Repo alias Pleroma.ScheduledActivity