Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop:
  Fix muting reblogs tests
  Fix missing announces in MastoAPI home timeline
  Add more user filters + move search to its own module
  Add vapid_key to the `POST /api/v1/apps` response
  Add GET /api/v1/apps/verify_credentials
This commit is contained in:
Henry Jameson 2019-03-27 21:36:59 +02:00
commit 5f41c54d8c
13 changed files with 381 additions and 100 deletions

View file

@ -8,10 +8,15 @@ Authentication is required and the user must be an admin.
- Method `GET` - Method `GET`
- Query Params: - Query Params:
- `query`: **string** *optional* search term - *optional* `query`: **string** search term
- `local_only`: **bool** *optional* whether to return only local users - *optional* `filters`: **string** comma-separated string of filters:
- `page`: **integer** *optional* page number - `local`: only local users
- `page_size`: **integer** *optional* number of users per page (default is `50`) - `external`: only external users
- `active`: only active users
- `deactivated`: only deactivated users
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of users per page (default is `50`)
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10`
- Response: - Response:
```JSON ```JSON
@ -22,7 +27,13 @@ Authentication is required and the user must be an admin.
{ {
"deactivated": bool, "deactivated": bool,
"id": integer, "id": integer,
"nickname": string "nickname": string,
"roles": {
"admin": bool,
"moderator": bool
},
"local": bool,
"tags": array
}, },
... ...
] ]
@ -99,7 +110,7 @@ Authentication is required and the user must be an admin.
Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesnt exist. Note: Available `:permission_group` is currently moderator and admin. 404 is returned when the permission group doesnt exist.
### Get user user permission groups membership ### Get user user permission groups membership per permission group
- Method: `GET` - Method: `GET`
- Params: none - Params: none

View file

@ -768,52 +768,6 @@ defmodule Pleroma.User do
Repo.all(query) Repo.all(query)
end end
@spec search_for_admin(%{
local: boolean(),
page: number(),
page_size: number()
}) :: {:ok, [Pleroma.User.t()], number()}
def search_for_admin(%{query: nil, local: local, page: page, page_size: page_size}) do
query =
from(u in User, order_by: u.nickname)
|> maybe_local_user_query(local)
paginated_query =
query
|> paginate(page, page_size)
count =
query
|> Repo.aggregate(:count, :id)
{:ok, Repo.all(paginated_query), count}
end
@spec search_for_admin(%{
query: binary(),
local: boolean(),
page: number(),
page_size: number()
}) :: {:ok, [Pleroma.User.t()], number()}
def search_for_admin(%{
query: term,
local: local,
page: page,
page_size: page_size
}) do
maybe_local_query = User |> maybe_local_user_query(local)
search_query = from(u in maybe_local_query, where: ilike(u.nickname, ^"%#{term}%"))
count = search_query |> Repo.aggregate(:count, :id)
results =
search_query
|> paginate(page, page_size)
|> Repo.all()
{:ok, results, count}
end
def search(query, resolve \\ false, for_user \\ nil) do def search(query, resolve \\ false, for_user \\ nil) do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
@ -1067,6 +1021,42 @@ defmodule Pleroma.User do
) )
end end
def maybe_external_user_query(query, external) do
if external, do: external_user_query(query), else: query
end
def external_user_query(query \\ User) do
from(
u in query,
where: u.local == false,
where: not is_nil(u.nickname)
)
end
def maybe_active_user_query(query, active) do
if active, do: active_user_query(query), else: query
end
def active_user_query(query \\ User) do
from(
u in query,
where: fragment("not (?->'deactivated' @> 'true')", u.info),
where: not is_nil(u.nickname)
)
end
def maybe_deactivated_user_query(query, deactivated) do
if deactivated, do: deactivated_user_query(query), else: query
end
def deactivated_user_query(query \\ User) do
from(
u in query,
where: fragment("(?->'deactivated' @> 'true')", u.info),
where: not is_nil(u.nickname)
)
end
def active_local_user_query do def active_local_user_query do
from( from(
u in local_user_query(), u in local_user_query(),

View file

@ -737,8 +737,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from( from(
activity in query, activity in query,
where: fragment("not ?->>'type' = 'Announce'", activity.data), where:
where: fragment("not ? = ANY(?)", activity.actor, ^muted_reblogs) fragment(
"not ( ?->>'type' = 'Announce' and ? = ANY(?))",
activity.data,
activity.actor,
^muted_reblogs
)
) )
end end

View file

@ -3,17 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIController do defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@users_page_size 50
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.AdminAPI.Search
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
require Logger require Logger
@users_page_size 50
action_fallback(:errors) action_fallback(:errors)
def user_delete(conn, %{"nickname" => nickname}) do def user_delete(conn, %{"nickname" => nickname}) do
@ -63,17 +64,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
end end
def list_users(%{assigns: %{user: admin}} = conn, params) do def list_users(conn, params) do
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
filters = maybe_parse_filters(params["filters"])
with {:ok, users, count} <- search_params = %{
User.search_for_admin(%{
query: params["query"], query: params["query"],
admin: admin,
local: params["local_only"] == "true",
page: page, page: page,
page_size: page_size page_size: page_size
}), }
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
do: do:
conn conn
|> json( |> json(
@ -85,6 +86,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
) )
end end
@filters ~w(local external active deactivated)
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
defp maybe_parse_filters(filters) do
filters
|> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1))
|> Enum.map(&String.to_atom(&1))
|> Enum.into(%{}, &{&1, true})
end
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_by_nickname(nickname) user = User.get_by_nickname(nickname)

View file

@ -0,0 +1,54 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.Search do
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
@page_size 50
def user(%{query: term} = params) when is_nil(term) or term == "" do
query = maybe_filtered_query(params)
paginated_query =
maybe_filtered_query(params)
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
count = query |> Repo.aggregate(:count, :id)
results = Repo.all(paginated_query)
{:ok, results, count}
end
def user(%{query: term} = params) when is_binary(term) do
search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%"))
count = search_query |> Repo.aggregate(:count, :id)
results =
search_query
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
|> Repo.all()
{:ok, results, count}
end
defp maybe_filtered_query(params) do
from(u in User, order_by: u.nickname)
|> User.maybe_local_user_query(params[:local])
|> User.maybe_external_user_query(params[:external])
|> User.maybe_active_user_query(params[:active])
|> User.maybe_deactivated_user_query(params[:deactivated])
end
defp paginate(query, page, page_size) do
from(u in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
end
end

View file

@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.AppView
alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPI
@ -51,16 +52,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
with cs <- App.register_changeset(%App{}, app_attrs), with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name, false <- cs.changes[:client_name] == @local_mastodon_name,
{:ok, app} <- Repo.insert(cs) do {:ok, app} <- Repo.insert(cs) do
res = %{ conn
id: app.id |> to_string, |> put_view(AppView)
name: app.client_name, |> render("show.json", %{app: app})
client_id: app.client_id,
client_secret: app.client_secret,
redirect_uri: app.redirect_uris,
website: app.website
}
json(conn, res)
end end
end end
@ -132,6 +126,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, account) json(conn, account)
end end
def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
conn
|> put_view(AppView)
|> render("short.json", %{app: app})
end
end
def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id), with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AppView do
use Pleroma.Web, :view
alias Pleroma.Web.OAuth.App
@vapid_key :web_push_encryption
|> Application.get_env(:vapid_details, [])
|> Keyword.get(:public_key)
def render("show.json", %{app: %App{} = app}) do
%{
id: app.id |> to_string,
name: app.client_name,
client_id: app.client_id,
client_secret: app.client_secret,
redirect_uri: app.redirect_uris,
website: app.website
}
|> with_vapid_key()
end
def render("short.json", %{app: %App{website: webiste, client_name: name}}) do
%{
name: name,
website: webiste
}
|> with_vapid_key()
end
defp with_vapid_key(data) do
if @vapid_key do
Map.put(data, "vapid_key", @vapid_key)
else
data
end
end
end

View file

@ -328,6 +328,7 @@ defmodule Pleroma.Web.Router do
get("/instance", MastodonAPIController, :masto_instance) get("/instance", MastodonAPIController, :masto_instance)
get("/instance/peers", MastodonAPIController, :peers) get("/instance/peers", MastodonAPIController, :peers)
post("/apps", MastodonAPIController, :create_app) post("/apps", MastodonAPIController, :create_app)
get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
get("/custom_emojis", MastodonAPIController, :custom_emojis) get("/custom_emojis", MastodonAPIController, :custom_emojis)
get("/statuses/:id/card", MastodonAPIController, :status_card) get("/statuses/:id/card", MastodonAPIController, :status_card)

View file

@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
@ -1107,21 +1108,4 @@ defmodule Pleroma.UserTest do
assert {:ok, user_state3} = User.bookmark(user, id2) assert {:ok, user_state3} = User.bookmark(user, id2)
assert user_state3.bookmarks == [id2] assert user_state3.bookmarks == [id2]
end end
describe "search for admin" do
test "it ignores case" do
insert(:user, nickname: "papercoach")
insert(:user, nickname: "CanadaPaperCoach")
{:ok, _results, count} =
User.search_for_admin(%{
query: "paper",
local: false,
page: 1,
page_size: 50
})
assert count == 2
end
end
end end

View file

@ -494,7 +494,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
refute Enum.member?(activities, activity) refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
end
test "returns reblogs for users for whom reblogs have not been muted" do
activity = insert(:note_activity)
user = insert(:user)
booster = insert(:user)
{:ok, user} = CommonAPI.hide_reblogs(user, booster)
{:ok, user} = CommonAPI.show_reblogs(user, booster)
{:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
end end
end end

View file

@ -408,13 +408,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
test "regular search with page size" do test "regular search with page size" do
admin = insert(:user, info: %{is_admin: true}) admin = insert(:user, info: %{is_admin: true})
user = insert(:user, nickname: "bob") user = insert(:user, nickname: "aalice")
user2 = insert(:user, nickname: "bo") user2 = insert(:user, nickname: "alice")
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?query=bo&page_size=1&page=1") |> get("/api/pleroma/admin/users?query=a&page_size=1&page=1")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -434,7 +434,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?query=bo&page_size=1&page=2") |> get("/api/pleroma/admin/users?query=a&page_size=1&page=2")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -461,7 +461,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?query=bo&local_only=true") |> get("/api/pleroma/admin/users?query=bo&filters=local")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 1, "count" => 1,
@ -488,7 +488,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?local_only=true") |> get("/api/pleroma/admin/users?filters=local")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
@ -513,6 +513,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
] ]
} }
end end
test "it works with multiple filters" do
admin = insert(:user, nickname: "john", info: %{is_admin: true})
user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true})
insert(:user, nickname: "ken", local: true, info: %{deactivated: true})
insert(:user, nickname: "bobb", local: false, info: %{deactivated: false})
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/users?filters=deactivated,external")
assert json_response(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user.local,
"tags" => []
}
]
}
end
end end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do

View file

@ -0,0 +1,88 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.SearchTest do
use Pleroma.Web.ConnCase
alias Pleroma.Web.AdminAPI.Search
import Pleroma.Factory
describe "search for admin" do
test "it ignores case" do
insert(:user, nickname: "papercoach")
insert(:user, nickname: "CanadaPaperCoach")
{:ok, _results, count} =
Search.user(%{
query: "paper",
local: false,
page: 1,
page_size: 50
})
assert count == 2
end
test "it returns local/external users" do
insert(:user, local: true)
insert(:user, local: false)
insert(:user, local: false)
{:ok, _results, local_count} =
Search.user(%{
query: "",
local: true
})
{:ok, _results, external_count} =
Search.user(%{
query: "",
external: true
})
assert local_count == 1
assert external_count == 2
end
test "it returns active/deactivated users" do
insert(:user, info: %{deactivated: true})
insert(:user, info: %{deactivated: true})
insert(:user, info: %{deactivated: false})
{:ok, _results, active_count} =
Search.user(%{
query: "",
active: true
})
{:ok, _results, deactivated_count} =
Search.user(%{
query: "",
deactivated: true
})
assert active_count == 1
assert deactivated_count == 2
end
test "it returns specific user" do
insert(:user)
insert(:user)
insert(:user, nickname: "bob", local: true, info: %{deactivated: false})
{:ok, _results, total_count} = Search.user(%{query: ""})
{:ok, _results, count} =
Search.user(%{
query: "Bo",
active: true,
local: true
})
assert total_count == 3
assert count == 1
end
end
end

View file

@ -14,7 +14,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.Push
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
@ -332,6 +334,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert id == to_string(user.id) assert id == to_string(user.id)
end end
test "apps/verify_credentials", %{conn: conn} do
token = insert(:oauth_token)
conn =
conn
|> assign(:user, token.user)
|> assign(:token, token)
|> get("/api/v1/apps/verify_credentials")
app = Repo.preload(token, :app).app
expected = %{
"name" => app.client_name,
"website" => app.website,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "creates an oauth app", %{conn: conn} do
user = insert(:user)
app_attrs = build(:oauth_app)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/apps", %{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
[app] = Repo.all(App)
expected = %{
"name" => app.client_name,
"website" => app.website,
"client_id" => app.client_id,
"client_secret" => app.client_secret,
"id" => app.id |> to_string(),
"redirect_uri" => app.redirect_uris,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "get a status", %{conn: conn} do test "get a status", %{conn: conn} do
activity = insert(:note_activity) activity = insert(:note_activity)