Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
authorsadposter <hannah+pleroma@coffee-and-dreams.uk>
Mon, 25 Nov 2019 11:37:49 +0000 (11:37 +0000)
committersadposter <hannah+pleroma@coffee-and-dreams.uk>
Mon, 25 Nov 2019 11:37:49 +0000 (11:37 +0000)
33 files changed:
.dockerignore
CHANGELOG.md
config/config.exs
config/prod.exs
config/releases.exs
config/test.exs
docs/API/admin_api.md
docs/administration/CLI_tasks/user.md
docs/configuration/cheatsheet.md
lib/mix/pleroma.ex
lib/mix/tasks/pleroma/user.ex
lib/pleroma/application.ex
lib/pleroma/following_relationship.ex
lib/pleroma/moderation_log.ex
lib/pleroma/object.ex
lib/pleroma/user.ex
lib/pleroma/user/search.ex
lib/pleroma/web/activity_pub/relay.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/account_view.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/router.ex
mix.exs
priv/repo/migrations/20191104133100_set_visible_service_actors.exs [new file with mode: 0644]
rel/env.sh.eex
test/following_relationship_test.exs [new file with mode: 0644]
test/http/request_builder_test.exs
test/user_search_test.exs
test/user_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs
test/web/mastodon_api/controllers/filter_controller_test.exs
test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs

index c5ef89b86d606de430f0e79df4d651ac8ee95f83..6b1879e62978d641b3b9512414ff19b35affd5f0 100644 (file)
@@ -5,7 +5,6 @@ CC-BY-SA-4.0
 COPYING
 *file
 elixir_buildpack.config
-docs/
 test/
 
 # Required to get version
index 6bd835a3da592d27b776ccb772ca484454d967b7..8db8039b842efdbd29c772aec076bab6e8aa1029 100644 (file)
@@ -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.
 <details>
@@ -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
 </details>
 
 ### Fixed
index bf2b3f6e28379208e57eae5e1bdc367402feec0e..b60ffef7dc9c7ad8a3690caf0fd7896d48b75613 100644 (file)
@@ -180,7 +180,8 @@ config :pleroma, Pleroma.Web.Endpoint,
 
 # 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 :tesla, adapter: Tesla.Adapter.Hackney
 config :pleroma, :http,
   proxy_url: nil,
   send_user_agent: true,
+  user_agent: :default,
   adapter: [
     ssl_options: [
       # Workaround for remote server certificate chain issues
index 9c205cbd2f02cde66445e94235a807413dcf70e0..301d2b9cb9c361fc267100d383160a91b2f588ac 100644 (file)
@@ -20,7 +20,7 @@ config :pleroma, Pleroma.Web.Endpoint,
 config :phoenix, serve_endpoints: true
 
 # Do not print debug messages in production
-config :logger, level: :warn
+config :logger, :console, level: :warn
 
 # ## SSL Support
 #
index 36c49367384be2eb63b9089ffdfbfd6ae1f1b278..98c5ceccd54a5501dfefcd8a845d148aedca390f 100644 (file)
@@ -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"
index da2778aa749ef6b32052f0bce6ae06f91b72eb47..9b737d4d7e3960588d74819f8e2fa423d3077d72 100644 (file)
@@ -15,7 +15,9 @@ config :pleroma, Pleroma.Captcha,
   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: []
 
index f64983a9041a9e479637737a4533e509f09a0331..2cac317def2df0790ae1ffd04db05ec0fd455c66 100644 (file)
@@ -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
index cf120f2c9fb91fc72f727ab2fb9183a2c25cf8b4..96b2d9e6a3326fafcd5bf54e0cceeaa88a288bf0 100644 (file)
@@ -15,6 +15,11 @@ $PREFIX new <nickname> <email> [<options>]
 - `--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 [<options>]
index 07d9a1d451765274f18633ff821fc31f4efab4c9..dc2f5522990d7a27dcab18d4f8d9ea1eaf6e52bf 100644 (file)
@@ -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.
 
index faeb30e1dc378da65b601ce01f4deb537056a2e6..73a076a531a6ee7fc9963488f6a6ff5804029340 100644 (file)
@@ -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
 
index 17deda63a02861c18c76e3d4b3ed078c0d563c54..1c37e579e0d729a1d35c5ae69aeaba7b2ef55f31 100644 (file)
@@ -393,6 +393,24 @@ defmodule Mix.Tasks.Pleroma.User 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
index 2b6a55f98fda23c120db6e170374a633fa92b1c3..9dbd1e26b699130c8b507d794de8191bcef530f7 100644 (file)
@@ -17,8 +17,14 @@ defmodule Pleroma.Application do
   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
index 2ffac17ee135667774c0cd64f110d6425fc81706..3aff9fb76ba6dee1b1f47734f66028918371911f 100644 (file)
@@ -101,7 +101,7 @@ defmodule Pleroma.FollowingRelationship 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]
index ffa5dc25db48a9bfa576a32d8658cf4f46169e27..706f089dc044f02c07d10ac2b621db30629bbf81 100644 (file)
@@ -624,7 +624,31 @@ defmodule Pleroma.ModerationLog do
           "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
index d9b41d710cebb6296a3b2d4cb41521feb2c27030..cde0eddd9e8d72267f4522d5c825d87a50db1a23 100644 (file)
@@ -63,7 +63,7 @@ defmodule Pleroma.Object 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))}")
index fcb1d5143942bc7738aff01ca8ecf70ca7166181..3010fe87fd46976784a3cd1e24229fc344869c4a 100644 (file)
@@ -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 @@ defmodule Pleroma.User do
 
   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 @@ defmodule Pleroma.User do
   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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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
index 09664db76b7393a144c9d54972ba3a884eabb7bf..b1bb9d4da0aa65ad14c14a1fe131f88380ecdc19 100644 (file)
@@ -45,6 +45,7 @@ defmodule Pleroma.User.Search 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 @@ defmodule Pleroma.User.Search 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))
index fc2619680f8e00d96606e7918b1291d65519dbe5..99a804568ccf0830529d55d1c17e916fce146c5f 100644 (file)
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.Relay do
       relay_ap_id()
       |> User.get_or_create_service_actor_by_ap_id()
 
-    {:ok, actor} = User.set_invisible(actor, true)
     actor
   end
 
index 8c1318d1bb2821f40c2797eedbe55e019637490a..c46ce76da304bd97dfbd3e7b8eac8740e42df869 100644 (file)
@@ -335,7 +335,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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)
index 6aa7257cead895b166043f343f10f26d278fbb9a..d9dba5c5181693044ea8df1bb9f70900d8bdca40 100644 (file)
@@ -36,7 +36,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
       "deactivated" => user.deactivated,
       "local" => user.local,
       "roles" => User.roles(user),
-      "tags" => user.tags || []
+      "tags" => user.tags || [],
+      "confirmation_pending" => user.confirmation_pending
     }
   end
 
index 5b01b964b8a52fb85aa05a60de590df627a9f87a..5f2544640427afb24b91f8fe81e4e587789fd907 100644 (file)
@@ -238,7 +238,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   @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")
index 129da422c2b3a4a13b9aee773859f2491910fe39..0b54f4ab7b665f1abb67c03f809a549853fa884c 100644 (file)
@@ -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 81ce4f25cef74d619776b7a7b6b18a569ff014f8..60c7fe3f61efedbe47b67ab63427dbe88df04032 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -194,27 +194,21 @@ defmodule Pleroma.Mixfile 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 (file)
index 0000000..6290709
--- /dev/null
@@ -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
index a4ce2529531778b8be50d53334a31a90816e728e..e1b87102d15207295dd9ed6a7199725b4d5b43e0 100644 (file)
@@ -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 (file)
index 0000000..93c0798
--- /dev/null
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# 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
index 170ca916f6448ae75450295cda14df38e502c346..80ef25d7b1051f6651e6750054adbe4e3a6bc284 100644 (file)
@@ -16,11 +16,21 @@ defmodule Pleroma.HTTP.RequestBuilderTest 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
index 721af1e5bb9bbecc1d677f3309dceb8126d1d1dd..98841dbbda2c231f586a3b17896ca08815f2b737 100644 (file)
@@ -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
index 8fdb6b25fc2f2bba9916d8bfdc32c4924fb00bf4..e6302b5255c87d04dcb6ce12f7d83fac51193d4a 100644 (file)
@@ -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 @@ defmodule Pleroma.UserTest 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 @@ defmodule Pleroma.UserTest 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)
index 3a4c4d65c9e118bc9426798f7041354b4c2e8606..9b4f359dc8bbf4fc60dfef468de24e3a46f348ec 100644 (file)
@@ -225,7 +225,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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
index 8fc2d93005f5cda6ec224ff92eaf078771b19581..585cb8a9e95d54b00572a949530d61e8416b9b25 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       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
index 5d5b56c8ebef16adedefb4af1a4ce9f719bafed6..550689788a377285559347240fa4f84751ca608e 100644 (file)
@@ -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
 
index 9ad6a4fa75cb17b3a09d6300c6414b5c67dcf2cd..ae5fee2bcd3b38b8b86ef52b7f30c6385f6e4d6a 100644 (file)
@@ -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