From a62f17da17fbebf817796b0278060abe2829c903 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Jul 2020 19:11:30 -0500 Subject: [PATCH 01/29] Add `approval_pending` field to User --- lib/pleroma/user.ex | 2 ++ .../20200712234852_add_approval_pending_to_users.exs | 9 +++++++++ test/user_test.exs | 5 +++++ 3 files changed, 16 insertions(+) create mode 100644 priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b9989f901..25c63fc44 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -106,6 +106,7 @@ defmodule Pleroma.User do field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) + field(:approval_pending, :boolean, default: false) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) @@ -262,6 +263,7 @@ def binary_id(%User{} = user), do: binary_id(user.id) @spec account_status(User.t()) :: account_status() def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending + def account_status(%User{approval_pending: true}), do: :approval_pending def account_status(%User{confirmation_pending: true}) do if Config.get([:instance, :account_activation_required]) do diff --git a/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs b/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs new file mode 100644 index 000000000..f7eb8179b --- /dev/null +++ b/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddApprovalPendingToUsers do + use Ecto.Migration + + def change do + alter table(:users) do + add(:approval_pending, :boolean) + end + end +end diff --git a/test/user_test.exs b/test/user_test.exs index 9788e09d9..040f532fe 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1342,6 +1342,11 @@ test "returns :deactivated for deactivated user" do user = insert(:user, local: true, confirmation_pending: false, deactivated: true) assert User.account_status(user) == :deactivated end + + test "returns :approval_pending for unapproved user" do + user = insert(:user, local: true, confirmation_pending: false, approval_pending: true) + assert User.account_status(user) == :approval_pending + end end describe "superuser?/1" do From 51ab8d0128970dd7458e93578acb36c20b1c185c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Jul 2020 20:14:57 -0500 Subject: [PATCH 02/29] Add `account_approval_required` instance setting --- config/config.exs | 1 + config/description.exs | 5 +++++ docs/configuration/cheatsheet.md | 1 + lib/pleroma/web/mastodon_api/views/instance_view.ex | 1 + .../mastodon_api/controllers/instance_controller_test.exs | 1 + 5 files changed, 9 insertions(+) diff --git a/config/config.exs b/config/config.exs index 6fc84efc2..791740663 100644 --- a/config/config.exs +++ b/config/config.exs @@ -205,6 +205,7 @@ registrations_open: true, invites_enabled: false, account_activation_required: false, + account_approval_required: false, federating: true, federation_incoming_replies_max_depth: 100, federation_reachability_timeout_days: 7, diff --git a/config/description.exs b/config/description.exs index b0cc8d527..e57379dee 100644 --- a/config/description.exs +++ b/config/description.exs @@ -665,6 +665,11 @@ type: :boolean, description: "Require users to confirm their emails before signing in" }, + %{ + key: :account_approval_required, + type: :boolean, + description: "Require users to be manually approved by an admin before signing in" + }, %{ key: :federating, type: :boolean, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index f796330f1..94389152e 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -33,6 +33,7 @@ To add configuration to your config file, you can copy it from the base config. * `registrations_open`: Enable registrations for anyone, invitations can be enabled when false. * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). * `account_activation_required`: Require users to confirm their emails before signing in. +* `account_approval_required`: Require users to be manually approved by an admin before signing in. * `federating`: Enable federation with other instances. * `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes. * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 5deb0d7ed..243067a73 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -39,6 +39,7 @@ def render("show.json", _) do pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), + account_approval_required: Keyword.get(instance, :account_approval_required), features: features(), federation: federation(), fields_limits: fields_limits() diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index cc880d82c..8a4183283 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -38,6 +38,7 @@ test "get instance information", %{conn: conn} do } = result assert result["pleroma"]["metadata"]["account_activation_required"] != nil + assert result["pleroma"]["metadata"]["account_approval_required"] != nil assert result["pleroma"]["metadata"]["features"] assert result["pleroma"]["metadata"]["federation"] assert result["pleroma"]["metadata"]["fields_limits"] From e4e557781877c7c3e4f6197cc52963025485dbb3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Jul 2020 20:15:27 -0500 Subject: [PATCH 03/29] Prevent unapproved users from logging in --- lib/pleroma/web/oauth/oauth_controller.ex | 10 ++++++++ test/web/oauth/oauth_controller_test.exs | 30 ++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 7683589cf..61fe81d33 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -337,6 +337,16 @@ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirm ) end + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do + render_error( + conn, + :forbidden, + "Your account is awaiting approval.", + %{}, + "awaiting_approval" + ) + end + defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do render_invalid_credentials_error(conn) end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index d389e4ce0..ec5b78750 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -19,7 +19,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do key: "_test", signing_salt: "cooldude" ] - setup do: clear_config([:instance, :account_activation_required]) + setup do + clear_config([:instance, :account_activation_required]) + clear_config([:instance, :account_approval_required]) + end describe "in OAuth consumer mode, " do setup do @@ -995,6 +998,31 @@ test "rejects token exchange for user with confirmation_pending set to true" do } end + test "rejects token exchange for valid credentials belonging to an unapproved user and approval is required" do + Pleroma.Config.put([:instance, :account_approval_required], true) + password = "testpassword" + + user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password), approval_pending: true) + + refute Pleroma.User.account_status(user) == :active + + app = insert(:oauth_app) + + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert resp = json_response(conn, 403) + assert %{"error" => _} = resp + refute Map.has_key?(resp, "access_token") + end + test "rejects an invalid authorization code" do app = insert(:oauth_app) From bcfd38c8f3ecd2620bae7fc756ffc3f4bbe2b89e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Jul 2020 21:31:13 -0500 Subject: [PATCH 04/29] Make a user unapproved when registering with `account_approval_required` on --- lib/pleroma/user.ex | 14 ++++++++++++++ test/user_test.exs | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 25c63fc44..e84900c4f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -634,8 +634,16 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do opts[:need_confirmation] end + need_approval? = + if is_nil(opts[:need_approval]) do + Config.get([:instance, :account_approval_required]) + else + opts[:need_approval] + end + struct |> confirmation_changeset(need_confirmation: need_confirmation?) + |> approval_changeset(need_approval: need_approval?) |> cast(params, [ :bio, :raw_bio, @@ -2145,6 +2153,12 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do cast(user, params, [:confirmation_pending, :confirmation_token]) end + @spec approval_changeset(User.t(), keyword()) :: Changeset.t() + def approval_changeset(user, need_approval: need_approval?) do + params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false} + cast(user, params, [:approval_pending]) + end + def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do if id not in user.pinned_activities do max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) diff --git a/test/user_test.exs b/test/user_test.exs index 040f532fe..e57453982 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -516,6 +516,27 @@ test "it creates confirmed user if :confirmed option is given" do end end + describe "user registration, with :account_approval_required" do + @full_user_data %{ + bio: "A guy", + name: "my name", + nickname: "nick", + password: "test", + password_confirmation: "test", + email: "email@example.com" + } + setup do: clear_config([:instance, :account_approval_required], true) + + test "it creates unapproved user" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + assert user.approval_pending + end + end + describe "get_or_fetch/1" do test "gets an existing user by nickname" do user = insert(:user) From 5ddf0415c4fd6021422eb38b4625c01ad27582c5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 00:22:12 -0500 Subject: [PATCH 05/29] Accept `reason` in POST /api/v1/accounts and store in DB --- lib/pleroma/user.ex | 4 +- lib/pleroma/web/twitter_api/twitter_api.ex | 1 + ...43918_add_registration_reason_to_users.exs | 9 +++ .../controllers/account_controller_test.exs | 70 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e84900c4f..51ccf6ffa 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -107,6 +107,7 @@ defmodule Pleroma.User do field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) field(:approval_pending, :boolean, default: false) + field(:registration_reason, :string, default: nil) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:domain_blocks, {:array, :string}, default: []) @@ -653,7 +654,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do :password, :password_confirmation, :emoji, - :accepts_chat_messages + :accepts_chat_messages, + :registration_reason ]) |> validate_required([:name, :nickname, :password, :password_confirmation]) |> validate_confirmation(:password) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 5cfb385ac..4ff021b82 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -19,6 +19,7 @@ def register_user(params, opts \\ []) do |> Map.put(:nickname, params[:username]) |> Map.put(:name, Map.get(params, :fullname, params[:username])) |> Map.put(:password_confirmation, params[:password]) + |> Map.put(:registration_reason, params[:reason]) if Pleroma.Config.get([:instance, :registrations_open]) do create_user(params, opts) diff --git a/priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs b/priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs new file mode 100644 index 000000000..fa02fded4 --- /dev/null +++ b/priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddRegistrationReasonToUsers do + use Ecto.Migration + + def change do + alter table(:users) do + add(:registration_reason, :string) + end + end +end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 9c7b5e9b2..28d21371a 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -885,6 +885,7 @@ test "blocking / unblocking a user" do end setup do: clear_config([:instance, :account_activation_required]) + setup do: clear_config([:instance, :account_approval_required]) test "Account registration via Application", %{conn: conn} do conn = @@ -949,6 +950,75 @@ test "Account registration via Application", %{conn: conn} do assert token_from_db.user.confirmation_pending end + test "Account registration via app with account_approval_required", %{conn: conn} do + Pleroma.Config.put([:instance, :account_approval_required], true) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + assert %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response_and_validate_schema(conn, 200) + + conn = + post(conn, "/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + assert refresh + assert scope == "read write follow" + + conn = + build_conn() + |> put_req_header("content-type", "multipart/form-data") + |> put_req_header("authorization", "Bearer " <> token) + |> post("/api/v1/accounts", %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + bio: "Test Bio", + agreement: true, + reason: "I'm a cool dude, bro" + }) + + %{ + "access_token" => token, + "created_at" => _created_at, + "scope" => ^scope, + "token_type" => "Bearer" + } = json_response_and_validate_schema(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + token_from_db = Repo.preload(token_from_db, :user) + assert token_from_db.user + + assert token_from_db.user.confirmation_pending + assert token_from_db.user.approval_pending + + assert token_from_db.user.registration_reason == "I'm a cool dude, bro" + end + test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do _user = insert(:user, email: "lain@example.org") app_token = insert(:oauth_token, user: nil) From 3062f86613696419f4716a53c3272ceef1b2b119 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 14 Jul 2020 07:31:21 +0300 Subject: [PATCH 06/29] added generated `pleroma.env` --- .gitignore | 2 + .../CLI_tasks/release_environments.md | 9 +++ docs/installation/otp_en.md | 7 +- installation/pleroma.service | 2 + lib/mix/tasks/pleroma/release_env.ex | 64 +++++++++++++++++++ test/tasks/release_env_test.exs | 30 +++++++++ 6 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 docs/administration/CLI_tasks/release_environments.md create mode 100644 lib/mix/tasks/pleroma/release_env.ex create mode 100644 test/tasks/release_env_test.exs diff --git a/.gitignore b/.gitignore index 599b52b9e..6ae21e914 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ erl_crash.dump # variables. /config/*.secret.exs /config/generated_config.exs +/config/*.env + # Database setup file, some may forget to delete it /config/setup_db.psql diff --git a/docs/administration/CLI_tasks/release_environments.md b/docs/administration/CLI_tasks/release_environments.md new file mode 100644 index 000000000..36ab43864 --- /dev/null +++ b/docs/administration/CLI_tasks/release_environments.md @@ -0,0 +1,9 @@ +# Generate release environment file + +```sh tab="OTP" + ./bin/pleroma_ctl release_env gen +``` + +```sh tab="From Source" +mix pleroma.release_env gen +``` diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index e4f822d1c..e115c2297 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -121,6 +121,9 @@ chown -R pleroma /etc/pleroma # Run the config generator su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" +# Run the environment file generator. +su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen" + # Create the postgres database su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" @@ -131,7 +134,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" # su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # Start the instance to verify that everything is working as expected -su pleroma -s $SHELL -lc "./bin/pleroma daemon" +su pleroma -s $SHELL -lc "export $( cat /opt/pleroma/config/pleroma.env | xargs); ./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance @@ -200,6 +203,7 @@ rc-update add pleroma # Copy the service into a proper directory cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service + # Start pleroma and enable it on boot systemctl start pleroma systemctl enable pleroma @@ -275,4 +279,3 @@ This will create an account withe the username of 'joeuser' with the email addre ## Questions Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**. - diff --git a/installation/pleroma.service b/installation/pleroma.service index 5dcbc1387..ee00a3b7a 100644 --- a/installation/pleroma.service +++ b/installation/pleroma.service @@ -17,6 +17,8 @@ Environment="MIX_ENV=prod" Environment="HOME=/var/lib/pleroma" ; Path to the folder containing the Pleroma installation. WorkingDirectory=/opt/pleroma +; Path to the environment file. the file contains RELEASE_COOKIE and etc +EnvironmentFile=/opt/pleroma/config/pleroma.env ; Path to the Mix binary. ExecStart=/usr/bin/mix phx.server diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex new file mode 100644 index 000000000..cbbbdeff6 --- /dev/null +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.ReleaseEnv do + use Mix.Task + import Mix.Pleroma + + @shortdoc "Generate Pleroma environment file." + @moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md") + + def run(["gen" | rest]) do + {options, [], []} = + OptionParser.parse( + rest, + strict: [ + force: :boolean, + path: :string + ], + aliases: [ + p: :path, + f: :force + ] + ) + + env_path = + get_option( + options, + :path, + "Environment file path", + "config/pleroma.env" + ) + |> Path.expand() + + proceed? = + if File.exists?(env_path) do + get_option( + options, + :force, + "Environment file is exist. Do you want overwritten the #{env_path} file? (y/n)", + "n" + ) === "y" + else + true + end + + if proceed? do + do_generate(env_path) + + shell_info( + "The file generated: #{env_path}.\nTo use the enviroment file need to add the line ';EnvironmentFile=#{ + env_path + }' in service file (/installation/pleroma.service)." + ) + end + end + + def do_generate(path) do + content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}" + + File.mkdir_p!(Path.dirname(path)) + File.write!(path, content) + end +end diff --git a/test/tasks/release_env_test.exs b/test/tasks/release_env_test.exs new file mode 100644 index 000000000..519f1eba9 --- /dev/null +++ b/test/tasks/release_env_test.exs @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.ReleaseEnvTest do + use ExUnit.Case + import ExUnit.CaptureIO, only: [capture_io: 1] + + @path "config/pleroma.test.env" + + def do_clean do + if File.exists?(@path) do + File.rm_rf(@path) + end + end + + setup do + do_clean() + on_exit(fn -> do_clean() end) + :ok + end + + test "generate pleroma.env" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.ReleaseEnv.run(["gen", "--path", @path, "--force"]) + end) =~ "The file generated" + + assert File.read!(@path) =~ "RELEASE_COOKIE=" + end +end From a1570ba6ad4c871df3783d06772b2eb8d2d6c4f1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 13:04:57 -0500 Subject: [PATCH 07/29] AdminAPI: Return `registration_reason` with users --- docs/API/admin_api.md | 3 +- .../web/admin_api/views/account_view.ex | 3 +- .../controllers/admin_api_controller_test.exs | 70 +++++++++++++------ 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index baf895d90..de4e36efa 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -46,7 +46,8 @@ Configuration options: "local": bool, "tags": array, "avatar": string, - "display_name": string + "display_name": string, + "registration_reason": string, }, ... ] diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index e1e929632..78062e520 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -77,7 +77,8 @@ def render("show.json", %{user: user}) do "roles" => User.roles(user), "tags" => user.tags || [], "confirmation_pending" => user.confirmation_pending, - "url" => user.uri || user.ap_id + "url" => user.uri || user.ap_id, + "registration_reason" => user.registration_reason } end diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index c2433f23c..556e8d97a 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -338,7 +338,8 @@ test "Show", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } assert expected == json_response(conn, 200) @@ -601,7 +602,9 @@ test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do describe "GET /api/pleroma/admin/users" do test "renders users array for the first page", %{conn: conn, admin: admin} do - user = insert(:user, local: false, tags: ["foo", "bar"]) + user = + insert(:user, local: false, tags: ["foo", "bar"], registration_reason: "I'm a chill dude") + conn = get(conn, "/api/pleroma/admin/users?page=1") users = @@ -616,7 +619,8 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => user.deactivated, @@ -628,7 +632,8 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => "I'm a chill dude" } ] |> Enum.sort_by(& &1["nickname"]) @@ -701,7 +706,8 @@ test "regular search", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -727,7 +733,8 @@ test "search by domain", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -753,7 +760,8 @@ test "search by full nickname", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -779,7 +787,8 @@ test "search by display name", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -805,7 +814,8 @@ test "search by email", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -831,7 +841,8 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -852,7 +863,8 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, - "url" => user2.ap_id + "url" => user2.ap_id, + "registration_reason" => nil } ] } @@ -885,7 +897,8 @@ test "only local users" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -911,7 +924,8 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil }, %{ "deactivated" => admin.deactivated, @@ -923,7 +937,8 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -935,7 +950,8 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), "confirmation_pending" => false, - "url" => old_admin.ap_id + "url" => old_admin.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -966,7 +982,8 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "url" => admin.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -978,7 +995,8 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), "confirmation_pending" => false, - "url" => second_admin.ap_id + "url" => second_admin.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -1011,7 +1029,8 @@ test "load only moderators", %{conn: conn} do "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), "confirmation_pending" => false, - "url" => moderator.ap_id + "url" => moderator.ap_id, + "registration_reason" => nil } ] } @@ -1037,7 +1056,8 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user1) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user1.name || user1.nickname), "confirmation_pending" => false, - "url" => user1.ap_id + "url" => user1.ap_id, + "registration_reason" => nil }, %{ "deactivated" => false, @@ -1049,7 +1069,8 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, - "url" => user2.ap_id + "url" => user2.ap_id, + "registration_reason" => nil } ] |> Enum.sort_by(& &1["nickname"]) @@ -1089,7 +1110,8 @@ test "it works with multiple filters" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } ] } @@ -1114,7 +1136,8 @@ test "it omits relay user", %{admin: admin, conn: conn} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, - "url" => admin.ap_id + "url" => admin.ap_id, + "registration_reason" => nil } ] } @@ -1177,7 +1200,8 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admi "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, - "url" => user.ap_id + "url" => user.ap_id, + "registration_reason" => nil } log_entry = Repo.one(ModerationLog) From b750129da1434823746e3dbc237d0e04552fa753 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 13:47:05 -0500 Subject: [PATCH 08/29] AdminAPI: Return `approval_pending` with users --- docs/API/admin_api.md | 2 + .../web/admin_api/views/account_view.ex | 1 + .../controllers/admin_api_controller_test.exs | 42 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index de4e36efa..fdd9df6c7 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -47,6 +47,8 @@ Configuration options: "tags": array, "avatar": string, "display_name": string, + "confirmation_pending": bool, + "approval_pending": bool, "registration_reason": string, }, ... diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 78062e520..bdab04ad2 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -77,6 +77,7 @@ def render("show.json", %{user: user}) do "roles" => User.roles(user), "tags" => user.tags || [], "confirmation_pending" => user.confirmation_pending, + "approval_pending" => user.approval_pending, "url" => user.uri || user.ap_id, "registration_reason" => user.registration_reason } diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index 556e8d97a..ccda5df3f 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -338,6 +338,7 @@ test "Show", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -602,8 +603,8 @@ test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do describe "GET /api/pleroma/admin/users" do test "renders users array for the first page", %{conn: conn, admin: admin} do - user = - insert(:user, local: false, tags: ["foo", "bar"], registration_reason: "I'm a chill dude") + user = insert(:user, local: false, tags: ["foo", "bar"]) + user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude") conn = get(conn, "/api/pleroma/admin/users?page=1") @@ -619,6 +620,7 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => admin.ap_id, "registration_reason" => nil }, @@ -632,14 +634,29 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, + "registration_reason" => nil + }, + %{ + "deactivated" => user2.deactivated, + "id" => user2.id, + "nickname" => user2.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => true, + "tags" => [], + "avatar" => User.avatar_url(user2) |> MediaProxy.url(), + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false, + "approval_pending" => true, + "url" => user2.ap_id, "registration_reason" => "I'm a chill dude" } ] |> Enum.sort_by(& &1["nickname"]) assert json_response(conn, 200) == %{ - "count" => 2, + "count" => 3, "page_size" => 50, "users" => users } @@ -706,6 +723,7 @@ test "regular search", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -733,6 +751,7 @@ test "search by domain", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -760,6 +779,7 @@ test "search by full nickname", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -787,6 +807,7 @@ test "search by display name", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -814,6 +835,7 @@ test "search by email", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -841,6 +863,7 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -863,6 +886,7 @@ test "regular search with page size", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user2.ap_id, "registration_reason" => nil } @@ -897,6 +921,7 @@ test "only local users" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -924,6 +949,7 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil }, @@ -937,6 +963,7 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => admin.ap_id, "registration_reason" => nil }, @@ -950,6 +977,7 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => old_admin.ap_id, "registration_reason" => nil } @@ -982,6 +1010,7 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => admin.ap_id, "registration_reason" => nil }, @@ -995,6 +1024,7 @@ test "load only admins", %{conn: conn, admin: admin} do "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => second_admin.ap_id, "registration_reason" => nil } @@ -1029,6 +1059,7 @@ test "load only moderators", %{conn: conn} do "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => moderator.ap_id, "registration_reason" => nil } @@ -1056,6 +1087,7 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user1) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user1.name || user1.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user1.ap_id, "registration_reason" => nil }, @@ -1069,6 +1101,7 @@ test "load users with tags list", %{conn: conn} do "avatar" => User.avatar_url(user2) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user2.name || user2.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user2.ap_id, "registration_reason" => nil } @@ -1110,6 +1143,7 @@ test "it works with multiple filters" do "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } @@ -1136,6 +1170,7 @@ test "it omits relay user", %{admin: admin, conn: conn} do "avatar" => User.avatar_url(admin) |> MediaProxy.url(), "display_name" => HTML.strip_tags(admin.name || admin.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => admin.ap_id, "registration_reason" => nil } @@ -1200,6 +1235,7 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admi "avatar" => User.avatar_url(user) |> MediaProxy.url(), "display_name" => HTML.strip_tags(user.name || user.nickname), "confirmation_pending" => false, + "approval_pending" => false, "url" => user.ap_id, "registration_reason" => nil } From 33f1b29b2c9cfe6f09c6b088b8b6f7bf14379b9b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 14:14:43 -0500 Subject: [PATCH 09/29] AdminAPI: Filter users by `need_approval` --- docs/API/admin_api.md | 1 + lib/pleroma/user/query.ex | 5 +++ .../controllers/admin_api_controller.ex | 2 +- .../controllers/admin_api_controller_test.exs | 38 +++++++++++++++++++ test/web/admin_api/search_test.exs | 11 ++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index fdd9df6c7..42071376e 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -19,6 +19,7 @@ Configuration options: - `local`: only local users - `external`: only external users - `active`: only active users + - `need_approval`: only unapproved users - `deactivated`: only deactivated users - `is_admin`: users with admin role - `is_moderator`: users with moderator role diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 66ffe9090..45553cb6c 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do external: boolean(), active: boolean(), deactivated: boolean(), + need_approval: boolean(), is_admin: boolean(), is_moderator: boolean(), super_users: boolean(), @@ -146,6 +147,10 @@ defp compose_query({:deactivated, true}, query) do |> where([u], not is_nil(u.nickname)) end + defp compose_query({:need_approval, _}, query) do + where(query, [u], u.approval_pending) + end + defp compose_query({:followers, %User{id: id}}, query) do query |> where([u], u.id != ^id) diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index e5f14269a..037a6f269 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -350,7 +350,7 @@ def list_users(conn, params) do end end - @filters ~w(local external active deactivated is_admin is_moderator) + @filters ~w(local external active deactivated need_approval is_admin is_moderator) @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index ccda5df3f..9cc8b1879 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -991,6 +991,44 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do } end + test "only unapproved users", %{conn: conn} do + user = + insert(:user, + nickname: "sadboy", + approval_pending: true, + registration_reason: "Plz let me in!" + ) + + insert(:user, nickname: "happyboy", approval_pending: false) + + conn = get(conn, "/api/pleroma/admin/users?filters=need_approval") + + users = + [ + %{ + "deactivated" => user.deactivated, + "id" => user.id, + "nickname" => user.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => true, + "tags" => [], + "avatar" => User.avatar_url(user) |> MediaProxy.url(), + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false, + "approval_pending" => true, + "url" => user.ap_id, + "registration_reason" => "Plz let me in!" + } + ] + |> Enum.sort_by(& &1["nickname"]) + + assert json_response(conn, 200) == %{ + "count" => 1, + "page_size" => 50, + "users" => users + } + end + test "load only admins", %{conn: conn, admin: admin} do second_admin = insert(:user, is_admin: true) insert(:user) diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs index e0e3d4153..b974cedd5 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/web/admin_api/search_test.exs @@ -166,5 +166,16 @@ test "it returns user by email" do assert total == 3 assert count == 1 end + + test "it returns unapproved user" do + unapproved = insert(:user, approval_pending: true) + insert(:user) + insert(:user) + + {:ok, _results, total} = Search.user() + {:ok, [^unapproved], count} = Search.user(%{need_approval: true}) + assert total == 3 + assert count == 1 + end end end From 20d24741af8ae755ce7f753680a55ca24ef7c1d4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 18:02:44 -0500 Subject: [PATCH 10/29] AdminAPI: Add `PATCH /api/pleroma/admin/users/approve` endpoint --- docs/API/admin_api.md | 18 +++++++++++++ lib/pleroma/moderation_log.ex | 11 ++++++++ lib/pleroma/user.ex | 13 ++++++++++ .../controllers/admin_api_controller.ex | 16 ++++++++++++ lib/pleroma/web/router.ex | 1 + test/user_test.exs | 25 +++++++++++++++++++ .../controllers/admin_api_controller_test.exs | 20 +++++++++++++++ 7 files changed, 104 insertions(+) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 42071376e..4b143e4ee 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -246,6 +246,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret } ``` +## `PATCH /api/pleroma/admin/users/approve` + +### Approve user + +- Params: + - `nicknames`: nicknames array +- Response: + +```json +{ + users: [ + { + // user object + } + ] +} +``` + ## `GET /api/pleroma/admin/users/:nickname_or_id` ### Retrive the details of a user diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 7aacd9d80..31c9afe2a 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -409,6 +409,17 @@ def get_log_entry_message(%ModerationLog{ "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}" end + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "approve", + "subject" => users + } + }) do + "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" + end + @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 51ccf6ffa..439c2c9b6 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1471,6 +1471,19 @@ def deactivate(%User{} = user, status) do end end + def approve(users) when is_list(users) do + Repo.transaction(fn -> + Enum.map(users, fn user -> + with {:ok, user} <- approve(user), do: user + end) + end) + end + + def approve(%User{} = user) do + change(user, approval_pending: false) + |> update_and_set_cache() + end + def update_notification_settings(%User{} = user, settings) do user |> cast(%{notification_settings: settings}, []) diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 037a6f269..53f71fcbf 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do :user_toggle_activation, :user_activate, :user_deactivate, + :user_approve, :tag_users, :untag_users, :right_add, @@ -303,6 +304,21 @@ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nickname |> render("index.json", %{users: Keyword.values(updated_users)}) end + def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = Enum.map(nicknames, &User.get_cached_by_nickname/1) + {:ok, updated_users} = User.approve(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "approve" + }) + + conn + |> put_view(AccountView) + |> render("index.json", %{users: updated_users}) + end + def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do with {:ok, _} <- User.tag(nicknames, tags) do ModerationLog.insert_log(%{ diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 386308362..c6433cc53 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -138,6 +138,7 @@ defmodule Pleroma.Web.Router do patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) patch("/users/activate", AdminAPIController, :user_activate) patch("/users/deactivate", AdminAPIController, :user_deactivate) + patch("/users/approve", AdminAPIController, :user_approve) put("/users/tag", AdminAPIController, :tag_users) delete("/users/tag", AdminAPIController, :untag_users) diff --git a/test/user_test.exs b/test/user_test.exs index e57453982..9da2aa411 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1202,6 +1202,31 @@ test "hide a user's statuses from timelines and notifications" do end end + describe "approve" do + test "approves a user" do + user = insert(:user, approval_pending: true) + assert true == user.approval_pending + {:ok, user} = User.approve(user) + assert false == user.approval_pending + end + + test "approves a list of users" do + unapproved_users = [ + insert(:user, approval_pending: true), + insert(:user, approval_pending: true), + insert(:user, approval_pending: true) + ] + + {:ok, users} = User.approve(unapproved_users) + + assert Enum.count(users) == 3 + + Enum.each(users, fn user -> + assert false == user.approval_pending + end) + end + end + describe "delete" do setup do {:ok, user} = insert(:user) |> User.set_cache() diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index 9cc8b1879..351df8883 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -1257,6 +1257,26 @@ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" end + test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do + user_one = insert(:user, approval_pending: true) + user_two = insert(:user, approval_pending: true) + + conn = + patch( + conn, + "/api/pleroma/admin/users/approve", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}" + end + test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do user = insert(:user) From fab44f69703975c0f6182ed1c26c1dcdad221dc5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 18:46:57 -0500 Subject: [PATCH 11/29] Test User with confirmation_pending: true, approval_pending: true --- test/user_test.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/user_test.exs b/test/user_test.exs index 9da2aa411..cd39e1623 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1390,7 +1390,10 @@ test "returns :deactivated for deactivated user" do end test "returns :approval_pending for unapproved user" do - user = insert(:user, local: true, confirmation_pending: false, approval_pending: true) + user = insert(:user, local: true, approval_pending: true) + assert User.account_status(user) == :approval_pending + + user = insert(:user, local: true, confirmation_pending: true, approval_pending: true) assert User.account_status(user) == :approval_pending end end From e82060c47253946b79685ebff9a38e2fe0ac360e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 18:47:23 -0500 Subject: [PATCH 12/29] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fed80a99..a0f529108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Support pagination in emoji packs API (for packs and for files in pack) - Support for viewing instances favicons next to posts and accounts - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. +- "By approval" registrations mode.
API Changes From df3d1bf5e57389e41a70676ccab1df81d83e3d74 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 18:48:17 -0500 Subject: [PATCH 13/29] Add :approval_pending to User @type account_status --- lib/pleroma/user.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 439c2c9b6..9c3b46ae8 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -42,7 +42,12 @@ defmodule Pleroma.User do require Logger @type t :: %__MODULE__{} - @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending + @type account_status :: + :active + | :deactivated + | :password_reset_pending + | :confirmation_pending + | :approval_pending @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength From 0d004a9d046f279be8462e8c751b5f1bcec3d35b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 Jul 2020 20:31:20 -0500 Subject: [PATCH 14/29] Email admins when a new unapproved account is up for review --- lib/pleroma/emails/admin_email.ex | 14 ++++++++ lib/pleroma/web/twitter_api/twitter_api.ex | 13 ++++++++ test/emails/admin_email_test.exs | 20 ++++++++++++ test/web/twitter_api/twitter_api_test.exs | 37 ++++++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index aa0b2a66b..fae7faf00 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -82,4 +82,18 @@ def report(to, reporter, account, statuses, comment) do |> subject("#{instance_name()} Report") |> html_body(html_body) end + + def new_unapproved_registration(to, account) do + html_body = """ +

New account for review: @#{account.nickname}

+
#{account.registration_reason}
+ Visit AdminFE + """ + + new() + |> to({to.name, to.email}) + |> from({instance_name(), instance_notify_email()}) + |> subject("New account up for review on #{instance_name()} (@#{account.nickname})") + |> html_body(html_body) + end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 4ff021b82..2294d9d0d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -45,6 +45,7 @@ defp create_user(params, opts) do case User.register(changeset) do {:ok, user} -> + maybe_notify_admins(user) {:ok, user} {:error, changeset} -> @@ -57,6 +58,18 @@ defp create_user(params, opts) do end end + defp maybe_notify_admins(%User{} = account) do + if Pleroma.Config.get([:instance, :account_approval_required]) do + User.all_superusers() + |> Enum.filter(fn user -> not is_nil(user.email) end) + |> Enum.each(fn superuser -> + superuser + |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account) + |> Pleroma.Emails.Mailer.deliver_async() + end) + end + end + def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), %User{local: true, email: email} = user when is_binary(email) <- diff --git a/test/emails/admin_email_test.exs b/test/emails/admin_email_test.exs index 9082ae5a7..e24231e27 100644 --- a/test/emails/admin_email_test.exs +++ b/test/emails/admin_email_test.exs @@ -46,4 +46,24 @@ test "it works when the reporter is a remote user without email" do assert res.to == [{to_user.name, to_user.email}] assert res.from == {config[:name], config[:notify_email]} end + + test "new unapproved registration email" do + config = Pleroma.Config.get(:instance) + to_user = insert(:user) + account = insert(:user, registration_reason: "Plz let me in") + + res = AdminEmail.new_unapproved_registration(to_user, account) + + account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id) + + assert res.to == [{to_user.name, to_user.email}] + assert res.from == {config[:name], config[:notify_email]} + assert res.subject == "New account up for review on #{config[:name]} (@#{account.nickname})" + + assert res.html_body == """ +

New account for review: @#{account.nickname}

+
Plz let me in
+ Visit AdminFE + """ + end end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 368533292..df9d59f6a 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do use Pleroma.DataCase + import Pleroma.Factory alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User @@ -85,6 +86,42 @@ test "it sends confirmation email if :account_activation_required is specified i ) end + test "it sends an admin email if :account_approval_required is specified in instance config" do + admin = insert(:user, is_admin: true) + setting = Pleroma.Config.get([:instance, :account_approval_required]) + + unless setting do + Pleroma.Config.put([:instance, :account_approval_required], true) + on_exit(fn -> Pleroma.Config.put([:instance, :account_approval_required], setting) end) + end + + data = %{ + :username => "lain", + :email => "lain@wired.jp", + :fullname => "lain iwakura", + :bio => "", + :password => "bear", + :confirm => "bear", + :reason => "I love anime" + } + + {:ok, user} = TwitterAPI.register_user(data) + ObanHelpers.perform_all() + + assert user.approval_pending + + email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) + + notify_email = Pleroma.Config.get([:instance, :notify_email]) + instance_name = Pleroma.Config.get([:instance, :name]) + + Swoosh.TestAssertions.assert_email_sent( + from: {instance_name, notify_email}, + to: {admin.name, admin.email}, + html_body: email.html_body + ) + end + test "it registers a new user and parses mentions in the bio" do data1 = %{ :username => "john", From 9ce95fa68f933c911c10e640b9a32ddae8a631c9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 15 Jul 2020 17:04:30 -0500 Subject: [PATCH 15/29] Use `approval_required` in /api/v1/instance --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +- test/web/mastodon_api/controllers/instance_controller_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 243067a73..5389d63cd 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -26,6 +26,7 @@ def render("show.json", _) do thumbnail: Keyword.get(instance, :instance_thumbnail), languages: ["en"], registrations: Keyword.get(instance, :registrations_open), + approval_required: Keyword.get(instance, :account_approval_required), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), poll_limits: Keyword.get(instance, :poll_limits), @@ -39,7 +40,6 @@ def render("show.json", _) do pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), - account_approval_required: Keyword.get(instance, :account_approval_required), features: features(), federation: federation(), fields_limits: fields_limits() diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index 8a4183283..6a9ccd979 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -27,6 +27,7 @@ test "get instance information", %{conn: conn} do "thumbnail" => _, "languages" => _, "registrations" => _, + "approval_required" => _, "poll_limits" => _, "upload_limit" => _, "avatar_upload_limit" => _, @@ -38,7 +39,6 @@ test "get instance information", %{conn: conn} do } = result assert result["pleroma"]["metadata"]["account_activation_required"] != nil - assert result["pleroma"]["metadata"]["account_approval_required"] != nil assert result["pleroma"]["metadata"]["features"] assert result["pleroma"]["metadata"]["federation"] assert result["pleroma"]["metadata"]["fields_limits"] From 02cc42e72c5f7dde78c705c3cbc83d2c13fb7a71 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 15 Jul 2020 17:08:46 -0500 Subject: [PATCH 16/29] Squash User approval migrations --- ...s => 20200712234852_add_approval_fields_to_users.exs} | 3 ++- .../20200712234852_add_approval_pending_to_users.exs | 9 --------- 2 files changed, 2 insertions(+), 10 deletions(-) rename priv/repo/migrations/{20200714043918_add_registration_reason_to_users.exs => 20200712234852_add_approval_fields_to_users.exs} (55%) delete mode 100644 priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs diff --git a/priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs similarity index 55% rename from priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs rename to priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs index fa02fded4..559640f01 100644 --- a/priv/repo/migrations/20200714043918_add_registration_reason_to_users.exs +++ b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs @@ -1,8 +1,9 @@ -defmodule Pleroma.Repo.Migrations.AddRegistrationReasonToUsers do +defmodule Pleroma.Repo.Migrations.AddApprovalFieldsToUsers do use Ecto.Migration def change do alter table(:users) do + add(:approval_pending, :boolean) add(:registration_reason, :string) end end diff --git a/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs b/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs deleted file mode 100644 index f7eb8179b..000000000 --- a/priv/repo/migrations/20200712234852_add_approval_pending_to_users.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Pleroma.Repo.Migrations.AddApprovalPendingToUsers do - use Ecto.Migration - - def change do - alter table(:users) do - add(:approval_pending, :boolean) - end - end -end From 4e0e19a7060da9f3eb06ffb0bdb816c7dedb720b Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 16 Jul 2020 08:52:14 +0300 Subject: [PATCH 17/29] update task messages --- installation/init.d/pleroma | 1 + lib/mix/tasks/pleroma/release_env.ex | 32 +++++++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/installation/init.d/pleroma b/installation/init.d/pleroma index 384536f7e..e908cda1b 100755 --- a/installation/init.d/pleroma +++ b/installation/init.d/pleroma @@ -8,6 +8,7 @@ pidfile="/var/run/pleroma.pid" directory=/opt/pleroma healthcheck_delay=60 healthcheck_timer=30 +export $(cat /opt/pleroma/config/pleroma.env) : ${pleroma_port:-4000} diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex index cbbbdeff6..63030c5cc 100644 --- a/lib/mix/tasks/pleroma/release_env.ex +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -23,14 +23,15 @@ def run(["gen" | rest]) do ] ) - env_path = + file_path = get_option( options, :path, "Environment file path", - "config/pleroma.env" + "./config/pleroma.env" ) - |> Path.expand() + + env_path = Path.expand(file_path) proceed? = if File.exists?(env_path) do @@ -45,13 +46,24 @@ def run(["gen" | rest]) do end if proceed? do - do_generate(env_path) + case do_generate(env_path) do + {:error, reason} -> + shell_error( + File.Error.message(%{action: "write to file", reason: reason, path: env_path}) + ) - shell_info( - "The file generated: #{env_path}.\nTo use the enviroment file need to add the line ';EnvironmentFile=#{ - env_path - }' in service file (/installation/pleroma.service)." - ) + _ -> + shell_info("\nThe file generated: #{env_path}.\n") + + shell_info(""" + WARNING: before start pleroma app please to made the file read-only and non-modifiable. + Example: + chmod 0444 #{file_path} + chattr +i #{file_path} + """) + end + else + shell_info("\nThe file is exist. #{env_path}.\n") end end @@ -59,6 +71,6 @@ def do_generate(path) do content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}" File.mkdir_p!(Path.dirname(path)) - File.write!(path, content) + File.write(path, content) end end From c72676d22f9c2e9ed83ba793fe9a85efd7e9a544 Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 16 Jul 2020 13:30:17 +0000 Subject: [PATCH 18/29] Apply 1 suggestion(s) to 1 file(s) --- lib/mix/tasks/pleroma/release_env.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex index 63030c5cc..4d8b6ff27 100644 --- a/lib/mix/tasks/pleroma/release_env.ex +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -56,7 +56,7 @@ def run(["gen" | rest]) do shell_info("\nThe file generated: #{env_path}.\n") shell_info(""" - WARNING: before start pleroma app please to made the file read-only and non-modifiable. + WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable. Example: chmod 0444 #{file_path} chattr +i #{file_path} From 16da9f5cfd63237549da7156e5297d356628a70f Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 16 Jul 2020 13:30:28 +0000 Subject: [PATCH 19/29] Apply 1 suggestion(s) to 1 file(s) --- lib/mix/tasks/pleroma/release_env.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex index 4d8b6ff27..9da74ffcf 100644 --- a/lib/mix/tasks/pleroma/release_env.ex +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -38,7 +38,7 @@ def run(["gen" | rest]) do get_option( options, :force, - "Environment file is exist. Do you want overwritten the #{env_path} file? (y/n)", + "Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)", "n" ) === "y" else From 5fcb3e873822c602a5f50cbeb159427e02ea1818 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 16 Jul 2020 16:35:09 +0300 Subject: [PATCH 20/29] fix docs --- docs/installation/otp_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index e115c2297..338dfa7d0 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -134,7 +134,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" # su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # Start the instance to verify that everything is working as expected -su pleroma -s $SHELL -lc "export $( cat /opt/pleroma/config/pleroma.env | xargs); ./bin/pleroma daemon" +su pleroma -s $SHELL -lc "export $(cat /opt/pleroma/config/pleroma.env); ./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance From 5e745567031e87ee0854dca8d10065449af27d9c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 16 Jul 2020 20:25:53 -0500 Subject: [PATCH 21/29] Sanitize `reason` param in POST /api/v1/accounts --- lib/pleroma/web/twitter_api/twitter_api.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 2294d9d0d..424a705dd 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail + alias Pleroma.HTML alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken @@ -19,7 +20,7 @@ def register_user(params, opts \\ []) do |> Map.put(:nickname, params[:username]) |> Map.put(:name, Map.get(params, :fullname, params[:username])) |> Map.put(:password_confirmation, params[:password]) - |> Map.put(:registration_reason, params[:reason]) + |> Map.put(:registration_reason, HTML.strip_tags(params[:reason])) if Pleroma.Config.get([:instance, :registrations_open]) do create_user(params, opts) From 57568437361dd14151e3aa0590c7d1da05141cf4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 17 Jul 2020 12:19:41 -0500 Subject: [PATCH 22/29] Fully delete users with status :approval_pending --- lib/pleroma/user.ex | 13 +++++++------ test/user_test.exs | 11 +++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 8e2c9fbe2..23288d434 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1525,12 +1525,13 @@ defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate defp delete_or_deactivate(%User{local: true} = user) do status = account_status(user) - if status == :confirmation_pending do - delete_and_invalidate_cache(user) - else - user - |> change(%{deactivated: true, email: nil}) - |> update_and_set_cache() + case status do + :confirmation_pending -> delete_and_invalidate_cache(user) + :approval_pending -> delete_and_invalidate_cache(user) + _ -> + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() end end diff --git a/test/user_test.exs b/test/user_test.exs index cd39e1623..57cc054af 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1314,6 +1314,17 @@ test "deactivates user when activation is not required", %{user: user} do end end + test "delete/1 when approval is pending deletes the user" do + user = insert(:user, approval_pending: true) + {:ok, user: user} + + {:ok, job} = User.delete(user) + {:ok, _} = ObanHelpers.perform(job) + + refute User.get_cached_by_id(user.id) + refute User.get_by_id(user.id) + end + test "get_public_key_for_ap_id fetches a user that's not in the db" do assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end From 15f8921b111bc38d0d9eb9ccd1fd09e41cdbc85e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 17 Jul 2020 12:26:52 -0500 Subject: [PATCH 23/29] Test that unapproved users can never log in regardless of admin settings --- test/web/oauth/oauth_controller_test.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index ec5b78750..1200126b8 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -998,8 +998,7 @@ test "rejects token exchange for user with confirmation_pending set to true" do } end - test "rejects token exchange for valid credentials belonging to an unapproved user and approval is required" do - Pleroma.Config.put([:instance, :account_approval_required], true) + test "rejects token exchange for valid credentials belonging to an unapproved user" do password = "testpassword" user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password), approval_pending: true) From 6f44a0ee84a8dca7a94a38b45493a444390f13ec Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 27 Jul 2020 15:13:34 -0500 Subject: [PATCH 24/29] Add configurable registration_reason limit --- config/config.exs | 1 + lib/pleroma/user.ex | 2 ++ ...0712234852_add_approval_fields_to_users.exs | 2 +- test/user_test.exs | 18 +++++++++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index d8bf921bb..1dc196a6b 100644 --- a/config/config.exs +++ b/config/config.exs @@ -238,6 +238,7 @@ max_remote_account_fields: 20, account_field_name_length: 512, account_field_value_length: 2048, + registration_reason_length: 500, external_user_synchronization: true, extended_nickname_format: true, cleanup_attachments: false, diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a78123fe4..913b6afd1 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -641,6 +641,7 @@ def force_password_reset(user), do: update_password_reset_pending(user, true) def register_changeset(struct, params \\ %{}, opts \\ []) do bio_limit = Config.get([:instance, :user_bio_length], 5000) name_limit = Config.get([:instance, :user_name_length], 100) + reason_limit = Config.get([:instance, :registration_reason_length], 500) params = Map.put_new(params, :accepts_chat_messages, true) need_confirmation? = @@ -681,6 +682,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do |> validate_format(:email, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) + |> validate_length(:registration_reason, max: reason_limit) |> maybe_validate_required_email(opts[:external]) |> put_password_hash |> put_ap_id() diff --git a/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs index 559640f01..43f741a5b 100644 --- a/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs +++ b/priv/repo/migrations/20200712234852_add_approval_fields_to_users.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.AddApprovalFieldsToUsers do def change do alter table(:users) do add(:approval_pending, :boolean) - add(:registration_reason, :string) + add(:registration_reason, :text) end end end diff --git a/test/user_test.exs b/test/user_test.exs index 5da86bcec..5bf677666 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -550,7 +550,8 @@ test "it creates confirmed user if :confirmed option is given" do nickname: "nick", password: "test", password_confirmation: "test", - email: "email@example.com" + email: "email@example.com", + registration_reason: "I'm a cool guy :)" } setup do: clear_config([:instance, :account_approval_required], true) @@ -561,6 +562,21 @@ test "it creates unapproved user" do {:ok, user} = Repo.insert(changeset) assert user.approval_pending + assert user.registration_reason == "I'm a cool guy :)" + end + + test "it restricts length of registration reason" do + reason_limit = Pleroma.Config.get([:instance, :registration_reason_length]) + + assert is_integer(reason_limit) + + params = + @full_user_data + |> Map.put(:registration_reason, "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in.") + + changeset = User.register_changeset(%User{}, params) + + refute changeset.valid? end end From 520dce857e4a6d3cdce275c46b3ad7b46a582c76 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 27 Jul 2020 15:24:20 -0500 Subject: [PATCH 25/29] Add :registration_reason_length to description.exs --- config/description.exs | 8 ++++++++ docs/configuration/cheatsheet.md | 1 + 2 files changed, 9 insertions(+) diff --git a/config/description.exs b/config/description.exs index 509effbc3..df9f256ef 100644 --- a/config/description.exs +++ b/config/description.exs @@ -879,6 +879,14 @@ 2048 ] }, + %{ + key: :registration_reason_length, + type: :integer, + description: "Maximum registration reason length. Default: 500.", + suggestions: [ + 500 + ] + }, %{ key: :external_user_synchronization, type: :boolean, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 5cf073293..c89df24cc 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -59,6 +59,7 @@ To add configuration to your config file, you can copy it from the base config. * `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`). * `account_field_name_length`: An account field name maximum length (default: `512`). * `account_field_value_length`: An account field value maximum length (default: `2048`). +* `registration_reason_length`: Maximum registration reason length (default: `500`). * `external_user_synchronization`: Enabling following/followers counters synchronization for external users. * `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. From f43518eb7433a6c50d635d6536c3fbe3a37ea82b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 27 Jul 2020 19:19:14 -0500 Subject: [PATCH 26/29] Lint, fix test --- lib/pleroma/user.ex | 8 ++++++-- test/user_test.exs | 5 ++++- .../mastodon_api/controllers/account_controller_test.exs | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 913b6afd1..dcf6ebee2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1553,8 +1553,12 @@ defp delete_or_deactivate(%User{local: true} = user) do status = account_status(user) case status do - :confirmation_pending -> delete_and_invalidate_cache(user) - :approval_pending -> delete_and_invalidate_cache(user) + :confirmation_pending -> + delete_and_invalidate_cache(user) + + :approval_pending -> + delete_and_invalidate_cache(user) + _ -> user |> change(%{deactivated: true, email: nil}) diff --git a/test/user_test.exs b/test/user_test.exs index 5bf677666..624baf8ad 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -572,7 +572,10 @@ test "it restricts length of registration reason" do params = @full_user_data - |> Map.put(:registration_reason, "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in.") + |> Map.put( + :registration_reason, + "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in." + ) changeset = User.register_changeset(%User{}, params) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index e6b283aab..1ba5bc964 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -1017,7 +1017,7 @@ test "Account registration via app with account_approval_required", %{conn: conn password: "PlzDontHackLain", bio: "Test Bio", agreement: true, - reason: "I'm a cool dude, bro" + reason: "I am a cool dude, bro" }) %{ @@ -1035,7 +1035,7 @@ test "Account registration via app with account_approval_required", %{conn: conn assert token_from_db.user.confirmation_pending assert token_from_db.user.approval_pending - assert token_from_db.user.registration_reason == "I'm a cool dude, bro" + assert token_from_db.user.registration_reason == "I am a cool dude, bro" end test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do From f688c8df82b955b50552b3198ddc153a716451c2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 27 Jul 2020 20:36:31 -0500 Subject: [PATCH 27/29] Fix User.registration_reason HTML sanitizing issues --- lib/pleroma/emails/admin_email.ex | 3 ++- lib/pleroma/web/twitter_api/twitter_api.ex | 3 +-- test/web/mastodon_api/controllers/account_controller_test.exs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index fae7faf00..c27ad1065 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do import Swoosh.Email alias Pleroma.Config + alias Pleroma.HTML alias Pleroma.Web.Router.Helpers defp instance_config, do: Config.get(:instance) @@ -86,7 +87,7 @@ def report(to, reporter, account, statuses, comment) do def new_unapproved_registration(to, account) do html_body = """

New account for review: @#{account.nickname}

-
#{account.registration_reason}
+
#{HTML.strip_tags(account.registration_reason)}
Visit AdminFE """ diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 424a705dd..2294d9d0d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail - alias Pleroma.HTML alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken @@ -20,7 +19,7 @@ def register_user(params, opts \\ []) do |> Map.put(:nickname, params[:username]) |> Map.put(:name, Map.get(params, :fullname, params[:username])) |> Map.put(:password_confirmation, params[:password]) - |> Map.put(:registration_reason, HTML.strip_tags(params[:reason])) + |> Map.put(:registration_reason, params[:reason]) if Pleroma.Config.get([:instance, :registrations_open]) do create_user(params, opts) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 1ba5bc964..e6b283aab 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -1017,7 +1017,7 @@ test "Account registration via app with account_approval_required", %{conn: conn password: "PlzDontHackLain", bio: "Test Bio", agreement: true, - reason: "I am a cool dude, bro" + reason: "I'm a cool dude, bro" }) %{ @@ -1035,7 +1035,7 @@ test "Account registration via app with account_approval_required", %{conn: conn assert token_from_db.user.confirmation_pending assert token_from_db.user.approval_pending - assert token_from_db.user.registration_reason == "I am a cool dude, bro" + assert token_from_db.user.registration_reason == "I'm a cool dude, bro" end test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do From edf8b6abfeba487406db756254a00524e3a9dce2 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 29 Jul 2020 10:53:08 +0200 Subject: [PATCH 28/29] EnsureRePrepended: Don't break on chat messages. --- .../web/activity_pub/mrf/ensure_re_prepended.ex | 3 ++- test/web/activity_pub/mrf/ensure_re_prepended_test.exs | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index 2627a0007..3bf70b894 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -27,7 +27,8 @@ def filter_by_summary( def filter_by_summary(_in_reply_to, child), do: child - def filter(%{"type" => "Create", "object" => child_object} = object) do + def filter(%{"type" => "Create", "object" => child_object} = object) + when is_map(child_object) do child = child_object["inReplyTo"] |> Object.normalize(child_object["inReplyTo"]) diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs index 38ddec5bb..9a283f27d 100644 --- a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs +++ b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -78,5 +78,15 @@ test "it skip if parent and child summary isn't equal" do assert {:ok, res} = EnsureRePrepended.filter(message) assert res == message end + + test "it skips if the object is only a reference" do + message = %{ + "type" => "Create", + "object" => "somereference" + } + + assert {:ok, res} = EnsureRePrepended.filter(message) + assert res == message + end end end From 2e27847573e91946e6777f9aa18b9cf9ccaf820d Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 29 Jul 2020 14:02:02 +0200 Subject: [PATCH 29/29] feed/user_controller: Return 404 when the user is remote --- lib/pleroma/web/feed/user_controller.ex | 3 ++- test/web/feed/user_controller_test.exs | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index d56f43818..9cd334a33 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -47,7 +47,7 @@ def feed(conn, %{"nickname" => nickname} = params) do "atom" end - with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do + with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do activities = %{ type: ["Create"], @@ -71,6 +71,7 @@ def errors(conn, {:error, :not_found}) do render_error(conn, :not_found, "Not found") end + def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found}) def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) def errors(conn, _) do diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs index fa2ed1ea5..0d2a61967 100644 --- a/test/web/feed/user_controller_test.exs +++ b/test/web/feed/user_controller_test.exs @@ -181,6 +181,17 @@ test "returns feed with public and unlisted activities", %{conn: conn} do assert activity_titles == ['public', 'unlisted'] end + + test "returns 404 when the user is remote", %{conn: conn} do + user = insert(:user, local: false) + + {:ok, _} = CommonAPI.post(user, %{status: "test"}) + + assert conn + |> put_req_header("accept", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(404) + end end # Note: see ActivityPubControllerTest for JSON format tests