Merge branch 'by-approval' into 'develop'
Registrations "by approval" mode Closes #1931 See merge request pleroma/pleroma!2757
This commit is contained in:
commit
79f9ddd8b7
|
@ -67,6 +67,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 pagination in emoji packs API (for packs and for files in pack)
|
||||||
- Support for viewing instances favicons next to posts and accounts
|
- 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.
|
- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
|
||||||
|
- "By approval" registrations mode.
|
||||||
- Configuration: Added `:welcome` settings for the welcome message to newly registered users.
|
- Configuration: Added `:welcome` settings for the welcome message to newly registered users.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -205,6 +205,7 @@
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
invites_enabled: false,
|
invites_enabled: false,
|
||||||
account_activation_required: false,
|
account_activation_required: false,
|
||||||
|
account_approval_required: false,
|
||||||
federating: true,
|
federating: true,
|
||||||
federation_incoming_replies_max_depth: 100,
|
federation_incoming_replies_max_depth: 100,
|
||||||
federation_reachability_timeout_days: 7,
|
federation_reachability_timeout_days: 7,
|
||||||
|
@ -237,6 +238,7 @@
|
||||||
max_remote_account_fields: 20,
|
max_remote_account_fields: 20,
|
||||||
account_field_name_length: 512,
|
account_field_name_length: 512,
|
||||||
account_field_value_length: 2048,
|
account_field_value_length: 2048,
|
||||||
|
registration_reason_length: 500,
|
||||||
external_user_synchronization: true,
|
external_user_synchronization: true,
|
||||||
extended_nickname_format: true,
|
extended_nickname_format: true,
|
||||||
cleanup_attachments: false,
|
cleanup_attachments: false,
|
||||||
|
|
|
@ -661,6 +661,11 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Require users to confirm their emails before signing in"
|
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,
|
key: :federating,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -874,6 +879,14 @@
|
||||||
2048
|
2048
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :registration_reason_length,
|
||||||
|
type: :integer,
|
||||||
|
description: "Maximum registration reason length. Default: 500.",
|
||||||
|
suggestions: [
|
||||||
|
500
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :external_user_synchronization,
|
key: :external_user_synchronization,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
|
|
@ -19,6 +19,7 @@ Configuration options:
|
||||||
- `local`: only local users
|
- `local`: only local users
|
||||||
- `external`: only external users
|
- `external`: only external users
|
||||||
- `active`: only active users
|
- `active`: only active users
|
||||||
|
- `need_approval`: only unapproved users
|
||||||
- `deactivated`: only deactivated users
|
- `deactivated`: only deactivated users
|
||||||
- `is_admin`: users with admin role
|
- `is_admin`: users with admin role
|
||||||
- `is_moderator`: users with moderator role
|
- `is_moderator`: users with moderator role
|
||||||
|
@ -46,7 +47,10 @@ Configuration options:
|
||||||
"local": bool,
|
"local": bool,
|
||||||
"tags": array,
|
"tags": array,
|
||||||
"avatar": string,
|
"avatar": string,
|
||||||
"display_name": string
|
"display_name": string,
|
||||||
|
"confirmation_pending": bool,
|
||||||
|
"approval_pending": bool,
|
||||||
|
"registration_reason": string,
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
@ -242,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`
|
## `GET /api/pleroma/admin/users/:nickname_or_id`
|
||||||
|
|
||||||
### Retrive the details of a user
|
### Retrive the details of a user
|
||||||
|
|
|
@ -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.
|
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
|
||||||
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: 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_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.
|
* `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_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.
|
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||||
|
@ -58,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`).
|
* `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_name_length`: An account field name maximum length (default: `512`).
|
||||||
* `account_field_value_length`: An account field value maximum length (default: `2048`).
|
* `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.
|
* `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.
|
* `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.
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do
|
||||||
import Swoosh.Email
|
import Swoosh.Email
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Web.Router.Helpers
|
alias Pleroma.Web.Router.Helpers
|
||||||
|
|
||||||
defp instance_config, do: Config.get(:instance)
|
defp instance_config, do: Config.get(:instance)
|
||||||
|
@ -82,4 +83,18 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
|> subject("#{instance_name()} Report")
|
|> subject("#{instance_name()} Report")
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_unapproved_registration(to, account) do
|
||||||
|
html_body = """
|
||||||
|
<p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
|
||||||
|
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
||||||
|
<a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -409,6 +409,17 @@ def get_log_entry_message(%ModerationLog{
|
||||||
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
|
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
|
||||||
end
|
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()
|
@spec get_log_entry_message(ModerationLog) :: String.t()
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
|
|
|
@ -42,7 +42,12 @@ defmodule Pleroma.User do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@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}
|
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
|
@ -106,6 +111,8 @@ defmodule Pleroma.User do
|
||||||
field(:locked, :boolean, default: false)
|
field(:locked, :boolean, default: false)
|
||||||
field(:confirmation_pending, :boolean, default: false)
|
field(:confirmation_pending, :boolean, default: false)
|
||||||
field(:password_reset_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(:confirmation_token, :string, default: nil)
|
||||||
field(:default_scope, :string, default: "public")
|
field(:default_scope, :string, default: "public")
|
||||||
field(:domain_blocks, {:array, :string}, default: [])
|
field(:domain_blocks, {:array, :string}, default: [])
|
||||||
|
@ -262,6 +269,7 @@ def binary_id(%User{} = user), do: binary_id(user.id)
|
||||||
@spec account_status(User.t()) :: account_status()
|
@spec account_status(User.t()) :: account_status()
|
||||||
def account_status(%User{deactivated: true}), do: :deactivated
|
def account_status(%User{deactivated: true}), do: :deactivated
|
||||||
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
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
|
def account_status(%User{confirmation_pending: true}) do
|
||||||
if Config.get([:instance, :account_activation_required]) do
|
if Config.get([:instance, :account_activation_required]) do
|
||||||
|
@ -633,6 +641,7 @@ def force_password_reset(user), do: update_password_reset_pending(user, true)
|
||||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Config.get([:instance, :user_name_length], 100)
|
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)
|
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||||
|
|
||||||
need_confirmation? =
|
need_confirmation? =
|
||||||
|
@ -642,8 +651,16 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
opts[:need_confirmation]
|
opts[:need_confirmation]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
need_approval? =
|
||||||
|
if is_nil(opts[:need_approval]) do
|
||||||
|
Config.get([:instance, :account_approval_required])
|
||||||
|
else
|
||||||
|
opts[:need_approval]
|
||||||
|
end
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
||||||
|
|> approval_changeset(need_approval: need_approval?)
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
:bio,
|
:bio,
|
||||||
:raw_bio,
|
:raw_bio,
|
||||||
|
@ -653,7 +670,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
:password,
|
:password,
|
||||||
:password_confirmation,
|
:password_confirmation,
|
||||||
:emoji,
|
:emoji,
|
||||||
:accepts_chat_messages
|
:accepts_chat_messages,
|
||||||
|
:registration_reason
|
||||||
])
|
])
|
||||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_confirmation(:password)
|
||||||
|
@ -664,6 +682,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:email, @email_regex)
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|
|> validate_length(:registration_reason, max: reason_limit)
|
||||||
|> maybe_validate_required_email(opts[:external])
|
|> maybe_validate_required_email(opts[:external])
|
||||||
|> put_password_hash
|
|> put_password_hash
|
||||||
|> put_ap_id()
|
|> put_ap_id()
|
||||||
|
@ -1494,6 +1513,19 @@ def deactivate(%User{} = user, status) do
|
||||||
end
|
end
|
||||||
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
|
def update_notification_settings(%User{} = user, settings) do
|
||||||
user
|
user
|
||||||
|> cast(%{notification_settings: settings}, [])
|
|> cast(%{notification_settings: settings}, [])
|
||||||
|
@ -1520,12 +1552,17 @@ defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate
|
||||||
defp delete_or_deactivate(%User{local: true} = user) do
|
defp delete_or_deactivate(%User{local: true} = user) do
|
||||||
status = account_status(user)
|
status = account_status(user)
|
||||||
|
|
||||||
if status == :confirmation_pending do
|
case status do
|
||||||
delete_and_invalidate_cache(user)
|
:confirmation_pending ->
|
||||||
else
|
delete_and_invalidate_cache(user)
|
||||||
user
|
|
||||||
|> change(%{deactivated: true, email: nil})
|
:approval_pending ->
|
||||||
|> update_and_set_cache()
|
delete_and_invalidate_cache(user)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
user
|
||||||
|
|> change(%{deactivated: true, email: nil})
|
||||||
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2178,6 +2215,12 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do
|
||||||
cast(user, params, [:confirmation_pending, :confirmation_token])
|
cast(user, params, [:confirmation_pending, :confirmation_token])
|
||||||
end
|
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
|
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
|
||||||
if id not in user.pinned_activities do
|
if id not in user.pinned_activities do
|
||||||
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
|
||||||
|
|
|
@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do
|
||||||
external: boolean(),
|
external: boolean(),
|
||||||
active: boolean(),
|
active: boolean(),
|
||||||
deactivated: boolean(),
|
deactivated: boolean(),
|
||||||
|
need_approval: boolean(),
|
||||||
is_admin: boolean(),
|
is_admin: boolean(),
|
||||||
is_moderator: boolean(),
|
is_moderator: boolean(),
|
||||||
super_users: boolean(),
|
super_users: boolean(),
|
||||||
|
@ -146,6 +147,10 @@ defp compose_query({:deactivated, true}, query) do
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp compose_query({:need_approval, _}, query) do
|
||||||
|
where(query, [u], u.approval_pending)
|
||||||
|
end
|
||||||
|
|
||||||
defp compose_query({:followers, %User{id: id}}, query) do
|
defp compose_query({:followers, %User{id: id}}, query) do
|
||||||
query
|
query
|
||||||
|> where([u], u.id != ^id)
|
|> where([u], u.id != ^id)
|
||||||
|
|
|
@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
:user_toggle_activation,
|
:user_toggle_activation,
|
||||||
:user_activate,
|
:user_activate,
|
||||||
:user_deactivate,
|
:user_deactivate,
|
||||||
|
:user_approve,
|
||||||
:tag_users,
|
:tag_users,
|
||||||
:untag_users,
|
:untag_users,
|
||||||
:right_add,
|
:right_add,
|
||||||
|
@ -303,6 +304,21 @@ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nickname
|
||||||
|> render("index.json", %{users: Keyword.values(updated_users)})
|
|> render("index.json", %{users: Keyword.values(updated_users)})
|
||||||
end
|
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
|
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
|
||||||
with {:ok, _} <- User.tag(nicknames, tags) do
|
with {:ok, _} <- User.tag(nicknames, tags) do
|
||||||
ModerationLog.insert_log(%{
|
ModerationLog.insert_log(%{
|
||||||
|
@ -354,7 +370,7 @@ def list_users(conn, params) do
|
||||||
end
|
end
|
||||||
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} | %{}
|
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
|
||||||
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
|
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
|
||||||
|
|
|
@ -77,7 +77,9 @@ def render("show.json", %{user: user}) do
|
||||||
"roles" => User.roles(user),
|
"roles" => User.roles(user),
|
||||||
"tags" => user.tags || [],
|
"tags" => user.tags || [],
|
||||||
"confirmation_pending" => user.confirmation_pending,
|
"confirmation_pending" => user.confirmation_pending,
|
||||||
"url" => user.uri || user.ap_id
|
"approval_pending" => user.approval_pending,
|
||||||
|
"url" => user.uri || user.ap_id,
|
||||||
|
"registration_reason" => user.registration_reason
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ def render("show.json", _) do
|
||||||
thumbnail: Keyword.get(instance, :instance_thumbnail),
|
thumbnail: Keyword.get(instance, :instance_thumbnail),
|
||||||
languages: ["en"],
|
languages: ["en"],
|
||||||
registrations: Keyword.get(instance, :registrations_open),
|
registrations: Keyword.get(instance, :registrations_open),
|
||||||
|
approval_required: Keyword.get(instance, :account_approval_required),
|
||||||
# Extra (not present in Mastodon):
|
# Extra (not present in Mastodon):
|
||||||
max_toot_chars: Keyword.get(instance, :limit),
|
max_toot_chars: Keyword.get(instance, :limit),
|
||||||
poll_limits: Keyword.get(instance, :poll_limits),
|
poll_limits: Keyword.get(instance, :poll_limits),
|
||||||
|
|
|
@ -337,6 +337,16 @@ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirm
|
||||||
)
|
)
|
||||||
end
|
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
|
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
|
||||||
render_invalid_credentials_error(conn)
|
render_invalid_credentials_error(conn)
|
||||||
end
|
end
|
||||||
|
|
|
@ -138,6 +138,7 @@ defmodule Pleroma.Web.Router do
|
||||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||||
patch("/users/activate", AdminAPIController, :user_activate)
|
patch("/users/activate", AdminAPIController, :user_activate)
|
||||||
patch("/users/deactivate", AdminAPIController, :user_deactivate)
|
patch("/users/deactivate", AdminAPIController, :user_deactivate)
|
||||||
|
patch("/users/approve", AdminAPIController, :user_approve)
|
||||||
put("/users/tag", AdminAPIController, :tag_users)
|
put("/users/tag", AdminAPIController, :tag_users)
|
||||||
delete("/users/tag", AdminAPIController, :untag_users)
|
delete("/users/tag", AdminAPIController, :untag_users)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ def register_user(params, opts \\ []) do
|
||||||
|> Map.put(:nickname, params[:username])
|
|> Map.put(:nickname, params[:username])
|
||||||
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|
||||||
|> Map.put(:password_confirmation, params[:password])
|
|> Map.put(:password_confirmation, params[:password])
|
||||||
|
|> Map.put(:registration_reason, params[:reason])
|
||||||
|
|
||||||
if Pleroma.Config.get([:instance, :registrations_open]) do
|
if Pleroma.Config.get([:instance, :registrations_open]) do
|
||||||
create_user(params, opts)
|
create_user(params, opts)
|
||||||
|
@ -44,6 +45,7 @@ defp create_user(params, opts) do
|
||||||
|
|
||||||
case User.register(changeset) do
|
case User.register(changeset) do
|
||||||
{:ok, user} ->
|
{:ok, user} ->
|
||||||
|
maybe_notify_admins(user)
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
|
@ -56,6 +58,18 @@ defp create_user(params, opts) do
|
||||||
end
|
end
|
||||||
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
|
def password_reset(nickname_or_email) do
|
||||||
with true <- is_binary(nickname_or_email),
|
with true <- is_binary(nickname_or_email),
|
||||||
%User{local: true, email: email} = user when is_binary(email) <-
|
%User{local: true, email: email} = user when is_binary(email) <-
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddApprovalFieldsToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:approval_pending, :boolean)
|
||||||
|
add(:registration_reason, :text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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.to == [{to_user.name, to_user.email}]
|
||||||
assert res.from == {config[:name], config[:notify_email]}
|
assert res.from == {config[:name], config[:notify_email]}
|
||||||
end
|
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 == """
|
||||||
|
<p>New account for review: <a href="#{account_url}">@#{account.nickname}</a></p>
|
||||||
|
<blockquote>Plz let me in</blockquote>
|
||||||
|
<a href="http://localhost:4001/pleroma/admin">Visit AdminFE</a>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -543,6 +543,46 @@ test "it creates confirmed user if :confirmed option is given" do
|
||||||
end
|
end
|
||||||
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",
|
||||||
|
registration_reason: "I'm a cool guy :)"
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
describe "get_or_fetch/1" do
|
describe "get_or_fetch/1" do
|
||||||
test "gets an existing user by nickname" do
|
test "gets an existing user by nickname" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -1208,6 +1248,31 @@ test "hide a user's statuses from timelines and notifications" do
|
||||||
end
|
end
|
||||||
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
|
describe "delete" do
|
||||||
setup do
|
setup do
|
||||||
{:ok, user} = insert(:user) |> User.set_cache()
|
{:ok, user} = insert(:user) |> User.set_cache()
|
||||||
|
@ -1295,6 +1360,17 @@ test "deactivates user when activation is not required", %{user: user} do
|
||||||
end
|
end
|
||||||
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
|
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")
|
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
|
||||||
end
|
end
|
||||||
|
@ -1369,6 +1445,14 @@ test "returns :deactivated for deactivated user" do
|
||||||
user = insert(:user, local: true, confirmation_pending: false, deactivated: true)
|
user = insert(:user, local: true, confirmation_pending: false, deactivated: true)
|
||||||
assert User.account_status(user) == :deactivated
|
assert User.account_status(user) == :deactivated
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "returns :approval_pending for unapproved user" do
|
||||||
|
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
|
end
|
||||||
|
|
||||||
describe "superuser?/1" do
|
describe "superuser?/1" do
|
||||||
|
|
|
@ -349,7 +349,9 @@ test "Show", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected == json_response(conn, 200)
|
assert expected == json_response(conn, 200)
|
||||||
|
@ -613,6 +615,8 @@ test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
|
||||||
describe "GET /api/pleroma/admin/users" do
|
describe "GET /api/pleroma/admin/users" do
|
||||||
test "renders users array for the first page", %{conn: conn, admin: admin} 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"])
|
||||||
|
user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?page=1")
|
conn = get(conn, "/api/pleroma/admin/users?page=1")
|
||||||
|
|
||||||
users =
|
users =
|
||||||
|
@ -627,7 +631,9 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
|
||||||
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"deactivated" => user.deactivated,
|
"deactivated" => user.deactivated,
|
||||||
|
@ -639,13 +645,29 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"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"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response(conn, 200) == %{
|
||||||
"count" => 2,
|
"count" => 3,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
}
|
}
|
||||||
|
@ -712,7 +734,9 @@ test "regular search", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -738,7 +762,9 @@ test "search by domain", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -764,7 +790,9 @@ test "search by full nickname", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -790,7 +818,9 @@ test "search by display name", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -816,7 +846,9 @@ test "search by email", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -842,7 +874,9 @@ test "regular search with page size", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -863,7 +897,9 @@ test "regular search with page size", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user2.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user2.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -896,7 +932,9 @@ test "only local users" do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -922,7 +960,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"deactivated" => admin.deactivated,
|
"deactivated" => admin.deactivated,
|
||||||
|
@ -934,7 +974,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do
|
||||||
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"deactivated" => false,
|
"deactivated" => false,
|
||||||
|
@ -946,7 +988,9 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do
|
||||||
"avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => old_admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => old_admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
@ -958,6 +1002,44 @@ test "only local users with no query", %{conn: conn, admin: old_admin} do
|
||||||
}
|
}
|
||||||
end
|
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
|
test "load only admins", %{conn: conn, admin: admin} do
|
||||||
second_admin = insert(:user, is_admin: true)
|
second_admin = insert(:user, is_admin: true)
|
||||||
insert(:user)
|
insert(:user)
|
||||||
|
@ -977,7 +1059,9 @@ test "load only admins", %{conn: conn, admin: admin} do
|
||||||
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"deactivated" => false,
|
"deactivated" => false,
|
||||||
|
@ -989,7 +1073,9 @@ test "load only admins", %{conn: conn, admin: admin} do
|
||||||
"avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => second_admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => second_admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
@ -1022,7 +1108,9 @@ test "load only moderators", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => moderator.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => moderator.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1048,7 +1136,9 @@ test "load users with tags list", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user1) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user1.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user1.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
"deactivated" => false,
|
"deactivated" => false,
|
||||||
|
@ -1060,7 +1150,9 @@ test "load users with tags list", %{conn: conn} do
|
||||||
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user2.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user2.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
@ -1100,7 +1192,9 @@ test "it works with multiple filters" do
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1125,7 +1219,9 @@ test "it omits relay user", %{admin: admin, conn: conn} do
|
||||||
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => admin.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => admin.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1172,6 +1268,26 @@ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
|
||||||
"@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
|
"@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
|
||||||
end
|
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
|
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -1188,7 +1304,9 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admi
|
||||||
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
|
"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,
|
"confirmation_pending" => false,
|
||||||
"url" => user.ap_id
|
"approval_pending" => false,
|
||||||
|
"url" => user.ap_id,
|
||||||
|
"registration_reason" => nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
|
|
@ -166,5 +166,16 @@ test "it returns user by email" do
|
||||||
assert total == 3
|
assert total == 3
|
||||||
assert count == 1
|
assert count == 1
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -904,6 +904,7 @@ test "blocking / unblocking a user" do
|
||||||
end
|
end
|
||||||
|
|
||||||
setup do: clear_config([:instance, :account_activation_required])
|
setup do: clear_config([:instance, :account_activation_required])
|
||||||
|
setup do: clear_config([:instance, :account_approval_required])
|
||||||
|
|
||||||
test "Account registration via Application", %{conn: conn} do
|
test "Account registration via Application", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
|
@ -968,6 +969,75 @@ test "Account registration via Application", %{conn: conn} do
|
||||||
assert token_from_db.user.confirmation_pending
|
assert token_from_db.user.confirmation_pending
|
||||||
end
|
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
|
test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
|
||||||
_user = insert(:user, email: "lain@example.org")
|
_user = insert(:user, email: "lain@example.org")
|
||||||
app_token = insert(:oauth_token, user: nil)
|
app_token = insert(:oauth_token, user: nil)
|
||||||
|
|
|
@ -27,6 +27,7 @@ test "get instance information", %{conn: conn} do
|
||||||
"thumbnail" => _,
|
"thumbnail" => _,
|
||||||
"languages" => _,
|
"languages" => _,
|
||||||
"registrations" => _,
|
"registrations" => _,
|
||||||
|
"approval_required" => _,
|
||||||
"poll_limits" => _,
|
"poll_limits" => _,
|
||||||
"upload_limit" => _,
|
"upload_limit" => _,
|
||||||
"avatar_upload_limit" => _,
|
"avatar_upload_limit" => _,
|
||||||
|
|
|
@ -19,7 +19,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
||||||
key: "_test",
|
key: "_test",
|
||||||
signing_salt: "cooldude"
|
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
|
describe "in OAuth consumer mode, " do
|
||||||
setup do
|
setup do
|
||||||
|
@ -995,6 +998,30 @@ test "rejects token exchange for user with confirmation_pending set to true" do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
test "rejects an invalid authorization code" do
|
||||||
app = insert(:oauth_app)
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
|
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -79,6 +79,42 @@ test "it sends confirmation email if :account_activation_required is specified i
|
||||||
)
|
)
|
||||||
end
|
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
|
test "it registers a new user and parses mentions in the bio" do
|
||||||
data1 = %{
|
data1 = %{
|
||||||
:username => "john",
|
:username => "john",
|
||||||
|
|
Loading…
Reference in a new issue