1
0
Fork 0
forked from mirrors/akkoma

Merge remote-tracking branch 'origin/develop' into feature/account-export

This commit is contained in:
Egor Kislitsyn 2020-10-30 19:34:02 +04:00
commit d2113428c0
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
37 changed files with 1883 additions and 1731 deletions

View file

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
- Pleroma API: Importing the mutes users from CSV files.
- Experimental websocket-based federation between Pleroma instances.
- Support pagination of blocks and mutes
- App metrics: ability to restrict access to specified IP whitelist.
- Account backup
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
@ -36,6 +37,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: Importing the mutes users from CSV files.
- Admin API: Importing emoji from a zip file
- Pleroma API: Pagination for remote/local packs and emoji.
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
</details>
@ -53,6 +56,7 @@ switched to a new configuration mechanism, however it was not officially removed
- Allow sending out emails again.
- Allow sending chat messages to yourself.
- Fix remote users with a whitespace name.
- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
## Unreleased (Patch)

View file

@ -1757,28 +1757,37 @@
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
label: "MRF Keyword",
type: :group,
description: "Reject or Word-Replace messages with a keyword or regex",
description:
"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
children: [
%{
key: :reject,
type: {:list, :string},
description:
"A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
description: """
A list of patterns which result in message being rejected.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :federated_timeline_removal,
type: {:list, :string},
description:
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
description: """
A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :replace,
type: {:list, :tuple},
description:
"A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
description: """
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
**Replacement**: a string. Leaving the field empty is permitted.
"""
}
]
},

View file

@ -20,12 +20,14 @@ Configuration options:
- `external`: only external users
- `active`: only active users
- `need_approval`: only unapproved users
- `unconfirmed`: only unconfirmed users
- `deactivated`: only deactivated users
- `is_admin`: users with admin role
- `is_moderator`: users with moderator role
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of users per page (default is `50`)
- *optional* `tags`: **[string]** tags list
- *optional* `actor_types`: **[string]** actor type list (`Person`, `Service`, `Application`)
- *optional* `name`: **string** user display name
- *optional* `email`: **string** user email
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`

View file

@ -1,12 +1,23 @@
# Managing frontends
`mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]`
=== "OTP"
```sh
./bin/pleroma_ctl frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
```
=== "From Source"
```sh
mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
```
Frontend can be installed either from local zip file, or automatically downloaded from the web.
You can give all the options directly on the command like, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
You can give all the options directly on the command line, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
Currently, known `<frontend>` values are:
Currently known `<frontend>` values are:
- [admin-fe](https://git.pleroma.social/pleroma/admin-fe)
- [kenoma](http://git.pleroma.social/lambadalambda/kenoma)
- [pleroma-fe](http://git.pleroma.social/pleroma/pleroma-fe)
@ -19,51 +30,67 @@ You can still install frontends that are not configured, see below.
For a frontend configured under the `available` key, it's enough to install it by name.
```sh tab="OTP"
./bin/pleroma_ctl frontend install pleroma
```
=== "OTP"
```sh tab="From Source"
mix pleroma.frontend install pleroma
```
```sh
./bin/pleroma_ctl frontend install pleroma
```
This will download the latest build for the the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
=== "From Source"
You can override any of the details. To install a pleroma build from a different url, you could do this:
```sh
mix pleroma.frontend install pleroma
```
```sh tab="OPT"
./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
```sh tab="From Source"
mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
You can override any of the details. To install a pleroma build from a different URL, you could do this:
=== "OTP"
```sh
./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
=== "From Source"
```sh
mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
```
Similarly, you can also install from a local zip file.
```sh tab="OTP"
./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
=== "OTP"
```sh tab="From Source"
mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
```sh
./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`
=== "From Source"
Careful: This folder will be completely replaced on installation
```sh
mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
```
The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`.
Careful: This folder will be completely replaced on installation.
## Example installation for an unknown frontend
The installation process is the same, but you will have to give all the needed options on the commond line. For example:
The installation process is the same, but you will have to give all the needed options on the command line. For example:
```sh tab="OTP"
./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
=== "OTP"
```sh tab="From Source"
mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
```sh
./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`
=== "From Source"
```sh
mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
```
If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`.

View file

@ -43,7 +43,7 @@ def get_for_ap_id(ap_id) do
def maybe_create_recipientships(participation, activity) do
participation = Repo.preload(participation, :recipients)
if participation.recipients |> Enum.empty?() do
if Enum.empty?(participation.recipients) do
recipients = User.get_all_by_ap_id(activity.recipients)
RecipientShip.create(recipients, participation)
end
@ -69,10 +69,6 @@ def create_or_bump_for(activity, opts \\ []) do
Enum.map(users, fn user ->
invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
unless invisible_conversation do
User.increment_unread_conversation_count(conversation, user)
end
opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
{:ok, participation} =

View file

@ -63,21 +63,10 @@ def mark_as_read(%User{} = user, %Conversation{} = conversation) do
end
end
def mark_as_read(participation) do
__MODULE__
|> where(id: ^participation.id)
|> update(set: [read: true])
|> select([p], p)
|> Repo.update_all([])
|> case do
{1, [participation]} ->
participation = Repo.preload(participation, :user)
User.set_unread_conversation_count(participation.user)
{:ok, participation}
error ->
error
end
def mark_as_read(%__MODULE__{} = participation) do
participation
|> change(read: true)
|> Repo.update()
end
def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
@ -93,7 +82,6 @@ def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
|> update([p], set: [read: true])
|> Repo.update_all([])
{:ok, user} = User.set_unread_conversation_count(user)
{:ok, user, []}
end
@ -108,7 +96,6 @@ def mark_all_as_read(%User{} = user) do
|> select([p], p)
|> Repo.update_all([])
{:ok, user} = User.set_unread_conversation_count(user)
{:ok, user, participations}
end
@ -220,6 +207,12 @@ def set_recipients(participation, user_ids) do
{:ok, Repo.preload(participation, :recipients, force: true)}
end
@spec unread_count(User.t()) :: integer()
def unread_count(%User{id: user_id}) do
from(q in __MODULE__, where: q.user_id == ^user_id and q.read == false)
|> Repo.aggregate(:count, :id)
end
def unread_conversation_count_for_user(user) do
from(p in __MODULE__,
where: p.user_id == ^user.id,

View file

@ -128,7 +128,6 @@ defmodule Pleroma.User do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
@ -1305,47 +1304,6 @@ def update_following_count(%User{local: true} = user) do
|> update_and_set_cache()
end
def set_unread_conversation_count(%User{local: true} = user) do
unread_query = Participation.unread_conversation_count_for_user(user)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
set: [unread_conversation_count: p.count]
)
|> where([u], u.id == ^user.id)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def set_unread_conversation_count(user), do: {:ok, user}
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
unread_query =
Participation.unread_conversation_count_for_user(user)
|> where([p], p.conversation_id == ^conversation.id)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
inc: [unread_conversation_count: 1]
)
|> where([u], u.id == ^user.id)
|> where([u, p], p.count == 0)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def increment_unread_conversation_count(_, user), do: {:ok, user}
@spec get_users_from_set([String.t()], keyword()) :: [User.t()]
def get_users_from_set(ap_ids, opts \\ []) do
local_only = Keyword.get(opts, :local_only, true)

View file

@ -43,6 +43,7 @@ defmodule Pleroma.User.Query do
active: boolean(),
deactivated: boolean(),
need_approval: boolean(),
unconfirmed: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
@ -55,7 +56,8 @@ defmodule Pleroma.User.Query do
ap_id: [String.t()],
order_by: term(),
select: term(),
limit: pos_integer()
limit: pos_integer(),
actor_types: [String.t()]
}
| map()
@ -114,6 +116,10 @@ defp compose_query({:is_admin, bool}, query) do
where(query, [u], u.is_admin == ^bool)
end
defp compose_query({:actor_types, actor_types}, query) when is_list(actor_types) do
where(query, [u], u.actor_type in ^actor_types)
end
defp compose_query({:is_moderator, bool}, query) do
where(query, [u], u.is_moderator == ^bool)
end
@ -156,6 +162,10 @@ defp compose_query({:need_approval, _}, query) do
where(query, [u], u.approval_pending)
end
defp compose_query({:unconfirmed, _}, query) do
where(query, [u], u.confirmation_pending)
end
defp compose_query({:followers, %User{id: id}}, query) do
query
|> where([u], u.id != ^id)

View file

@ -44,29 +44,30 @@ def is_direct?(activity) do
def is_list?(%{data: %{"listMessage" => _}}), do: true
def is_list?(_), do: false
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
@spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean()
def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true
def visible_for_user?(nil, _), do: false
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
def visible_for_user?(
%Activity{data: %{"listMessage" => list_ap_id}} = activity,
%User{} = user
) do
user.ap_id in activity.data["to"] ||
list_ap_id
|> Pleroma.List.get_by_ap_id()
|> Pleroma.List.member?(user)
end
def visible_for_user?(%{local: local} = activity, nil) do
cfg_key = if local, do: :local, else: :remote
if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
def visible_for_user?(%Activity{} = activity, nil) do
if restrict_unauthenticated_access?(activity),
do: false,
else: is_public?(activity)
end
def visible_for_user?(activity, user) do
def visible_for_user?(%Activity{} = activity, user) do
x = [user.ap_id | User.following(user)]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
is_public?(activity) || Enum.any?(x, &(&1 in y))
@ -82,6 +83,26 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
result
end
def restrict_unauthenticated_access?(%Activity{local: local}) do
restrict_unauthenticated_access_to_activity?(local)
end
def restrict_unauthenticated_access?(%Object{} = object) do
object
|> Object.local?()
|> restrict_unauthenticated_access_to_activity?()
end
def restrict_unauthenticated_access?(%User{} = user) do
User.visible_for(user, _reading_user = nil)
end
defp restrict_unauthenticated_access_to_activity?(local?) when is_boolean(local?) do
cfg_key = if local?, do: :local, else: :remote
Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key)
end
def get_visibility(object) do
to = object.data["to"] || []
cc = object.data["cc"] || []

View file

@ -5,7 +5,8 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, fetch_integer_param: 3]
alias Pleroma.Config
alias Pleroma.MFA
@ -13,24 +14,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.ModerationLogView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
require Logger
@users_page_size 50
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"], admin: true}
when action in [:list_users, :user_show, :right_get, :show_user_credentials, :create_backup]
when action in [:right_get, :show_user_credentials, :create_backup]
)
plug(
@ -39,12 +35,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
when action in [
:get_password_reset,
:force_password_reset,
:user_delete,
:users_create,
:user_toggle_activation,
:user_activate,
:user_deactivate,
:user_approve,
:tag_users,
:untag_users,
:right_add,
@ -56,12 +46,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:follows"], admin: true}
when action in [:user_follow, :user_unfollow]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], admin: true}
@ -97,132 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(AdminAPI.FallbackController)
def user_delete(conn, %{"nickname" => nickname}) do
user_delete(conn, %{"nicknames" => [nickname]})
end
def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users =
nicknames
|> Enum.map(&User.get_cached_by_nickname/1)
users
|> Enum.each(fn user ->
{:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
Pipeline.common_pipeline(delete_data, local: true)
end)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "delete"
})
json(conn, nicknames)
end
def user_follow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
}) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.follow(follower, followed)
ModerationLog.insert_log(%{
actor: admin,
followed: followed,
follower: follower,
action: "follow"
})
end
json(conn, "ok")
end
def user_unfollow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
}) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.unfollow(follower, followed)
ModerationLog.insert_log(%{
actor: admin,
followed: followed,
follower: follower,
action: "unfollow"
})
end
json(conn, "ok")
end
def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
changesets =
Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
user_data = %{
nickname: nickname,
name: nickname,
email: email,
password: password,
password_confirmation: password,
bio: "."
}
User.register_changeset(%User{}, user_data, need_confirmation: false)
end)
|> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
end)
case Pleroma.Repo.transaction(changesets) do
{:ok, users} ->
res =
users
|> Map.values()
|> Enum.map(fn user ->
{:ok, user} = User.post_register_action(user)
user
end)
|> Enum.map(&AccountView.render("created.json", %{user: &1}))
ModerationLog.insert_log(%{
actor: admin,
subjects: Map.values(users),
action: "create"
})
json(conn, res)
{:error, id, changeset, _} ->
res =
Enum.map(changesets.operations, fn
{current_id, {:changeset, _current_changeset, _}} when current_id == id ->
AccountView.render("create-error.json", %{changeset: changeset})
{_, {:changeset, current_changeset, _}} ->
AccountView.render("create-error.json", %{changeset: current_changeset})
end)
conn
|> put_status(:conflict)
|> json(res)
end
end
def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: user})
else
_ -> {:error, :not_found}
end
end
def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
@ -276,69 +134,6 @@ def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}
end
end
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
{:ok, updated_user} = User.deactivate(user, !user.deactivated)
action = if user.deactivated, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
subject: [user],
action: action
})
conn
|> put_view(AccountView)
|> render("show.json", %{user: updated_user})
end
def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.deactivate(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "activate"
})
conn
|> put_view(AccountView)
|> render("index.json", %{users: Keyword.values(updated_users)})
end
def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.deactivate(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "deactivate"
})
conn
|> put_view(AccountView)
|> 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(%{
@ -365,43 +160,6 @@ def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "
end
end
def list_users(conn, params) do
{page, page_size} = page_params(params)
filters = maybe_parse_filters(params["filters"])
search_params = %{
query: params["query"],
page: page,
page_size: page_size,
tags: params["tags"],
name: params["name"],
email: params["email"]
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
json(
conn,
AccountView.render("index.json",
users: users,
count: count,
page_size: page_size
)
)
end
end
@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: %{}
defp maybe_parse_filters(filters) do
filters
|> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1))
|> Map.new(&{String.to_existing_atom(&1), true})
end
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
"permission_group" => permission_group,
"nicknames" => nicknames
@ -693,24 +451,9 @@ def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname})
end
defp page_params(params) do
{get_page(params["page"]), get_page_size(params["page_size"])}
end
defp get_page(page_string) when is_nil(page_string), do: 1
defp get_page(page_string) do
case Integer.parse(page_string) do
{page, _} -> page
:error -> 1
end
end
defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
defp get_page_size(page_size_string) do
case Integer.parse(page_size_string) do
{page_size, _} -> page_size
:error -> @users_page_size
end
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @users_page_size)
}
end
end

View file

@ -0,0 +1,281 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [fetch_integer_param: 3]
alias Pleroma.ModerationLog
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Plugs.OAuthScopesPlug
@users_page_size 50
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"], admin: true}
when action in [:list, :show]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"], admin: true}
when action in [
:delete,
:create,
:toggle_activation,
:activate,
:deactivate,
:approve
]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:follows"], admin: true}
when action in [:follow, :unfollow]
)
action_fallback(AdminAPI.FallbackController)
def delete(conn, %{"nickname" => nickname}) do
delete(conn, %{"nicknames" => [nickname]})
end
def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
Enum.each(users, fn user ->
{:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
Pipeline.common_pipeline(delete_data, local: true)
end)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "delete"
})
json(conn, nicknames)
end
def follow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
}) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.follow(follower, followed)
ModerationLog.insert_log(%{
actor: admin,
followed: followed,
follower: follower,
action: "follow"
})
end
json(conn, "ok")
end
def unfollow(%{assigns: %{user: admin}} = conn, %{
"follower" => follower_nick,
"followed" => followed_nick
}) do
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.unfollow(follower, followed)
ModerationLog.insert_log(%{
actor: admin,
followed: followed,
follower: follower,
action: "unfollow"
})
end
json(conn, "ok")
end
def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
changesets =
Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
user_data = %{
nickname: nickname,
name: nickname,
email: email,
password: password,
password_confirmation: password,
bio: "."
}
User.register_changeset(%User{}, user_data, need_confirmation: false)
end)
|> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
end)
case Pleroma.Repo.transaction(changesets) do
{:ok, users} ->
res =
users
|> Map.values()
|> Enum.map(fn user ->
{:ok, user} = User.post_register_action(user)
user
end)
|> Enum.map(&AccountView.render("created.json", %{user: &1}))
ModerationLog.insert_log(%{
actor: admin,
subjects: Map.values(users),
action: "create"
})
json(conn, res)
{:error, id, changeset, _} ->
res =
Enum.map(changesets.operations, fn
{current_id, {:changeset, _current_changeset, _}} when current_id == id ->
AccountView.render("create-error.json", %{changeset: changeset})
{_, {:changeset, current_changeset, _}} ->
AccountView.render("create-error.json", %{changeset: current_changeset})
end)
conn
|> put_status(:conflict)
|> json(res)
end
end
def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: user})
else
_ -> {:error, :not_found}
end
end
def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
{:ok, updated_user} = User.deactivate(user, !user.deactivated)
action = if user.deactivated, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
subject: [user],
action: action
})
conn
|> put_view(AccountView)
|> render("show.json", %{user: updated_user})
end
def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.deactivate(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "activate"
})
conn
|> put_view(AccountView)
|> render("index.json", %{users: Keyword.values(updated_users)})
end
def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.deactivate(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "deactivate"
})
conn
|> put_view(AccountView)
|> render("index.json", %{users: Keyword.values(updated_users)})
end
def 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 list(conn, params) do
{page, page_size} = page_params(params)
filters = maybe_parse_filters(params["filters"])
search_params =
%{
query: params["query"],
page: page,
page_size: page_size,
tags: params["tags"],
name: params["name"],
email: params["email"],
actor_types: params["actor_types"]
}
|> Map.merge(filters)
with {:ok, users, count} <- Search.user(search_params) do
json(
conn,
AccountView.render("index.json",
users: users,
count: count,
page_size: page_size
)
)
end
end
@filters ~w(local external active deactivated need_approval unconfirmed 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: %{}
defp maybe_parse_filters(filters) do
filters
|> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1))
|> Map.new(&{String.to_existing_atom(&1), true})
end
defp page_params(params) do
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @users_page_size)
}
end
end

View file

@ -52,7 +52,7 @@ def render("credentials.json", %{user: user, for: for_user}) do
:skip_thread_containment,
:pleroma_settings_store,
:raw_fields,
:discoverable,
:is_discoverable,
:actor_type
])
|> Map.merge(%{

View file

@ -335,6 +335,7 @@ def mutes_operation do
operationId: "AccountController.mutes",
description: "Accounts the user has muted.",
security: [%{"oAuth" => ["follow", "read:mutes"]}],
parameters: pagination_params(),
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}
@ -348,6 +349,7 @@ def blocks_operation do
operationId: "AccountController.blocks",
description: "View your blocks. See also accounts/:id/{block,unblock}",
security: [%{"oAuth" => ["read:blocks"]}],
parameters: pagination_params(),
responses: %{
200 => Operation.response("Accounts", "application/json", array_of_accounts())
}

View file

@ -10,14 +10,14 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.Feed.FeedView
def feed(conn, params) do
unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
if Config.get!([:instance, :public]) do
render_feed(conn, params)
else
render_error(conn, :not_found, "Not found")
end
end
def render_feed(conn, %{"tag" => raw_tag} = params) do
defp render_feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
@ -36,12 +36,13 @@ def render_feed(conn, %{"tag" => raw_tag} = params) do
end
@spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
defp parse_tag(raw_tag) when is_binary(raw_tag) do
case Enum.reverse(String.split(raw_tag, ".")) do
[format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
_ -> {"rss", raw_tag}
defp parse_tag(raw_tag) do
case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do
[format | tag] when format in ["rss", "atom"] ->
{format, Enum.join(tag, ".")}
_ ->
{"atom", raw_tag}
end
end
defp parse_tag(raw_tag), do: {"rss", raw_tag}
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
@ -22,12 +23,7 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
with %{halted: false} = conn <-
Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn,
unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
) do
ActivityPubController.call(conn, :user)
end
ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
@ -36,25 +32,18 @@ def feed_redirect(conn, %{"nickname" => nickname}) do
end
end
def feed(conn, params) do
unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
render_feed(conn, params)
else
errors(conn, {:error, :not_found})
end
end
def render_feed(conn, %{"nickname" => nickname} = params) do
def feed(conn, %{"nickname" => nickname} = params) do
format = get_format(conn)
format =
if format in ["rss", "atom"] do
if format in ["atom", "rss"] do
format
else
"atom"
end
with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
{_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
activities =
%{
type: ["Create"],
@ -69,7 +58,7 @@ def render_feed(conn, %{"nickname" => nickname} = params) do
|> render("user.#{format}",
user: user,
activities: activities,
feed_config: Pleroma.Config.get([:feed])
feed_config: Config.get([:feed])
)
end
end
@ -81,6 +70,8 @@ def errors(conn, {:error, :not_found}) do
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, {:visibility, _}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end

View file

@ -442,15 +442,27 @@ def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
end
@doc "GET /api/v1/mutes"
def mutes(%{assigns: %{user: user}} = conn, _) do
users = User.muted_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
def mutes(%{assigns: %{user: user}} = conn, params) do
users =
user
|> User.muted_users_relation(_restrict_deactivated = true)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
conn
|> add_link_headers(users)
|> render("index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/blocks"
def blocks(%{assigns: %{user: user}} = conn, _) do
users = User.blocked_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
def blocks(%{assigns: %{user: user}} = conn, params) do
users =
user
|> User.blocked_users_relation(_restrict_deactivated = true)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
conn
|> add_link_headers(users)
|> render("index.json", users: users, for: user, as: :user)
end
@doc "GET /api/v1/endorsements"

View file

@ -388,7 +388,7 @@ defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{
data
|> Kernel.put_in(
[:pleroma, :unread_conversation_count],
user.unread_conversation_count
Pleroma.Conversation.Participation.unread_count(user)
)
end

View file

@ -16,10 +16,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.Router
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
)
plug(
RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@ -37,14 +33,12 @@ def object(%{assigns: %{format: format}} = conn, _params)
ActivityPubController.call(conn, :object)
end
def object(%{assigns: %{format: format}} = conn, _params) do
def object(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
case format do
_ -> redirect(conn, to: "/notice/#{activity.id}")
end
redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@ -59,13 +53,11 @@ def activity(%{assigns: %{format: format}} = conn, _params)
ActivityPubController.call(conn, :activity)
end
def activity(%{assigns: %{format: format}} = conn, _params) do
def activity(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
case format do
_ -> redirect(conn, to: "/notice/#{activity.id}")
end
redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@ -119,6 +111,7 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%Object{} = object <- Object.normalize(activity),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do

View file

@ -34,22 +34,26 @@ def init(opts) do
end
def call(conn, opts) do
frontend_type = Map.get(opts, :frontend_type, :primary)
path = file_path("", frontend_type)
if path do
conn
|> call_static(opts, path)
with false <- invalid_path?(conn.path_info),
frontend_type <- Map.get(opts, :frontend_type, :primary),
path when not is_nil(path) <- file_path("", frontend_type) do
call_static(conn, opts, path)
else
conn
_ ->
conn
end
end
defp call_static(conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
defp invalid_path?(list) do
invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
end
defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
defp invalid_path?([], _match), do: false
defp call_static(conn, opts, from) do
opts = Map.put(opts, :from, from)
Plug.Static.call(conn, opts)
end
end

View file

@ -5,6 +5,26 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
pipeline :accepts_html do
plug(:accepts, ["html"])
end
pipeline :accepts_html_xml do
plug(:accepts, ["html", "xml", "rss", "atom"])
end
pipeline :accepts_html_json do
plug(:accepts, ["html", "activity+json", "json"])
end
pipeline :accepts_html_xml_json do
plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
end
pipeline :accepts_xml_rss_atom do
plug(:accepts, ["xml", "rss", "atom"])
end
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
@ -128,19 +148,8 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through(:admin_api)
post("/backups", AdminAPIController, :create_backup)
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
put("/users/disable_mfa", AdminAPIController, :disable_mfa)
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :users_create)
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)
@ -163,6 +172,15 @@ defmodule Pleroma.Web.Router do
:right_delete_multiple
)
post("/users/follow", UserController, :follow)
post("/users/unfollow", UserController, :unfollow)
delete("/users", UserController, :delete)
post("/users", UserController, :create)
patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
patch("/users/activate", UserController, :activate)
patch("/users/deactivate", UserController, :deactivate)
patch("/users/approve", UserController, :approve)
get("/relay", RelayController, :index)
post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow)
@ -177,8 +195,8 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
get("/users", UserController, :list)
get("/users/:nickname", UserController, :show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
@ -225,6 +243,8 @@ defmodule Pleroma.Web.Router do
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
post("/backups", AdminAPIController, :create_backup)
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
@ -571,30 +591,43 @@ defmodule Pleroma.Web.Router do
)
end
pipeline :ostatus do
plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
plug(Pleroma.Web.Plugs.StaticFEPlug)
end
pipeline :oembed do
plug(:accepts, ["json", "xml"])
end
scope "/", Pleroma.Web do
pipe_through([:ostatus, :http_signature])
# Note: html format is supported only if static FE is enabled
# Note: http signature is only considered for json requests (no auth for non-json requests)
pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
# Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
end
scope "/", Pleroma.Web do
# Note: html format is supported only if static FE is enabled
# Note: http signature is only considered for json requests (no auth for non-json requests)
pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
end
scope "/", Pleroma.Web do
# Note: html format is supported only if static FE is enabled
pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
end
scope "/", Pleroma.Web do
pipe_through(:accepts_html)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
end
scope "/", Pleroma.Web do
pipe_through(:accepts_xml_rss_atom)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end

View file

@ -17,12 +17,96 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
)
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
@doc "Renders requested local public activity or public activities of requested user"
def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
timeline =
activity.object.data["context"]
|> ActivityPub.fetch_activities_for_context(%{})
|> Enum.reverse()
|> Enum.map(&represent(&1, &1.object.id == activity.object.id))
render(conn, "conversation.html", %{activities: timeline, meta: meta})
else
%Activity{object: %Object{data: data}} ->
conn
|> put_status(:found)
|> redirect(external: data["url"] || data["external_url"] || data["id"])
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
with {_, %User{local: true} = user} <-
{:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
{_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
meta = Metadata.build_tags(%{user: user})
params =
params
|> Map.take(@page_keys)
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
timeline =
user
|> ActivityPub.fetch_user_activities(_reading_user = nil, params)
|> Enum.map(&represent/1)
prev_page_id =
(params["min_id"] || params["max_id"]) &&
List.first(timeline) && List.first(timeline).id
next_page_id = List.last(timeline) && List.last(timeline).id
render(conn, "profile.html", %{
user: User.sanitize_html(user),
timeline: timeline,
prev_page_id: prev_page_id,
next_page_id: next_page_id,
meta: meta
})
else
_ ->
not_found(conn, "User not found.")
end
end
def show(%{assigns: %{object_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_create_by_object_ap_id_with_object(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{activity_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
do: name
@ -81,91 +165,6 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
}
end
def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
timeline =
activity.object.data["context"]
|> ActivityPub.fetch_activities_for_context(%{})
|> Enum.reverse()
|> Enum.map(&represent(&1, &1.object.id == activity.object.id))
render(conn, "conversation.html", %{activities: timeline, meta: meta})
else
%Activity{object: %Object{data: data}} ->
conn
|> put_status(:found)
|> redirect(external: data["url"] || data["external_url"] || data["id"])
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
case User.get_cached_by_nickname_or_id(username_or_id) do
%User{} = user ->
meta = Metadata.build_tags(%{user: user})
params =
params
|> Map.take(@page_keys)
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
timeline =
user
|> ActivityPub.fetch_user_activities(nil, params)
|> Enum.map(&represent/1)
prev_page_id =
(params["min_id"] || params["max_id"]) &&
List.first(timeline) && List.first(timeline).id
next_page_id = List.last(timeline) && List.last(timeline).id
render(conn, "profile.html", %{
user: User.sanitize_html(user),
timeline: timeline,
prev_page_id: prev_page_id,
next_page_id: next_page_id,
meta: meta
})
_ ->
not_found(conn, "User not found.")
end
end
def show(%{assigns: %{object_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_create_by_object_ap_id_with_object(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{activity_id: _}} = conn, _params) do
url = Helpers.url(conn) <> conn.request_path
case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)

View file

@ -0,0 +1,38 @@
defmodule Pleroma.Repo.Migrations.RemoveUnreadConversationCountFromUser do
use Ecto.Migration
import Ecto.Query
alias Pleroma.Repo
def up do
alter table(:users) do
remove_if_exists(:unread_conversation_count, :integer)
end
end
def down do
alter table(:users) do
add_if_not_exists(:unread_conversation_count, :integer, default: 0)
end
flush()
recalc_unread_conversation_count()
end
defp recalc_unread_conversation_count do
participations_subquery =
from(
p in "conversation_participations",
where: p.read == false,
group_by: p.user_id,
select: %{user_id: p.user_id, unread_conversation_count: count(p.id)}
)
from(
u in "users",
join: p in subquery(participations_subquery),
on: p.user_id == u.id,
update: [set: [unread_conversation_count: p.unread_conversation_count]]
)
|> Repo.update_all([])
end
end

View file

@ -0,0 +1,12 @@
defmodule Pleroma.Repo.Migrations.AddUnreadIndexToConversationParticipation do
use Ecto.Migration
def change do
create(
index(:conversation_participations, [:user_id],
where: "read = false",
name: "unread_conversation_participation_count_index"
)
)
end
end

View file

@ -37,9 +37,8 @@ test "for a new conversation or a reply, it doesn't mark the author's participat
[%{read: true}] = Participation.for_user(user)
[%{read: false} = participation] = Participation.for_user(other_user)
assert User.get_cached_by_id(user.id).unread_conversation_count == 0
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 1
assert Participation.unread_count(user) == 0
assert Participation.unread_count(other_user) == 1
{:ok, _} =
CommonAPI.post(other_user, %{
@ -54,8 +53,8 @@ test "for a new conversation or a reply, it doesn't mark the author's participat
[%{read: false}] = Participation.for_user(user)
[%{read: true}] = Participation.for_user(other_user)
assert User.get_cached_by_id(user.id).unread_conversation_count == 1
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
assert Participation.unread_count(user) == 1
assert Participation.unread_count(other_user) == 0
end
test "for a new conversation, it sets the recipents of the participation" do
@ -264,7 +263,7 @@ test "when the user blocks a recipient, the existing conversations with them are
assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
assert Participation.unread_count(blocker) == 4
{:ok, _user_relationship} = User.block(blocker, blocked)
@ -272,15 +271,15 @@ test "when the user blocks a recipient, the existing conversations with them are
assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 1
assert Participation.unread_count(blocker) == 1
# The conversation is not marked as read for the blocked user
assert [_, _, %{read: false}] = Participation.for_user(blocked)
assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
assert Participation.unread_count(blocker) == 1
# The conversation is not marked as read for the third user
assert [%{read: false}, _, _] = Participation.for_user(third_user)
assert User.get_cached_by_id(third_user.id).unread_conversation_count == 1
assert Participation.unread_count(third_user) == 1
end
test "the new conversation with the blocked user is not marked as unread " do
@ -298,7 +297,7 @@ test "the new conversation with the blocked user is not marked as unread " do
})
assert [%{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
assert Participation.unread_count(blocker) == 0
# When the blocked user is a recipient
{:ok, _direct2} =
@ -308,10 +307,10 @@ test "the new conversation with the blocked user is not marked as unread " do
})
assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
assert Participation.unread_count(blocker) == 0
assert [%{read: false}, _] = Participation.for_user(blocked)
assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
assert Participation.unread_count(blocked) == 1
end
test "the conversation with the blocked user is not marked as unread on a reply" do
@ -327,8 +326,8 @@ test "the conversation with the blocked user is not marked as unread on a reply"
{:ok, _user_relationship} = User.block(blocker, blocked)
assert [%{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
assert Participation.unread_count(blocker) == 0
assert [blocked_participation] = Participation.for_user(blocked)
# When it's a reply from the blocked user
@ -340,8 +339,8 @@ test "the conversation with the blocked user is not marked as unread on a reply"
})
assert [%{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
assert Participation.unread_count(blocker) == 0
assert [third_user_participation] = Participation.for_user(third_user)
# When it's a reply from the third user
@ -353,11 +352,12 @@ test "the conversation with the blocked user is not marked as unread on a reply"
})
assert [%{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
assert Participation.unread_count(blocker) == 0
# Marked as unread for the blocked user
assert [%{read: false}] = Participation.for_user(blocked)
assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
assert Participation.unread_count(blocked) == 1
end
end
end

View file

@ -156,21 +156,6 @@ test "it returns error when user is not found", %{conn: conn} do
assert response == "Not found"
end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
conn =
put_req_header(
conn,
"accept",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
)
ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
end
end
describe "mastodon compatibility routes" do
@ -338,18 +323,6 @@ test "cached purged after object deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found)
end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
note = insert(:note)
uuid = String.split(note.data["id"], "/") |> List.last()
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
end
end
describe "/activities/:uuid" do
@ -421,18 +394,6 @@ test "cached purged after activity deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found)
end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
activity = insert(:note_activity)
uuid = String.split(activity.data["id"], "/") |> List.last()
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
end
end
describe "/inbox" do
@ -893,15 +854,6 @@ test "it returns an announce activity in a collection", %{conn: conn} do
assert response(conn, 200) =~ announce_activity.data["object"]
end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
end
end
describe "POST /users/:nickname/outbox (C2S)" do

View file

@ -0,0 +1,971 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserControllerTest do
use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
import Mock
import Pleroma.Factory
alias Pleroma.Config
alias Pleroma.HTML
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
{:ok, %{admin: admin, token: token, conn: conn}}
end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:admin_token], "password123")
user = insert(:user)
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
assert json_response(conn, 200)
end
describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
%{admin: admin} do
user = insert(:user)
url = "/api/pleroma/admin/users/#{user.nickname}"
good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
bad_token3 = nil
for good_token <- [good_token1, good_token2, good_token3] do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, 200)
end
for good_token <- [good_token1, good_token2, good_token3] do
conn =
build_conn()
|> assign(:user, nil)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, :forbidden)
end
for bad_token <- [bad_token1, bad_token2, bad_token3] do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, bad_token)
|> get(url)
assert json_response(conn, :forbidden)
end
end
end
describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
test "GET /api/pleroma/admin/users/:nickname requires " <>
"read:accounts or admin:read:accounts or broader scope",
%{admin: admin} do
user = insert(:user)
url = "/api/pleroma/admin/users/#{user.nickname}"
good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
good_token5 = insert(:oauth_token, user: admin, scopes: ["read"])
good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5]
bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"])
bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
bad_token3 = nil
for good_token <- good_tokens do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, 200)
end
for good_token <- good_tokens do
conn =
build_conn()
|> assign(:user, nil)
|> assign(:token, good_token)
|> get(url)
assert json_response(conn, :forbidden)
end
for bad_token <- [bad_token1, bad_token2, bad_token3] do
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, bad_token)
|> get(url)
assert json_response(conn, :forbidden)
end
end
end
describe "DELETE /api/pleroma/admin/users" do
test "single user", %{admin: admin, conn: conn} do
clear_config([:instance, :federating], true)
user =
insert(:user,
avatar: %{"url" => [%{"href" => "https://someurl"}]},
banner: %{"url" => [%{"href" => "https://somebanner"}]},
bio: "Hello world!",
name: "A guy"
)
# Create some activities to check they got deleted later
follower = insert(:user)
{:ok, _} = CommonAPI.post(user, %{status: "test"})
{:ok, _, _, _} = CommonAPI.follow(user, follower)
{:ok, _, _, _} = CommonAPI.follow(follower, user)
user = Repo.get(User, user.id)
assert user.note_count == 1
assert user.follower_count == 1
assert user.following_count == 1
refute user.deactivated
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end,
perform: fn _, _ -> nil end do
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
ObanHelpers.perform_all()
assert User.get_by_nickname(user.nickname).deactivated
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deleted users: @#{user.nickname}"
assert json_response(conn, 200) == [user.nickname]
user = Repo.get(User, user.id)
assert user.deactivated
assert user.avatar == %{}
assert user.banner == %{}
assert user.note_count == 0
assert user.follower_count == 0
assert user.following_count == 0
assert user.bio == ""
assert user.name == nil
assert called(Pleroma.Web.Federator.publish(:_))
end
end
test "multiple users", %{admin: admin, conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users", %{
nicknames: [user_one.nickname, user_two.nickname]
})
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
response = json_response(conn, 200)
assert response -- [user_one.nickname, user_two.nickname] == []
end
end
describe "/api/pleroma/admin/users" do
test "Create", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "lain",
"email" => "lain@example.org",
"password" => "test"
},
%{
"nickname" => "lain2",
"email" => "lain2@example.org",
"password" => "test"
}
]
})
response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
assert response == ["success", "success"]
log_entry = Repo.one(ModerationLog)
assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
end
test "Cannot create user with existing email", %{conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "lain",
"email" => user.email,
"password" => "test"
}
]
})
assert json_response(conn, 409) == [
%{
"code" => 409,
"data" => %{
"email" => user.email,
"nickname" => "lain"
},
"error" => "email has already been taken",
"type" => "error"
}
]
end
test "Cannot create user with existing nickname", %{conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => user.nickname,
"email" => "someuser@plerama.social",
"password" => "test"
}
]
})
assert json_response(conn, 409) == [
%{
"code" => 409,
"data" => %{
"email" => "someuser@plerama.social",
"nickname" => user.nickname
},
"error" => "nickname has already been taken",
"type" => "error"
}
]
end
test "Multiple user creation works in transaction", %{conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "newuser",
"email" => "newuser@pleroma.social",
"password" => "test"
},
%{
"nickname" => "lain",
"email" => user.email,
"password" => "test"
}
]
})
assert json_response(conn, 409) == [
%{
"code" => 409,
"data" => %{
"email" => user.email,
"nickname" => "lain"
},
"error" => "email has already been taken",
"type" => "error"
},
%{
"code" => 409,
"data" => %{
"email" => "newuser@pleroma.social",
"nickname" => "newuser"
},
"error" => "",
"type" => "error"
}
]
assert User.get_by_nickname("newuser") === nil
end
end
describe "/api/pleroma/admin/users/:nickname" do
test "Show", %{conn: conn} do
user = insert(:user)
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
assert user_response(user) == json_response(conn, 200)
end
test "when the user doesn't exist", %{conn: conn} do
user = build(:user)
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
assert %{"error" => "Not found"} == json_response(conn, 404)
end
end
describe "/api/pleroma/admin/users/follow" do
test "allows to force-follow another user", %{admin: admin, conn: conn} do
user = insert(:user)
follower = insert(:user)
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users/follow", %{
"follower" => follower.nickname,
"followed" => user.nickname
})
user = User.get_cached_by_id(user.id)
follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, user)
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
end
end
describe "/api/pleroma/admin/users/unfollow" do
test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
user = insert(:user)
follower = insert(:user)
User.follow(follower, user)
conn
|> put_req_header("accept", "application/json")
|> post("/api/pleroma/admin/users/unfollow", %{
"follower" => follower.nickname,
"followed" => user.nickname
})
user = User.get_cached_by_id(user.id)
follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, user)
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
end
end
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"])
user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
conn = get(conn, "/api/pleroma/admin/users?page=1")
users =
[
user_response(
admin,
%{"roles" => %{"admin" => true, "moderator" => false}}
),
user_response(user, %{"local" => false, "tags" => ["foo", "bar"]}),
user_response(
user2,
%{
"local" => true,
"approval_pending" => true,
"registration_reason" => "I'm a chill dude",
"actor_type" => "Person"
}
)
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{
"count" => 3,
"page_size" => 50,
"users" => users
}
end
test "pagination works correctly with service users", %{conn: conn} do
service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
insert_list(25, :user)
assert %{"count" => 26, "page_size" => 10, "users" => users1} =
conn
|> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
|> json_response(200)
assert Enum.count(users1) == 10
assert service1 not in users1
assert %{"count" => 26, "page_size" => 10, "users" => users2} =
conn
|> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
|> json_response(200)
assert Enum.count(users2) == 10
assert service1 not in users2
assert %{"count" => 26, "page_size" => 10, "users" => users3} =
conn
|> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
|> json_response(200)
assert Enum.count(users3) == 6
assert service1 not in users3
end
test "renders empty array for the second page", %{conn: conn} do
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?page=2")
assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => []
}
end
test "regular search", %{conn: conn} do
user = insert(:user, nickname: "bob")
conn = get(conn, "/api/pleroma/admin/users?query=bo")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user, %{"local" => true})]
}
end
test "search by domain", %{conn: conn} do
user = insert(:user, nickname: "nickname@domain.com")
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "search by full nickname", %{conn: conn} do
user = insert(:user, nickname: "nickname@domain.com")
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "search by display name", %{conn: conn} do
user = insert(:user, name: "Display name")
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?name=display")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "search by email", %{conn: conn} do
user = insert(:user, email: "email@example.com")
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "regular search with page size", %{conn: conn} do
user = insert(:user, nickname: "aalice")
user2 = insert(:user, nickname: "alice")
conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
assert json_response(conn1, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [user_response(user)]
}
conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
assert json_response(conn2, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [user_response(user2)]
}
end
test "only local users" do
admin = insert(:user, is_admin: true, nickname: "john")
token = insert(:oauth_admin_token, user: admin)
user = insert(:user, nickname: "bob")
insert(:user, nickname: "bobb", local: false)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
|> get("/api/pleroma/admin/users?query=bo&filters=local")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "only local users with no query", %{conn: conn, admin: old_admin} do
admin = insert(:user, is_admin: true, nickname: "john")
user = insert(:user, nickname: "bob")
insert(:user, nickname: "bobb", local: false)
conn = get(conn, "/api/pleroma/admin/users?filters=local")
users =
[
user_response(user),
user_response(admin, %{
"roles" => %{"admin" => true, "moderator" => false}
}),
user_response(old_admin, %{
"deactivated" => false,
"roles" => %{"admin" => true, "moderator" => false}
})
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{
"count" => 3,
"page_size" => 50,
"users" => users
}
end
test "only unconfirmed users", %{conn: conn} do
sad_user = insert(:user, nickname: "sadboy", confirmation_pending: true)
old_user = insert(:user, nickname: "oldboy", confirmation_pending: true)
insert(:user, nickname: "happyboy", approval_pending: false)
insert(:user, confirmation_pending: false)
result =
conn
|> get("/api/pleroma/admin/users?filters=unconfirmed")
|> json_response(200)
users =
Enum.map([old_user, sad_user], fn user ->
user_response(user, %{
"confirmation_pending" => true,
"approval_pending" => false
})
end)
|> Enum.sort_by(& &1["nickname"])
assert result == %{"count" => 2, "page_size" => 50, "users" => users}
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 = [
user_response(
user,
%{"approval_pending" => true, "registration_reason" => "Plz let me in!"}
)
]
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)
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
users =
[
user_response(admin, %{
"deactivated" => false,
"roles" => %{"admin" => true, "moderator" => false}
}),
user_response(second_admin, %{
"deactivated" => false,
"roles" => %{"admin" => true, "moderator" => false}
})
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => users
}
end
test "load only moderators", %{conn: conn} do
moderator = insert(:user, is_moderator: true)
insert(:user)
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
user_response(moderator, %{
"deactivated" => false,
"roles" => %{"admin" => false, "moderator" => true}
})
]
}
end
test "load users with actor_type is Person", %{admin: admin, conn: conn} do
insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> get(user_path(conn, :list), %{actor_types: ["Person"]})
|> json_response(200)
users =
[
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}),
user_response(user1),
user_response(user2)
]
|> Enum.sort_by(& &1["nickname"])
assert response == %{"count" => 3, "page_size" => 50, "users" => users}
end
test "load users with actor_type is Person and Service", %{admin: admin, conn: conn} do
user_service = insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> get(user_path(conn, :list), %{actor_types: ["Person", "Service"]})
|> json_response(200)
users =
[
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}),
user_response(user1),
user_response(user2),
user_response(user_service, %{"actor_type" => "Service"})
]
|> Enum.sort_by(& &1["nickname"])
assert response == %{"count" => 4, "page_size" => 50, "users" => users}
end
test "load users with actor_type is Service", %{conn: conn} do
user_service = insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
insert(:user)
insert(:user)
response =
conn
|> get(user_path(conn, :list), %{actor_types: ["Service"]})
|> json_response(200)
users = [user_response(user_service, %{"actor_type" => "Service"})]
assert response == %{"count" => 1, "page_size" => 50, "users" => users}
end
test "load users with tags list", %{conn: conn} do
user1 = insert(:user, tags: ["first"])
user2 = insert(:user, tags: ["second"])
insert(:user)
insert(:user)
conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
users =
[
user_response(user1, %{"tags" => ["first"]}),
user_response(user2, %{"tags" => ["second"]})
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => users
}
end
test "`active` filters out users pending approval", %{token: token} do
insert(:user, approval_pending: true)
%{id: user_id} = insert(:user, approval_pending: false)
%{id: admin_id} = token.user
conn =
build_conn()
|> assign(:user, token.user)
|> assign(:token, token)
|> get("/api/pleroma/admin/users?filters=active")
assert %{
"count" => 2,
"page_size" => 50,
"users" => [
%{"id" => ^admin_id},
%{"id" => ^user_id}
]
} = json_response(conn, 200)
end
test "it works with multiple filters" do
admin = insert(:user, nickname: "john", is_admin: true)
token = insert(:oauth_admin_token, user: admin)
user = insert(:user, nickname: "bob", local: false, deactivated: true)
insert(:user, nickname: "ken", local: true, deactivated: true)
insert(:user, nickname: "bobb", local: false, deactivated: false)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
|> get("/api/pleroma/admin/users?filters=deactivated,external")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
end
test "it omits relay user", %{admin: admin, conn: conn} do
assert %User{} = Relay.get_actor()
conn = get(conn, "/api/pleroma/admin/users")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}})
]
}
end
end
test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
user_one = insert(:user, deactivated: true)
user_two = insert(:user, deactivated: true)
conn =
patch(
conn,
"/api/pleroma/admin/users/activate",
%{nicknames: [user_one.nickname, user_two.nickname]}
)
response = json_response(conn, 200)
assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
end
test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
user_one = insert(:user, deactivated: false)
user_two = insert(:user, deactivated: false)
conn =
patch(
conn,
"/api/pleroma/admin/users/deactivate",
%{nicknames: [user_one.nickname, user_two.nickname]}
)
response = json_response(conn, 200)
assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{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)
conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
assert json_response(conn, 200) ==
user_response(
user,
%{"deactivated" => !user.deactivated}
)
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deactivated users: @#{user.nickname}"
end
defp user_response(user, attrs \\ %{}) do
%{
"deactivated" => user.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user.local,
"tags" => [],
"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,
"actor_type" => "Person"
}
|> Map.merge(attrs)
end
end

View file

@ -143,6 +143,20 @@ test "it returns users with tags" do
assert user2 in users
end
test "it returns users by actor_types" do
user_service = insert(:user, actor_type: "Service")
user_application = insert(:user, actor_type: "Application")
user1 = insert(:user)
user2 = insert(:user)
{:ok, [^user_service], 1} = Search.user(%{actor_types: ["Service"]})
{:ok, [^user_application], 1} = Search.user(%{actor_types: ["Application"]})
{:ok, [^user1, ^user2], 2} = Search.user(%{actor_types: ["Person"]})
{:ok, [^user_service, ^user1, ^user2], 3} =
Search.user(%{actor_types: ["Person", "Service"]})
end
test "it returns user by display name" do
user = insert(:user, name: "Display name")
insert(:user)
@ -178,6 +192,17 @@ test "it returns unapproved user" do
assert count == 1
end
test "it returns unconfirmed user" do
unconfirmed = insert(:user, confirmation_pending: true)
insert(:user)
insert(:user)
{:ok, _results, total} = Search.user()
{:ok, [^unconfirmed], count} = Search.user(%{unconfirmed: true})
assert total == 3
assert count == 1
end
test "it returns non-discoverable users" do
insert(:user)
insert(:user, is_discoverable: false)

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
import Pleroma.Factory
import SweetXml
alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Feed.FeedView
@ -15,7 +16,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
setup do: clear_config([:feed])
test "gets a feed (ATOM)", %{conn: conn} do
Pleroma.Config.put(
Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
@ -82,7 +83,7 @@ test "gets a feed (ATOM)", %{conn: conn} do
end
test "gets a feed (RSS)", %{conn: conn} do
Pleroma.Config.put(
Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
@ -157,7 +158,7 @@ test "gets a feed (RSS)", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/rss+xml")
|> get(tag_feed_path(conn, :feed, "pleromaart"))
|> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
|> response(200)
xml = parse(response)
@ -183,14 +184,12 @@ test "gets a feed (RSS)", %{conn: conn} do
end
describe "private instance" do
setup do: clear_config([:instance, :public])
setup do: clear_config([:instance, :public], false)
test "returns 404 for tags feed", %{conn: conn} do
Config.put([:instance, :public], false)
conn
|> put_req_header("accept", "application/rss+xml")
|> get(tag_feed_path(conn, :feed, "pleromaart"))
|> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
|> response(404)
end
end

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
setup do: clear_config([:instance, :federating], true)
setup do: clear_config([:static_fe, :enabled], false)
describe "feed" do
setup do: clear_config([:feed])
@ -192,6 +192,16 @@ test "returns 404 when the user is remote", %{conn: conn} do
|> get(user_feed_path(conn, :feed, user.nickname))
|> response(404)
end
test "does not require authentication on non-federating instances", %{conn: conn} do
clear_config([:instance, :federating], false)
user = insert(:user)
conn
|> put_req_header("accept", "application/rss+xml")
|> get("/users/#{user.nickname}/feed.rss")
|> response(200)
end
end
# Note: see ActivityPubControllerTest for JSON format tests

View file

@ -1509,28 +1509,103 @@ test "returns an empty list on a bad request", %{conn: conn} do
test "getting a list of mutes" do
%{user: user, conn: conn} = oauth_access(["read:mutes"])
other_user = insert(:user)
%{id: id1} = other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user)
%{id: id3} = other_user3 = insert(:user)
{:ok, _user_relationships} = User.mute(user, other_user)
{:ok, _user_relationships} = User.mute(user, other_user1)
{:ok, _user_relationships} = User.mute(user, other_user2)
{:ok, _user_relationships} = User.mute(user, other_user3)
conn = get(conn, "/api/v1/mutes")
result =
conn
|> assign(:user, user)
|> get("/api/v1/mutes")
|> json_response_and_validate_schema(200)
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
assert [id1, id2, id3] == Enum.map(result, & &1["id"])
result =
conn
|> assign(:user, user)
|> get("/api/v1/mutes?limit=1")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id1}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/mutes?since_id=#{id1}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/mutes?since_id=#{id1}&max_id=#{id3}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/mutes?since_id=#{id1}&limit=1")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}] = result
end
test "getting a list of blocks" do
%{user: user, conn: conn} = oauth_access(["read:blocks"])
other_user = insert(:user)
%{id: id1} = other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user)
%{id: id3} = other_user3 = insert(:user)
{:ok, _user_relationship} = User.block(user, other_user)
{:ok, _user_relationship} = User.block(user, other_user1)
{:ok, _user_relationship} = User.block(user, other_user3)
{:ok, _user_relationship} = User.block(user, other_user2)
conn =
result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks")
|> json_response_and_validate_schema(200)
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
assert [id1, id2, id3] == Enum.map(result, & &1["id"])
result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks?limit=1")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id1}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks?since_id=#{id1}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks?since_id=#{id1}&max_id=#{id3}")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}] = result
result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks?since_id=#{id1}&limit=1")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^id2}] = result
end
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Conversation.Participation
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@ -28,10 +29,10 @@ test "returns correct conversations", %{
user_three: user_three,
conn: conn
} do
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
assert Participation.unread_count(user_two) == 0
{:ok, direct} = create_direct_message(user_one, [user_two, user_three])
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
assert Participation.unread_count(user_two) == 1
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
@ -59,7 +60,7 @@ test "returns correct conversations", %{
assert is_binary(res_id)
assert unread == false
assert res_last_status["id"] == direct.id
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
assert Participation.unread_count(user_one) == 0
end
test "observes limit params", %{
@ -134,8 +135,8 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
user_two = insert(:user)
{:ok, direct} = create_direct_message(user_one, [user_two])
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
assert Participation.unread_count(user_one) == 0
assert Participation.unread_count(user_two) == 1
user_two_conn =
build_conn()
@ -155,8 +156,8 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
|> post("/api/v1/conversations/#{direct_conversation_id}/read")
|> json_response_and_validate_schema(200)
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
assert Participation.unread_count(user_one) == 0
assert Participation.unread_count(user_two) == 0
# The conversation is marked as unread on reply
{:ok, _} =
@ -171,8 +172,8 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
|> get("/api/v1/conversations")
|> json_response_and_validate_schema(200)
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
assert Participation.unread_count(user_one) == 1
assert Participation.unread_count(user_two) == 0
# A reply doesn't increment the user's unread_conversation_count if the conversation is unread
{:ok, _} =
@ -182,8 +183,8 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
in_reply_to_status_id: direct.id
})
assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
assert Participation.unread_count(user_one) == 1
assert Participation.unread_count(user_two) == 0
end
test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do

View file

@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
import Pleroma.Factory
alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -21,7 +20,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
:ok
end
setup do: clear_config([:instance, :federating], true)
setup do: clear_config([:static_fe, :enabled], false)
describe "Mastodon compatibility routes" do
setup %{conn: conn} do
@ -215,15 +214,16 @@ test "404s a non-existing notice", %{conn: conn} do
assert response(conn, 404)
end
test "it requires authentication if instance is NOT federating", %{
test "does not require authentication on non-federating instances", %{
conn: conn
} do
user = insert(:user)
clear_config([:instance, :federating], false)
note_activity = insert(:note_activity)
conn = put_req_header(conn, "accept", "text/html")
ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user)
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{note_activity.id}")
|> response(200)
end
end
@ -325,14 +325,16 @@ test "404s when attachment isn't audio or video", %{conn: conn} do
|> response(404)
end
test "it requires authentication if instance is NOT federating", %{
test "does not require authentication on non-federating instances", %{
conn: conn,
note_activity: note_activity
} do
user = insert(:user)
conn = put_req_header(conn, "accept", "text/html")
clear_config([:instance, :federating], false)
ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user)
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{note_activity.id}/embed_player")
|> response(200)
end
end
end

View file

@ -121,7 +121,7 @@ test "POST /api/v1/pleroma/conversations/read" do
[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == false
assert Participation.get(participation1.id).read == false
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
assert Participation.unread_count(other_user) == 2
[%{"unread" => false}, %{"unread" => false}] =
conn
@ -131,6 +131,6 @@ test "POST /api/v1/pleroma/conversations/read" do
[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == true
assert Participation.get(participation1.id).read == true
assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
assert Participation.unread_count(other_user) == 0
end
end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
use Pleroma.Web.ConnCase
import Mock
@dir "test/tmp/instance_static"
@ -53,4 +54,24 @@ test "overrides existing static files for the `pleroma/admin` path", %{conn: con
index = get(conn, "/pleroma/admin/")
assert html_response(index, 200) == "from frontend plug"
end
test "exclude invalid path", %{conn: conn} do
name = "pleroma-fe"
ref = "dist"
clear_config([:media_proxy, :enabled], true)
clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
path = "#{@dir}/frontends/#{name}/#{ref}"
File.mkdir_p!("#{path}/proxy/rr/ss")
File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image")
url =
Pleroma.Web.MediaProxy.encode_url("https://pbs.twimg.com/media/Ek7w8WPVcAApOvN.jpg:large")
with_mock Pleroma.ReverseProxy,
call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
assert %Plug.Conn{status: :success} = get(conn, url)
end
end
end

View file

@ -6,14 +6,12 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup_all do: clear_config([:static_fe, :enabled], true)
setup do: clear_config([:instance, :federating], true)
setup %{conn: conn} do
conn = put_req_header(conn, "accept", "text/html")
@ -74,8 +72,27 @@ test "pagination, page 2", %{conn: conn, user: user} do
refute html =~ ">test29<"
end
test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user)
test "does not require authentication on non-federating instances", %{
conn: conn,
user: user
} do
clear_config([:instance, :federating], false)
conn = get(conn, "/users/#{user.nickname}")
assert html_response(conn, 200) =~ user.nickname
end
test "returns 404 for local user with `restrict_unauthenticated/profiles/local` setting", %{
conn: conn
} do
clear_config([:restrict_unauthenticated, :profiles, :local], true)
local_user = insert(:user, local: true)
conn
|> get("/users/#{local_user.nickname}")
|> html_response(404)
end
end
@ -187,10 +204,28 @@ test "302 for remote cached status", %{conn: conn, user: user} do
assert html_response(conn, 302) =~ "redirected"
end
test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
test "does not require authentication on non-federating instances", %{
conn: conn,
user: user
} do
clear_config([:instance, :federating], false)
{:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user)
conn = get(conn, "/notice/#{activity.id}")
assert html_response(conn, 200) =~ "testing a thing!"
end
test "returns 404 for local public activity with `restrict_unauthenticated/activities/local` setting",
%{conn: conn, user: user} do
clear_config([:restrict_unauthenticated, :activities, :local], true)
{:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
conn
|> get("/notice/#{activity.id}")
|> html_response(404)
end
end
end

View file

@ -112,28 +112,6 @@ defp json_response_and_validate_schema(
defp json_response_and_validate_schema(conn, _status) do
flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
end
defp ensure_federating_or_authenticated(conn, url, user) do
initial_setting = Config.get([:instance, :federating])
on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
Config.put([:instance, :federating], false)
conn
|> get(url)
|> response(403)
conn
|> assign(:user, user)
|> get(url)
|> response(200)
Config.put([:instance, :federating], true)
conn
|> get(url)
|> response(200)
end
end
end