Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms

This commit is contained in:
lain 2020-05-06 11:44:30 +02:00
commit 205313e541
80 changed files with 2138 additions and 337 deletions

View file

@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- Mastodon API: Add support for filtering replies in public and home timelines - Mastodon API: Add support for filtering replies in public and home timelines
- Admin API: endpoints for create/update/delete OAuth Apps. - Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view.
</details> </details>
### Fixed ### Fixed
@ -37,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking**: SimplePolicy `:reject` and `:accept` allow deletions again - **Breaking**: SimplePolicy `:reject` and `:accept` allow deletions again
- Fix follower/blocks import when nicknames starts with @ - Fix follower/blocks import when nicknames starts with @
- Filtering of push notifications on activities from blocked domains - Filtering of push notifications on activities from blocked domains
- Resolving Peertube accounts with Webfinger
## [unreleased-patch] ## [unreleased-patch]
### Security ### Security
@ -47,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Logger configuration through AdminFE - Logger configuration through AdminFE
- HTTP Basic Authentication permissions issue - HTTP Basic Authentication permissions issue
- ObjectAgePolicy didn't filter out old messages - ObjectAgePolicy didn't filter out old messages
- Transmogrifier: Keep object sensitive settings for outgoing representation (AP C2S)
### Added ### Added
- NodeInfo: ObjectAgePolicy settings to the `federation` list. - NodeInfo: ObjectAgePolicy settings to the `federation` list.

View file

@ -653,6 +653,8 @@
profiles: %{local: false, remote: false}, profiles: %{local: false, remote: false},
activities: %{local: false, remote: false} activities: %{local: false, remote: false}
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -2247,6 +2247,7 @@
children: [ children: [
%{ %{
key: :active, key: :active,
label: "Enabled",
type: :boolean, type: :boolean,
description: "Globally enable or disable digest emails" description: "Globally enable or disable digest emails"
}, },
@ -3194,5 +3195,19 @@
] ]
} }
] ]
},
%{
group: :pleroma,
key: Pleroma.Web.ApiSpec.CastAndValidate,
type: :group,
children: [
%{
key: :strict,
type: :boolean,
description:
"Enables strict input validation (useful in development, not recommended in production)",
suggestions: [false]
}
]
} }
] ]

View file

@ -52,6 +52,8 @@
hostname: "localhost", hostname: "localhost",
pool_size: 10 pool_size: 10
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
if File.exists?("./config/dev.secret.exs") do if File.exists?("./config/dev.secret.exs") do
import_config "dev.secret.exs" import_config "dev.secret.exs"
else else

View file

@ -96,6 +96,8 @@
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View file

@ -755,6 +755,17 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- 400 Bad Request `"Invalid parameters"` when `status` is missing - 400 Bad Request `"Invalid parameters"` when `status` is missing
- On success: `204`, empty response - On success: `204`, empty response
## `GET /api/pleroma/admin/statuses/:id`
### Show status by id
- Params:
- `id`: required, status id
- Response:
- On failure:
- 404 Not Found `"Not Found"`
- On success: JSON, Mastodon Status entity
## `PUT /api/pleroma/admin/statuses/:id` ## `PUT /api/pleroma/admin/statuses/:id`
### Change the scope of an individual reported status ### Change the scope of an individual reported status

View file

@ -924,4 +924,8 @@ Restrict access for unauthenticated users to timelines (public and federate), us
* `remote` * `remote`
* `activities` - statuses * `activities` - statuses
* `local` * `local`
* `remote` * `remote`
## Pleroma.Web.ApiSpec.CastAndValidate
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.

View file

@ -128,7 +128,7 @@ def for_user(user, params \\ %{}) do
|> Pleroma.Pagination.fetch_paginated(params) |> Pleroma.Pagination.fetch_paginated(params)
end end
def restrict_recipients(query, user, %{"recipients" => user_ids}) do def restrict_recipients(query, user, %{recipients: user_ids}) do
user_binary_ids = user_binary_ids =
[user.id | user_ids] [user.id | user_ids]
|> Enum.uniq() |> Enum.uniq()
@ -172,7 +172,7 @@ def for_user_with_last_activity_id(user, params \\ %{}) do
| last_activity_id: activity_id | last_activity_id: activity_id
} }
end) end)
|> Enum.filter(& &1.last_activity_id) |> Enum.reject(&is_nil(&1.last_activity_id))
end end
def get(_, _ \\ []) def get(_, _ \\ [])

View file

@ -89,11 +89,10 @@ def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do
|> Repo.delete() |> Repo.delete()
end end
def update(%Pleroma.Filter{} = filter) do def update(%Pleroma.Filter{} = filter, params) do
destination = Map.from_struct(filter) filter
|> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word])
Pleroma.Filter.get(filter.filter_id, %{id: filter.user_id}) |> validate_required([:phrase, :context])
|> cast(destination, [:phrase, :context, :hide, :expires_at, :whole_word])
|> Repo.update() |> Repo.update()
end end
end end

View file

@ -19,22 +19,7 @@ def perform(%{assigns: %{user: %User{}}} = conn, _) do
conn conn
end end
def perform(conn, options) do def perform(conn, _) do
perform =
cond do
options[:if_func] -> options[:if_func].()
options[:unless_func] -> !options[:unless_func].()
true -> true
end
if perform do
fail(conn)
else
conn
end
end
def fail(conn) do
conn conn
|> render_error(:forbidden, "Invalid credentials.") |> render_error(:forbidden, "Invalid credentials.")
|> halt() |> halt()

View file

@ -19,6 +19,9 @@ def call(conn, _opts) do
def federating?, do: Pleroma.Config.get([:instance, :federating]) def federating?, do: Pleroma.Config.get([:instance, :federating])
# Definition for the use in :if_func / :unless_func plug options
def federating?(_conn), do: federating?()
defp fail(conn) do defp fail(conn) do
conn conn
|> put_status(404) |> put_status(404)

View file

@ -91,7 +91,7 @@ def calculate_stat_data do
peers: peers, peers: peers,
stats: %{ stats: %{
domain_count: domain_count, domain_count: domain_count,
status_count: status_count, status_count: status_count || 0,
user_count: user_count user_count: user_count
} }
} }

View file

@ -113,7 +113,6 @@ defmodule Pleroma.User do
field(:is_admin, :boolean, default: false) field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true) field(:show_role, :boolean, default: true)
field(:settings, :map, default: nil) field(:settings, :map, default: nil)
field(:magic_key, :string, default: nil)
field(:uri, Types.Uri, default: nil) field(:uri, Types.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false) field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false)
@ -387,7 +386,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
:banner, :banner,
:locked, :locked,
:last_refreshed_at, :last_refreshed_at,
:magic_key,
:uri, :uri,
:follower_address, :follower_address,
:following_address, :following_address,

View file

@ -45,6 +45,7 @@ defmodule Pleroma.User.Query do
is_admin: boolean(), is_admin: boolean(),
is_moderator: boolean(), is_moderator: boolean(),
super_users: boolean(), super_users: boolean(),
exclude_service_users: boolean(),
followers: User.t(), followers: User.t(),
friends: User.t(), friends: User.t(),
recipients_from_activity: [String.t()], recipients_from_activity: [String.t()],
@ -88,6 +89,10 @@ defp compose_query({key, value}, query)
where(query, [u], ilike(field(u, ^key), ^"%#{value}%")) where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
end end
defp compose_query({:exclude_service_users, _}, query) do
where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch"))
end
defp compose_query({key, value}, query) defp compose_query({key, value}, query)
when key in @equal_criteria and not_empty_string(value) do when key in @equal_criteria and not_empty_string(value) do
where(query, [u], ^[{key, value}]) where(query, [u], ^[{key, value}])
@ -98,7 +103,7 @@ defp compose_query({key, values}, query) when key in @contains_criteria and is_l
end end
defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do
Enum.reduce(tags, query, &prepare_tag_criteria/2) where(query, [u], fragment("? && ?", u.tags, ^tags))
end end
defp compose_query({:is_admin, _}, query) do defp compose_query({:is_admin, _}, query) do
@ -192,10 +197,6 @@ defp compose_query({:limit, limit}, query) do
defp compose_query(_unsupported_param, query), do: query defp compose_query(_unsupported_param, query), do: query
defp prepare_tag_criteria(tag, query) do
or_where(query, [u], fragment("? = any(?)", ^tag, u.tags))
end
defp location_query(query, local) do defp location_query(query, local) do
where(query, [u], u.local == ^local) where(query, [u], u.local == ^local)
|> where([u], not is_nil(u.nickname)) |> where([u], not is_nil(u.nickname))

View file

@ -1550,21 +1550,34 @@ def fetch_follow_information_for_user(user) do
defp normalize_counter(counter) when is_integer(counter), do: counter defp normalize_counter(counter) when is_integer(counter), do: counter
defp normalize_counter(_), do: 0 defp normalize_counter(_), do: 0
defp maybe_update_follow_information(data) do def maybe_update_follow_information(user_data) do
with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])}, with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},
{:ok, info} <- fetch_follow_information_for_user(data) do {_, true} <- {:user_type_check, user_data[:type] in ["Person", "Service"]},
info = Map.merge(data[:info] || %{}, info) {_, true} <-
Map.put(data, :info, info) {:collections_available,
!!(user_data[:following_address] && user_data[:follower_address])},
{:ok, info} <-
fetch_follow_information_for_user(user_data) do
info = Map.merge(user_data[:info] || %{}, info)
user_data
|> Map.put(:info, info)
else else
{:user_type_check, false} ->
user_data
{:collections_available, false} ->
user_data
{:enabled, false} -> {:enabled, false} ->
data user_data
e -> e ->
Logger.error( Logger.error(
"Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e) "Follower/Following counter update for #{user_data.ap_id} failed.\n" <> inspect(e)
) )
data user_data
end end
end end

View file

@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
plug( plug(
EnsureAuthenticatedPlug, EnsureAuthenticatedPlug,
[unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions [unless_func: &FederatingPlug.federating?/1] when action not in @federating_only_actions
) )
# Note: :following and :followers must be served even without authentication (as via :api) # Note: :following and :followers must be served even without authentication (as via :api)

View file

@ -1205,6 +1205,10 @@ def set_conversation(object) do
Map.put(object, "conversation", object["context"]) Map.put(object, "conversation", object["context"])
end end
def set_sensitive(%{"sensitive" => true} = object) do
object
end
def set_sensitive(object) do def set_sensitive(object) do
tags = object["tag"] || [] tags = object["tag"] || []
Map.put(object, "sensitive", "nsfw" in tags) Map.put(object, "sensitive", "nsfw" in tags)

View file

@ -93,7 +93,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"], admin: true} %{scopes: ["read:statuses"], admin: true}
when action in [:list_statuses, :list_user_statuses, :list_instance_statuses] when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show]
) )
plug( plug(
@ -392,29 +392,12 @@ def list_users(conn, params) do
email: params["email"] email: params["email"]
} }
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
{:ok, users, count} <- filter_service_users(users, count), json(
do: conn,
conn AccountView.render("index.json", users: users, count: count, page_size: page_size)
|> json( )
AccountView.render("index.json", end
users: users,
count: count,
page_size: page_size
)
)
end
defp filter_service_users(users, count) do
filtered_users = Enum.reject(users, &service_user?/1)
count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
{:ok, filtered_users, count}
end
defp service_user?(user) do
String.match?(user.ap_id, ~r/.*\/relay$/) or
String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
end end
@filters ~w(local external active deactivated is_admin is_moderator) @filters ~w(local external active deactivated is_admin is_moderator)
@ -837,6 +820,16 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end end
def status_show(conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id(id) do
conn
|> put_view(StatusView)
|> render("show.json", %{activity: activity})
else
_ -> errors(conn, {:error, :not_found})
end
end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])

View file

@ -21,6 +21,7 @@ def user(params \\ %{}) do
query = query =
params params
|> Map.drop([:page, :page_size]) |> Map.drop([:page, :page_size])
|> Map.put(:exclude_service_users, true)
|> User.Query.build() |> User.Query.build()
|> order_by([u], u.nickname) |> order_by([u], u.nickname)

View file

@ -39,7 +39,12 @@ def spec do
password: %OpenApiSpex.OAuthFlow{ password: %OpenApiSpex.OAuthFlow{
authorizationUrl: "/oauth/authorize", authorizationUrl: "/oauth/authorize",
tokenUrl: "/oauth/token", tokenUrl: "/oauth/token",
scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} scopes: %{
"read" => "read",
"write" => "write",
"follow" => "follow",
"push" => "push"
}
} }
} }
} }

View file

@ -0,0 +1,139 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.CastAndValidate do
@moduledoc """
This plug is based on [`OpenApiSpex.Plug.CastAndValidate`]
(https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex).
The main difference is ignoring unexpected query params instead of throwing
an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`)
to disable this behavior. Also, the default rendering error module
is `Pleroma.Web.ApiSpec.RenderError`.
"""
@behaviour Plug
alias Plug.Conn
@impl Plug
def init(opts) do
opts
|> Map.new()
|> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError)
end
@impl Plug
def call(%{private: %{open_api_spex: private_data}} = conn, %{
operation_id: operation_id,
render_error: render_error
}) do
spec = private_data.spec
operation = private_data.operation_lookup[operation_id]
content_type =
case Conn.get_req_header(conn, "content-type") do
[header_value | _] ->
header_value
|> String.split(";")
|> List.first()
_ ->
nil
end
private_data = Map.put(private_data, :operation_id, operation_id)
conn = Conn.put_private(conn, :open_api_spex, private_data)
case cast_and_validate(spec, operation, conn, content_type, strict?()) do
{:ok, conn} ->
conn
{:error, reason} ->
opts = render_error.init(reason)
conn
|> render_error.call(opts)
|> Plug.Conn.halt()
end
end
def call(
%{
private: %{
phoenix_controller: controller,
phoenix_action: action,
open_api_spex: private_data
}
} = conn,
opts
) do
operation =
case private_data.operation_lookup[{controller, action}] do
nil ->
operation_id = controller.open_api_operation(action).operationId
operation = private_data.operation_lookup[operation_id]
operation_lookup =
private_data.operation_lookup
|> Map.put({controller, action}, operation)
OpenApiSpex.Plug.Cache.adapter().put(
private_data.spec_module,
{private_data.spec, operation_lookup}
)
operation
operation ->
operation
end
if operation.operationId do
call(conn, Map.put(opts, :operation_id, operation.operationId))
else
raise "operationId was not found in action API spec"
end
end
def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts)
defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
end
defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
{:ok, conn} ->
{:ok, conn}
# Remove unexpected query params and cast/validate again
{:error, errors} ->
query_params =
Enum.reduce(errors, conn.query_params, fn
%{reason: :unexpected_field, name: name, path: [name]}, params ->
Map.delete(params, name)
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
update_in(params, path, &List.delete(&1, value))
_, params ->
params
end)
conn = %Conn{conn | query_params: query_params}
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
end
end
defp list_items_to_string(list) do
Enum.map(list, fn
i when is_atom(i) -> to_string(i)
i -> i
end)
end
defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false)
end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias Pleroma.Web.ApiSpec.Schemas.ActorType alias Pleroma.Web.ApiSpec.Schemas.ActorType
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.List
alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@ -646,28 +647,12 @@ defp mute_request do
} }
end end
defp list do
%Schema{
title: "List",
description: "Response schema for a list",
type: :object,
properties: %{
id: %Schema{type: :string},
title: %Schema{type: :string}
},
example: %{
"id" => "123",
"title" => "my list"
}
}
end
defp array_of_lists do defp array_of_lists do
%Schema{ %Schema{
title: "ArrayOfLists", title: "ArrayOfLists",
description: "Response schema for lists", description: "Response schema for lists",
type: :array, type: :array,
items: list(), items: List,
example: [ example: [
%{"id" => "123", "title" => "my list"}, %{"id" => "123", "title" => "my list"},
%{"id" => "1337", "title" => "anotehr list"} %{"id" => "1337", "title" => "anotehr list"}

View file

@ -0,0 +1,61 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ConversationOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Conversation
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Conversations"],
summary: "Show conversation",
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "ConversationController.index",
parameters: [
Operation.parameter(
:recipients,
:query,
%Schema{type: :array, items: FlakeID},
"Only return conversations with the given recipients (a list of user ids)"
)
| pagination_params()
],
responses: %{
200 =>
Operation.response("Array of Conversation", "application/json", %Schema{
type: :array,
items: Conversation,
example: [Conversation.schema().example]
})
}
}
end
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
summary: "Mark as read",
operationId: "ConversationController.mark_as_read",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
],
security: [%{"oAuth" => ["write:conversations"]}],
responses: %{
200 => Operation.response("Conversation", "application/json", Conversation)
}
}
end
end

View file

@ -0,0 +1,227 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.FilterOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["apps"],
summary: "View all filters",
operationId: "FilterController.index",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
200 => Operation.response("Filters", "application/json", array_of_filters())
}
}
end
def create_operation do
%Operation{
tags: ["apps"],
summary: "Create a filter",
operationId: "FilterController.create",
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
responses: %{200 => Operation.response("Filter", "application/json", filter())}
}
end
def show_operation do
%Operation{
tags: ["apps"],
summary: "View all filters",
parameters: [id_param()],
operationId: "FilterController.show",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
200 => Operation.response("Filter", "application/json", filter())
}
}
end
def update_operation do
%Operation{
tags: ["apps"],
summary: "Update a filter",
parameters: [id_param()],
operationId: "FilterController.update",
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
responses: %{
200 => Operation.response("Filter", "application/json", filter())
}
}
end
def delete_operation do
%Operation{
tags: ["apps"],
summary: "Remove a filter",
parameters: [id_param()],
operationId: "FilterController.delete",
security: [%{"oAuth" => ["write:filters"]}],
responses: %{
200 =>
Operation.response("Filter", "application/json", %Schema{
type: :object,
description: "Empty object"
})
}
}
end
defp id_param do
Operation.parameter(:id, :path, :string, "Filter ID", example: "123", required: true)
end
defp filter do
%Schema{
title: "Filter",
type: :object,
properties: %{
id: %Schema{type: :string},
phrase: %Schema{type: :string, description: "The text to be filtered"},
context: %Schema{
type: :array,
items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]},
description: "The contexts in which the filter should be applied."
},
expires_at: %Schema{
type: :string,
format: :"date-time",
description:
"When the filter should no longer be applied. String (ISO 8601 Datetime), or null if the filter does not expire.",
nullable: true
},
irreversible: %Schema{
type: :boolean,
description:
"Should matching entities in home and notifications be dropped by the server?"
},
whole_word: %Schema{
type: :boolean,
description: "Should the filter consider word boundaries?"
}
},
example: %{
"id" => "5580",
"phrase" => "@twitter.com",
"context" => [
"home",
"notifications",
"public",
"thread"
],
"whole_word" => false,
"expires_at" => nil,
"irreversible" => true
}
}
end
defp array_of_filters do
%Schema{
title: "ArrayOfFilters",
description: "Array of Filters",
type: :array,
items: filter(),
example: [
%{
"id" => "5580",
"phrase" => "@twitter.com",
"context" => [
"home",
"notifications",
"public",
"thread"
],
"whole_word" => false,
"expires_at" => nil,
"irreversible" => true
},
%{
"id" => "6191",
"phrase" => ":eurovision2019:",
"context" => [
"home"
],
"whole_word" => true,
"expires_at" => "2019-05-21T13:47:31.333Z",
"irreversible" => false
}
]
}
end
defp create_request do
%Schema{
title: "FilterCreateRequest",
allOf: [
update_request(),
%Schema{
type: :object,
properties: %{
irreversible: %Schema{
type: :bolean,
description:
"Should the server irreversibly drop matching entities from home and notifications?",
default: false
}
}
}
],
example: %{
"phrase" => "knights",
"context" => ["home"]
}
}
end
defp update_request do
%Schema{
title: "FilterUpdateRequest",
type: :object,
properties: %{
phrase: %Schema{type: :string, description: "The text to be filtered"},
context: %Schema{
type: :array,
items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]},
description:
"Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified."
},
irreversible: %Schema{
type: :bolean,
description:
"Should the server irreversibly drop matching entities from home and notifications?"
},
whole_word: %Schema{
type: :bolean,
description: "Consider word boundaries?",
default: true
}
# TODO: probably should implement filter expiration
# expires_in: %Schema{
# type: :string,
# format: :"date-time",
# description:
# "ISO 8601 Datetime for when the filter expires. Otherwise,
# null for a filter that doesn't expire."
# }
},
required: [:phrase, :context],
example: %{
"phrase" => "knights",
"context" => ["home"]
}
}
end
end

View file

@ -0,0 +1,65 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Follow Requests"],
summary: "Pending Follows",
security: [%{"oAuth" => ["read:follows", "follow"]}],
operationId: "FollowRequestController.index",
responses: %{
200 =>
Operation.response("Array of Account", "application/json", %Schema{
type: :array,
items: Account,
example: [Account.schema().example]
})
}
}
end
def authorize_operation do
%Operation{
tags: ["Follow Requests"],
summary: "Accept Follow",
operationId: "FollowRequestController.authorize",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def reject_operation do
%Operation{
tags: ["Follow Requests"],
summary: "Reject Follow",
operationId: "FollowRequestController.reject",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
defp id_param do
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
required: true
)
end
end

View file

@ -0,0 +1,169 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.InstanceOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def show_operation do
%Operation{
tags: ["Instance"],
summary: "Fetch instance",
description: "Information about the server",
operationId: "InstanceController.show",
responses: %{
200 => Operation.response("Instance", "application/json", instance())
}
}
end
def peers_operation do
%Operation{
tags: ["Instance"],
summary: "List of known hosts",
operationId: "InstanceController.peers",
responses: %{
200 => Operation.response("Array of domains", "application/json", array_of_domains())
}
}
end
defp instance do
%Schema{
type: :object,
properties: %{
uri: %Schema{type: :string, description: "The domain name of the instance"},
title: %Schema{type: :string, description: "The title of the website"},
description: %Schema{
type: :string,
description: "Admin-defined description of the Pleroma site"
},
version: %Schema{
type: :string,
description: "The version of Pleroma installed on the instance"
},
email: %Schema{
type: :string,
description: "An email that may be contacted for any inquiries",
format: :email
},
urls: %Schema{
type: :object,
description: "URLs of interest for clients apps",
properties: %{
streaming_api: %Schema{
type: :string,
description: "Websockets address for push streaming"
}
}
},
stats: %Schema{
type: :object,
description: "Statistics about how much information the instance contains",
properties: %{
user_count: %Schema{
type: :integer,
description: "Users registered on this instance"
},
status_count: %Schema{
type: :integer,
description: "Statuses authored by users on instance"
},
domain_count: %Schema{
type: :integer,
description: "Domains federated with this instance"
}
}
},
thumbnail: %Schema{
type: :string,
description: "Banner image for the website",
nullable: true
},
languages: %Schema{
type: :array,
items: %Schema{type: :string},
description: "Primary langauges of the website and its staff"
},
registrations: %Schema{type: :boolean, description: "Whether registrations are enabled"},
# Extra (not present in Mastodon):
max_toot_chars: %Schema{
type: :integer,
description: ": Posts character limit (CW/Subject included in the counter)"
},
poll_limits: %Schema{
type: :object,
description: "A map with poll limits for local polls",
properties: %{
max_options: %Schema{
type: :integer,
description: "Maximum number of options."
},
max_option_chars: %Schema{
type: :integer,
description: "Maximum number of characters per option."
},
min_expiration: %Schema{
type: :integer,
description: "Minimum expiration time (in seconds)."
},
max_expiration: %Schema{
type: :integer,
description: "Maximum expiration time (in seconds)."
}
}
},
upload_limit: %Schema{
type: :integer,
description: "File size limit of uploads (except for avatar, background, banner)"
},
avatar_upload_limit: %Schema{type: :integer, description: "The title of the website"},
background_upload_limit: %Schema{type: :integer, description: "The title of the website"},
banner_upload_limit: %Schema{type: :integer, description: "The title of the website"}
},
example: %{
"avatar_upload_limit" => 2_000_000,
"background_upload_limit" => 4_000_000,
"banner_upload_limit" => 4_000_000,
"description" => "A Pleroma instance, an alternative fediverse server",
"email" => "lain@lain.com",
"languages" => ["en"],
"max_toot_chars" => 5000,
"poll_limits" => %{
"max_expiration" => 31_536_000,
"max_option_chars" => 200,
"max_options" => 20,
"min_expiration" => 0
},
"registrations" => false,
"stats" => %{
"domain_count" => 2996,
"status_count" => 15_802,
"user_count" => 5
},
"thumbnail" => "https://lain.com/instance/thumbnail.jpeg",
"title" => "lain.com",
"upload_limit" => 16_000_000,
"uri" => "https://lain.com",
"urls" => %{
"streaming_api" => "wss://lain.com"
},
"version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)"
}
}
end
defp array_of_domains do
%Schema{
type: :array,
items: %Schema{type: :string},
example: ["pleroma.site", "lain.com", "bikeshed.party"]
}
end
end

View file

@ -0,0 +1,188 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ListOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.List
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Lists"],
summary: "Show user's lists",
description: "Fetch all lists that the user owns",
security: [%{"oAuth" => ["read:lists"]}],
operationId: "ListController.index",
responses: %{
200 => Operation.response("Array of List", "application/json", array_of_lists())
}
}
end
def create_operation do
%Operation{
tags: ["Lists"],
summary: "Create a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.create",
requestBody: create_update_request(),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("List", "application/json", List),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def show_operation do
%Operation{
tags: ["Lists"],
summary: "Show a single list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.show",
parameters: [id_param()],
security: [%{"oAuth" => ["read:lists"]}],
responses: %{
200 => Operation.response("List", "application/json", List),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
tags: ["Lists"],
summary: "Update a list",
description: "Change the title of a list",
operationId: "ListController.update",
parameters: [id_param()],
requestBody: create_update_request(),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("List", "application/json", List),
422 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Lists"],
summary: "Delete a list",
operationId: "ListController.delete",
parameters: [id_param()],
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
}
}
end
def list_accounts_operation do
%Operation{
tags: ["Lists"],
summary: "View accounts in list",
operationId: "ListController.list_accounts",
parameters: [id_param()],
security: [%{"oAuth" => ["read:lists"]}],
responses: %{
200 =>
Operation.response("Array of Account", "application/json", %Schema{
type: :array,
items: Account
})
}
}
end
def add_to_list_operation do
%Operation{
tags: ["Lists"],
summary: "Add accounts to list",
description: "Add accounts to the given list.",
operationId: "ListController.add_to_list",
parameters: [id_param()],
requestBody: add_remove_accounts_request(),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
}
}
end
def remove_from_list_operation do
%Operation{
tags: ["Lists"],
summary: "Remove accounts from list",
operationId: "ListController.remove_from_list",
parameters: [id_param()],
requestBody: add_remove_accounts_request(),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
}
}
end
defp array_of_lists do
%Schema{
title: "ArrayOfLists",
description: "Response schema for lists",
type: :array,
items: List,
example: [
%{"id" => "123", "title" => "my list"},
%{"id" => "1337", "title" => "another list"}
]
}
end
defp id_param do
Operation.parameter(:id, :path, :string, "List ID",
example: "123",
required: true
)
end
defp create_update_request do
request_body(
"Parameters",
%Schema{
description: "POST body for creating or updating a List",
type: :object,
properties: %{
title: %Schema{type: :string, description: "List title"}
},
required: [:title]
},
required: true
)
end
defp add_remove_accounts_request do
request_body(
"Parameters",
%Schema{
description: "POST body for adding/removing accounts to/from a List",
type: :object,
properties: %{
account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID}
},
required: [:account_ids]
},
required: true
)
end
end

View file

@ -0,0 +1,140 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.MarkerOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Markers"],
summary: "Get saved timeline position",
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "MarkerController.index",
parameters: [
Operation.parameter(
:timeline,
:query,
%Schema{
type: :array,
items: %Schema{type: :string, enum: ["home", "notifications"]}
},
"Array of markers to fetch. If not provided, an empty object will be returned."
)
],
responses: %{
200 => Operation.response("Marker", "application/json", response()),
403 => Operation.response("Error", "application/json", api_error())
}
}
end
def upsert_operation do
%Operation{
tags: ["Markers"],
summary: "Save position in timeline",
operationId: "MarkerController.upsert",
requestBody: Helpers.request_body("Parameters", upsert_request(), required: true),
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{
200 => Operation.response("Marker", "application/json", response()),
403 => Operation.response("Error", "application/json", api_error())
}
}
end
defp marker do
%Schema{
title: "Marker",
description: "Schema for a marker",
type: :object,
properties: %{
last_read_id: %Schema{type: :string},
version: %Schema{type: :integer},
updated_at: %Schema{type: :string},
pleroma: %Schema{
type: :object,
properties: %{
unread_count: %Schema{type: :integer}
}
}
},
example: %{
"last_read_id" => "35098814",
"version" => 361,
"updated_at" => "2019-11-26T22:37:25.239Z",
"pleroma" => %{"unread_count" => 5}
}
}
end
defp response do
%Schema{
title: "MarkersResponse",
description: "Response schema for markers",
type: :object,
properties: %{
notifications: %Schema{allOf: [marker()], nullable: true},
home: %Schema{allOf: [marker()], nullable: true}
},
items: %Schema{type: :string},
example: %{
"notifications" => %{
"last_read_id" => "35098814",
"version" => 361,
"updated_at" => "2019-11-26T22:37:25.239Z",
"pleroma" => %{"unread_count" => 0}
},
"home" => %{
"last_read_id" => "103206604258487607",
"version" => 468,
"updated_at" => "2019-11-26T22:37:25.235Z",
"pleroma" => %{"unread_count" => 10}
}
}
}
end
defp upsert_request do
%Schema{
title: "MarkersUpsertRequest",
description: "Request schema for marker upsert",
type: :object,
properties: %{
notifications: %Schema{
type: :object,
properties: %{
last_read_id: %Schema{type: :string}
}
},
home: %Schema{
type: :object,
properties: %{
last_read_id: %Schema{type: :string}
}
}
},
example: %{
"home" => %{
"last_read_id" => "103194548672408537",
"version" => 462,
"updated_at" => "2019-11-24T19:39:39.337Z"
}
}
}
end
defp api_error do
%Schema{
type: :object,
properties: %{error: %Schema{type: :string}}
}
end
end

View file

@ -0,0 +1,96 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Scheduled Statuses"],
summary: "View scheduled statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: pagination_params(),
operationId: "ScheduledActivity.index",
responses: %{
200 =>
Operation.response("Array of ScheduledStatus", "application/json", %Schema{
type: :array,
items: ScheduledStatus
})
}
}
end
def show_operation do
%Operation{
tags: ["Scheduled Statuses"],
summary: "View a single scheduled status",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
operationId: "ScheduledActivity.show",
responses: %{
200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
tags: ["Scheduled Statuses"],
summary: "Schedule a status",
operationId: "ScheduledActivity.update",
security: [%{"oAuth" => ["write:statuses"]}],
parameters: [id_param()],
requestBody:
request_body("Parameters", %Schema{
type: :object,
properties: %{
scheduled_at: %Schema{
type: :string,
format: :"date-time",
description:
"ISO 8601 Datetime at which the status will be published. Must be at least 5 minutes into the future."
}
}
}),
responses: %{
200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Scheduled Statuses"],
summary: "Cancel a scheduled status",
security: [%{"oAuth" => ["write:statuses"]}],
parameters: [id_param()],
operationId: "ScheduledActivity.delete",
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object}),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Poll ID",
example: "123",
required: true
)
end
end

View file

@ -0,0 +1,188 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.PushSubscription
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def create_operation do
%Operation{
tags: ["Push Subscriptions"],
summary: "Subscribe to push notifications",
description:
"Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.",
operationId: "SubscriptionController.create",
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
200 => Operation.response("Push Subscription", "application/json", PushSubscription),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def show_operation do
%Operation{
tags: ["Push Subscriptions"],
summary: "Get current subscription",
description: "View the PushSubscription currently associated with this access token.",
operationId: "SubscriptionController.show",
security: [%{"oAuth" => ["push"]}],
responses: %{
200 => Operation.response("Push Subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
tags: ["Push Subscriptions"],
summary: "Change types of notifications",
description:
"Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.",
operationId: "SubscriptionController.update",
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
responses: %{
200 => Operation.response("Push Subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Push Subscriptions"],
summary: "Remove current subscription",
description: "Removes the current Web Push API subscription.",
operationId: "SubscriptionController.delete",
security: [%{"oAuth" => ["push"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object}),
403 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
defp create_request do
%Schema{
title: "SubscriptionCreateRequest",
description: "POST body for creating a push subscription",
type: :object,
properties: %{
subscription: %Schema{
type: :object,
properties: %{
endpoint: %Schema{
type: :string,
description: "Endpoint URL that is called when a notification event occurs."
},
keys: %Schema{
type: :object,
properties: %{
p256dh: %Schema{
type: :string,
description:
"User agent public key. Base64 encoded string of public key of ECDH key using `prime256v1` curve."
},
auth: %Schema{
type: :string,
description: "Auth secret. Base64 encoded string of 16 bytes of random data."
}
},
required: [:p256dh, :auth]
}
},
required: [:endpoint, :keys]
},
data: %Schema{
type: :object,
properties: %{
alerts: %Schema{
type: :object,
properties: %{
follow: %Schema{type: :boolean, description: "Receive follow notifications?"},
favourite: %Schema{
type: :boolean,
description: "Receive favourite notifications?"
},
reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"},
mention: %Schema{type: :boolean, description: "Receive mention notifications?"},
poll: %Schema{type: :boolean, description: "Receive poll notifications?"}
}
}
}
}
},
required: [:subscription],
example: %{
"subscription" => %{
"endpoint" => "https://example.com/example/1234",
"keys" => %{
"auth" => "8eDyX_uCN0XRhSbY5hs7Hg==",
"p256dh" =>
"BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA="
}
},
"data" => %{
"alerts" => %{
"follow" => true,
"mention" => true,
"poll" => false
}
}
}
}
end
defp update_request do
%Schema{
title: "SubscriptionUpdateRequest",
type: :object,
properties: %{
data: %Schema{
type: :object,
properties: %{
alerts: %Schema{
type: :object,
properties: %{
follow: %Schema{type: :boolean, description: "Receive follow notifications?"},
favourite: %Schema{
type: :boolean,
description: "Receive favourite notifications?"
},
reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"},
mention: %Schema{type: :boolean, description: "Receive mention notifications?"},
poll: %Schema{type: :boolean, description: "Receive poll notifications?"}
}
}
}
}
},
example: %{
"data" => %{
"alerts" => %{
"follow" => true,
"favourite" => true,
"reblog" => true,
"mention" => true,
"poll" => true
}
}
}
}
end
end

View file

@ -17,6 +17,9 @@ def init(opts), do: opts
def call(conn, errors) do def call(conn, errors) do
errors = errors =
Enum.map(errors, fn Enum.map(errors, fn
%{name: nil, reason: :invalid_enum} = err ->
%OpenApiSpex.Cast.Error{err | name: err.value}
%{name: nil} = err -> %{name: nil} = err ->
%OpenApiSpex.Cast.Error{err | name: List.last(err.path)} %OpenApiSpex.Cast.Error{err | name: List.last(err.path)}

View file

@ -0,0 +1,68 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "Attachment",
description: "Represents a file or media attachment that can be added to a status.",
type: :object,
requried: [:id, :url, :preview_url],
properties: %{
id: %Schema{type: :string},
url: %Schema{
type: :string,
format: :uri,
description: "The location of the original full-size attachment"
},
remote_url: %Schema{
type: :string,
format: :uri,
description:
"The location of the full-size original attachment on the remote website. String (URL), or null if the attachment is local",
nullable: true
},
preview_url: %Schema{
type: :string,
format: :uri,
description: "The location of a scaled-down preview of the attachment"
},
text_url: %Schema{
type: :string,
format: :uri,
description: "A shorter URL for the attachment"
},
description: %Schema{
type: :string,
nullable: true,
description:
"Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load"
},
type: %Schema{
type: :string,
enum: ["image", "video", "audio", "unknown"],
description: "The type of the attachment"
},
pleroma: %Schema{
type: :object,
properties: %{
mime_type: %Schema{type: :string, description: "mime type of the attachment"}
}
}
},
example: %{
id: "1638338801",
type: "image",
url: "someurl",
remote_url: "someurl",
preview_url: "someurl",
text_url: "someurl",
description: nil,
pleroma: %{mime_type: "image/png"}
}
})
end

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.Conversation do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.Status
require OpenApiSpex
OpenApiSpex.schema(%{
title: "Conversation",
description: "Represents a conversation with \"direct message\" visibility.",
type: :object,
required: [:id, :accounts, :unread],
properties: %{
id: %Schema{type: :string},
accounts: %Schema{
type: :array,
items: Account,
description: "Participants in the conversation"
},
unread: %Schema{
type: :boolean,
description: "Is the conversation currently marked as unread?"
},
# last_status: Status
last_status: %Schema{
allOf: [Status],
description: "The last status in the conversation, to be used for optional display"
}
},
example: %{
"id" => "418450",
"unread" => true,
"accounts" => [Account.schema().example],
"last_status" => Status.schema().example
}
})
end

View file

@ -0,0 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.List do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "List",
description: "Represents a list of users",
type: :object,
properties: %{
id: %Schema{type: :string, description: "The internal database ID of the list"},
title: %Schema{type: :string, description: "The user-defined title of the list"}
},
example: %{
"id" => "12249",
"title" => "Friends"
}
})
end

View file

@ -0,0 +1,66 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.PushSubscription do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "PushSubscription",
description: "Response schema for a push subscription",
type: :object,
properties: %{
id: %Schema{
anyOf: [%Schema{type: :string}, %Schema{type: :integer}],
description: "The id of the push subscription in the database."
},
endpoint: %Schema{type: :string, description: "Where push alerts will be sent to."},
server_key: %Schema{type: :string, description: "The streaming server's VAPID key."},
alerts: %Schema{
type: :object,
description: "Which alerts should be delivered to the endpoint.",
properties: %{
follow: %Schema{
type: :boolean,
description: "Receive a push notification when someone has followed you?"
},
favourite: %Schema{
type: :boolean,
description:
"Receive a push notification when a status you created has been favourited by someone else?"
},
reblog: %Schema{
type: :boolean,
description:
"Receive a push notification when a status you created has been boosted by someone else?"
},
mention: %Schema{
type: :boolean,
description:
"Receive a push notification when someone else has mentioned you in a status?"
},
poll: %Schema{
type: :boolean,
description:
"Receive a push notification when a poll you voted in or created has ended? "
}
}
}
},
example: %{
"id" => "328_183",
"endpoint" => "https://yourdomain.example/listener",
"alerts" => %{
"follow" => true,
"favourite" => true,
"reblog" => true,
"mention" => true,
"poll" => true
},
"server_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M="
}
})
end

View file

@ -0,0 +1,54 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Attachment
alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
require OpenApiSpex
OpenApiSpex.schema(%{
title: "ScheduledStatus",
description: "Represents a status that will be published at a future scheduled date.",
type: :object,
required: [:id, :scheduled_at, :params],
properties: %{
id: %Schema{type: :string},
scheduled_at: %Schema{type: :string, format: :"date-time"},
media_attachments: %Schema{type: :array, items: Attachment},
params: %Schema{
type: :object,
required: [:text, :visibility],
properties: %{
text: %Schema{type: :string, nullable: true},
media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}},
sensitive: %Schema{type: :boolean, nullable: true},
spoiler_text: %Schema{type: :string, nullable: true},
visibility: %Schema{type: VisibilityScope, nullable: true},
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
poll: %Schema{type: Poll, nullable: true},
in_reply_to_id: %Schema{type: :string, nullable: true}
}
}
},
example: %{
id: "3221",
scheduled_at: "2019-12-05T12:33:01.000Z",
params: %{
text: "test content",
media_ids: nil,
sensitive: nil,
spoiler_text: nil,
visibility: nil,
scheduled_at: nil,
poll: nil,
idempotency: nil,
in_reply_to_id: nil
},
media_attachments: [Attachment.schema().example]
}
})
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.ApiSpec.Schemas.Status do defmodule Pleroma.Web.ApiSpec.Schemas.Status do
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.Attachment
alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.Emoji
alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.Poll alias Pleroma.Web.ApiSpec.Schemas.Poll
@ -50,22 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
language: %Schema{type: :string, nullable: true}, language: %Schema{type: :string, nullable: true},
media_attachments: %Schema{ media_attachments: %Schema{
type: :array, type: :array,
items: %Schema{ items: Attachment
type: :object,
properties: %{
id: %Schema{type: :string},
url: %Schema{type: :string, format: :uri},
remote_url: %Schema{type: :string, format: :uri},
preview_url: %Schema{type: :string, format: :uri},
text_url: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]},
pleroma: %Schema{
type: :object,
properties: %{mime_type: %Schema{type: :string}}
}
}
}
}, },
mentions: %Schema{ mentions: %Schema{
type: :array, type: :array,
@ -86,7 +72,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
properties: %{ properties: %{
content: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, content: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
conversation_id: %Schema{type: :integer}, conversation_id: %Schema{type: :integer},
direct_conversation_id: %Schema{type: :string, nullable: true}, direct_conversation_id: %Schema{
type: :integer,
nullable: true,
description:
"The ID of the Mastodon direct message conversation the status is associated with (if any)"
},
emoji_reactions: %Schema{ emoji_reactions: %Schema{
type: :array, type: :array,
items: %Schema{ items: %Schema{

View file

@ -27,7 +27,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do when format in ["json", "activity+json"] do
with %{halted: false} = conn <- with %{halted: false} = conn <-
Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0 unless_func: &Pleroma.Web.FederatingPlug.federating?/1
) do ) do
ActivityPubController.call(conn, :user) ActivityPubController.call(conn, :user)
end end

View file

@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create) plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)

View file

@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(OpenApiSpex.Plug.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"

View file

@ -13,9 +13,12 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index)
plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index) plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ConversationOperation
@doc "GET /api/v1/conversations" @doc "GET /api/v1/conversations"
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
participations = Participation.for_user_with_last_activity_id(user, params) participations = Participation.for_user_with_last_activity_id(user, params)
@ -26,7 +29,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
end end
@doc "POST /api/v1/conversations/:id/read" @doc "POST /api/v1/conversations/:id/read"
def mark_as_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do def mark_as_read(%{assigns: %{user: user}} = conn, %{id: participation_id}) do
with %Participation{} = participation <- with %Participation{} = participation <-
Repo.get_by(Participation, id: participation_id, user_id: user.id), Repo.get_by(Participation, id: participation_id, user_id: user.id),
{:ok, participation} <- Participation.mark_as_read(participation) do {:ok, participation} <- Participation.mark_as_read(participation) do

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
plug(OpenApiSpex.Plug.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
:skip_plug, :skip_plug,

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
plug(OpenApiSpex.Plug.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
plug( plug(

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
@oauth_read_actions [:show, :index] @oauth_read_actions [:show, :index]
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions)
plug( plug(
@ -17,60 +18,60 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
%{scopes: ["write:filters"]} when action not in @oauth_read_actions %{scopes: ["write:filters"]} when action not in @oauth_read_actions
) )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation
@doc "GET /api/v1/filters" @doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user) filters = Filter.get_filters(user)
render(conn, "filters.json", filters: filters) render(conn, "index.json", filters: filters)
end end
@doc "POST /api/v1/filters" @doc "POST /api/v1/filters"
def create( def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
%{assigns: %{user: user}} = conn,
%{"phrase" => phrase, "context" => context} = params
) do
query = %Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
phrase: phrase, phrase: params.phrase,
context: context, context: params.context,
hide: Map.get(params, "irreversible", false), hide: params.irreversible,
whole_word: Map.get(params, "boolean", true) whole_word: params.whole_word
# expires_at # TODO: support `expires_in` parameter (as in Mastodon API)
} }
{:ok, response} = Filter.create(query) {:ok, response} = Filter.create(query)
render(conn, "filter.json", filter: response) render(conn, "show.json", filter: response)
end end
@doc "GET /api/v1/filters/:id" @doc "GET /api/v1/filters/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
filter = Filter.get(filter_id, user) filter = Filter.get(filter_id, user)
render(conn, "filter.json", filter: filter) render(conn, "show.json", filter: filter)
end end
@doc "PUT /api/v1/filters/:id" @doc "PUT /api/v1/filters/:id"
def update( def update(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}, body_params: params} = conn,
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params %{id: filter_id}
) do ) do
query = %Filter{ params =
user_id: user.id, params
filter_id: filter_id, |> Map.delete(:irreversible)
phrase: phrase, |> Map.put(:hide, params[:irreversible])
context: context, |> Enum.reject(fn {_key, value} -> is_nil(value) end)
hide: Map.get(params, "irreversible", nil), |> Map.new()
whole_word: Map.get(params, "boolean", true)
# expires_at
}
{:ok, response} = Filter.update(query) # TODO: support `expires_in` parameter (as in Mastodon API)
render(conn, "filter.json", filter: response)
with %Filter{} = filter <- Filter.get(filter_id, user),
{:ok, %Filter{} = filter} <- Filter.update(filter, params) do
render(conn, "show.json", filter: filter)
end
end end
@doc "DELETE /api/v1/filters/:id" @doc "DELETE /api/v1/filters/:id"
def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
query = %Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: filter_id filter_id: filter_id

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:assign_follower when action != :index) plug(:assign_follower when action != :index)
action_fallback(:errors) action_fallback(:errors)
@ -21,6 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
%{scopes: ["follow", "write:follows"]} when action != :index %{scopes: ["follow", "write:follows"]} when action != :index
) )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FollowRequestOperation
@doc "GET /api/v1/follow_requests" @doc "GET /api/v1/follow_requests"
def index(%{assigns: %{user: followed}} = conn, _params) do def index(%{assigns: %{user: followed}} = conn, _params) do
follow_requests = User.get_follow_requests(followed) follow_requests = User.get_follow_requests(followed)
@ -42,7 +45,7 @@ def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
end end
end end
defp assign_follower(%{params: %{"id" => id}} = conn, _) do defp assign_follower(%{params: %{id: id}} = conn, _) do
case User.get_cached_by_id(id) do case User.get_cached_by_id(id) do
%User{} = follower -> assign(conn, :follower, follower) %User{} = follower -> assign(conn, :follower, follower)
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()

View file

@ -5,12 +5,16 @@
defmodule Pleroma.Web.MastodonAPI.InstanceController do defmodule Pleroma.Web.MastodonAPI.InstanceController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
plug(OpenApiSpex.Plug.CastAndValidate)
plug( plug(
:skip_plug, :skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:show, :peers] when action in [:show, :peers]
) )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation
@doc "GET /api/v1/instance" @doc "GET /api/v1/instance"
def show(conn, _params) do def show(conn, _params) do
render(conn, "show.json") render(conn, "show.json")

View file

@ -9,20 +9,17 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
plug(:list_by_id_and_user when action not in [:index, :create])
@oauth_read_actions [:index, :show, :list_accounts] @oauth_read_actions [:index, :show, :list_accounts]
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:list_by_id_and_user when action not in [:index, :create])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions)
plug(OAuthScopesPlug, %{scopes: ["write:lists"]} when action not in @oauth_read_actions)
plug(
OAuthScopesPlug,
%{scopes: ["write:lists"]}
when action not in @oauth_read_actions
)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation
# GET /api/v1/lists # GET /api/v1/lists
def index(%{assigns: %{user: user}} = conn, opts) do def index(%{assigns: %{user: user}} = conn, opts) do
lists = Pleroma.List.for_user(user, opts) lists = Pleroma.List.for_user(user, opts)
@ -30,7 +27,7 @@ def index(%{assigns: %{user: user}} = conn, opts) do
end end
# POST /api/v1/lists # POST /api/v1/lists
def create(%{assigns: %{user: user}} = conn, %{"title" => title}) do def create(%{assigns: %{user: user}, body_params: %{title: title}} = conn, _) do
with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
render(conn, "show.json", list: list) render(conn, "show.json", list: list)
end end
@ -42,7 +39,7 @@ def show(%{assigns: %{list: list}} = conn, _) do
end end
# PUT /api/v1/lists/:id # PUT /api/v1/lists/:id
def update(%{assigns: %{list: list}} = conn, %{"title" => title}) do def update(%{assigns: %{list: list}, body_params: %{title: title}} = conn, _) do
with {:ok, list} <- Pleroma.List.rename(list, title) do with {:ok, list} <- Pleroma.List.rename(list, title) do
render(conn, "show.json", list: list) render(conn, "show.json", list: list)
end end
@ -65,7 +62,7 @@ def list_accounts(%{assigns: %{user: user, list: list}} = conn, _) do
end end
# POST /api/v1/lists/:id/accounts # POST /api/v1/lists/:id/accounts
def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, _) do
Enum.each(account_ids, fn account_id -> Enum.each(account_ids, fn account_id ->
with %User{} = followed <- User.get_cached_by_id(account_id) do with %User{} = followed <- User.get_cached_by_id(account_id) do
Pleroma.List.follow(list, followed) Pleroma.List.follow(list, followed)
@ -76,7 +73,10 @@ def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids
end end
# DELETE /api/v1/lists/:id/accounts # DELETE /api/v1/lists/:id/accounts
def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do def remove_from_list(
%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn,
_
) do
Enum.each(account_ids, fn account_id -> Enum.each(account_ids, fn account_id ->
with %User{} = followed <- User.get_cached_by_id(account_id) do with %User{} = followed <- User.get_cached_by_id(account_id) do
Pleroma.List.unfollow(list, followed) Pleroma.List.unfollow(list, followed)
@ -86,7 +86,7 @@ def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => accoun
json(conn, %{}) json(conn, %{})
end end
defp list_by_id_and_user(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do
case Pleroma.List.get(id, user) do case Pleroma.List.get(id, user) do
%Pleroma.List{} = list -> assign(conn, :list, list) %Pleroma.List{} = list -> assign(conn, :list, list)
nil -> conn |> render_error(:not_found, "List not found") |> halt() nil -> conn |> render_error(:not_found, "List not found") |> halt()

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"]} %{scopes: ["read:statuses"]}
@ -16,14 +18,18 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MarkerOperation
# GET /api/v1/markers # GET /api/v1/markers
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
markers = Pleroma.Marker.get_markers(user, params["timeline"]) markers = Pleroma.Marker.get_markers(user, params[:timeline])
render(conn, "markers.json", %{markers: markers}) render(conn, "markers.json", %{markers: markers})
end end
# POST /api/v1/markers # POST /api/v1/markers
def upsert(%{assigns: %{user: user}} = conn, params) do def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do
params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
with {:ok, result} <- Pleroma.Marker.upsert(user, params), with {:ok, result} <- Pleroma.Marker.upsert(user, params),
markers <- Map.values(result) do markers <- Map.values(result) do
render(conn, "markers.json", %{markers: markers}) render(conn, "markers.json", %{markers: markers})

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
@oauth_read_actions [:show, :index] @oauth_read_actions [:show, :index]
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation

View file

@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
alias Pleroma.ScheduledActivity alias Pleroma.ScheduledActivity
alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPI
plug(:assign_scheduled_activity when action != :index)
@oauth_read_actions [:show, :index] @oauth_read_actions [:show, :index]
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions)
plug(:assign_scheduled_activity when action != :index)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation
@doc "GET /api/v1/scheduled_statuses" @doc "GET /api/v1/scheduled_statuses"
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
conn conn
|> add_link_headers(scheduled_activities) |> add_link_headers(scheduled_activities)
@ -35,7 +39,7 @@ def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params)
end end
@doc "PUT /api/v1/scheduled_statuses/:id" @doc "PUT /api/v1/scheduled_statuses/:id"
def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do
with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
render(conn, "show.json", scheduled_activity: scheduled_activity) render(conn, "show.json", scheduled_activity: scheduled_activity)
end end
@ -48,7 +52,7 @@ def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params
end end
end end
defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do
case ScheduledActivity.get(user, id) do case ScheduledActivity.get(user, id) do
%ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()

View file

@ -11,14 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
action_fallback(:errors) action_fallback(:errors)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:restrict_push_enabled)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
plug(:restrict_push_enabled) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation
# Creates PushSubscription # Creates PushSubscription
# POST /api/v1/push/subscription # POST /api/v1/push/subscription
# #
def create(%{assigns: %{user: user, token: token}} = conn, params) do def create(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do
with {:ok, _} <- Subscription.delete_if_exists(user, token), with {:ok, _} <- Subscription.delete_if_exists(user, token),
{:ok, subscription} <- Subscription.create(user, token, params) do {:ok, subscription} <- Subscription.create(user, token, params) do
render(conn, "show.json", subscription: subscription) render(conn, "show.json", subscription: subscription)
@ -28,7 +30,7 @@ def create(%{assigns: %{user: user, token: token}} = conn, params) do
# Gets PushSubscription # Gets PushSubscription
# GET /api/v1/push/subscription # GET /api/v1/push/subscription
# #
def get(%{assigns: %{user: user, token: token}} = conn, _params) do def show(%{assigns: %{user: user, token: token}} = conn, _params) do
with {:ok, subscription} <- Subscription.get(user, token) do with {:ok, subscription} <- Subscription.get(user, token) do
render(conn, "show.json", subscription: subscription) render(conn, "show.json", subscription: subscription)
end end
@ -37,7 +39,7 @@ def get(%{assigns: %{user: user, token: token}} = conn, _params) do
# Updates PushSubscription # Updates PushSubscription
# PUT /api/v1/push/subscription # PUT /api/v1/push/subscription
# #
def update(%{assigns: %{user: user, token: token}} = conn, params) do def update(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do
with {:ok, subscription} <- Subscription.update(user, token, params) do with {:ok, subscription} <- Subscription.update(user, token, params) do
render(conn, "show.json", subscription: subscription) render(conn, "show.json", subscription: subscription)
end end
@ -66,7 +68,7 @@ defp restrict_push_enabled(conn, _) do
def errors(conn, {:error, :not_found}) do def errors(conn, {:error, :not_found}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(dgettext("errors", "Not found")) |> json(%{error: dgettext("errors", "Record not found")})
end end
def errors(conn, _) do def errors(conn, _) do

View file

@ -7,11 +7,11 @@ defmodule Pleroma.Web.MastodonAPI.FilterView do
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.FilterView
def render("filters.json", %{filters: filters} = opts) do def render("index.json", %{filters: filters}) do
render_many(filters, FilterView, "filter.json", opts) render_many(filters, FilterView, "show.json")
end end
def render("filter.json", %{filter: filter}) do def render("show.json", %{filter: filter}) do
expires_at = expires_at =
if filter.expires_at do if filter.expires_at do
Utils.to_masto_date(filter.expires_at) Utils.to_masto_date(filter.expires_at)

View file

@ -6,12 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.MarkerView do
use Pleroma.Web, :view use Pleroma.Web, :view
def render("markers.json", %{markers: markers}) do def render("markers.json", %{markers: markers}) do
Enum.reduce(markers, %{}, fn m, acc -> Map.new(markers, fn m ->
Map.put_new(acc, m.timeline, %{ {m.timeline,
last_read_id: m.last_read_id, %{
version: m.lock_version, last_read_id: m.last_read_id,
updated_at: NaiveDateTime.to_iso8601(m.updated_at) version: m.lock_version,
}) updated_at: NaiveDateTime.to_iso8601(m.updated_at)
}}
end) end)
end end
end end

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Router alias Pleroma.Web.Router
plug(Pleroma.Plugs.EnsureAuthenticatedPlug, plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0 unless_func: &Pleroma.Web.FederatingPlug.federating?/1
) )
plug( plug(

View file

@ -25,9 +25,9 @@ defmodule Pleroma.Web.Push.Subscription do
timestamps() timestamps()
end end
@supported_alert_types ~w[follow favourite mention reblog] @supported_alert_types ~w[follow favourite mention reblog]a
defp alerts(%{"data" => %{"alerts" => alerts}}) do defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types) alerts = Map.take(alerts, @supported_alert_types)
%{"alerts" => alerts} %{"alerts" => alerts}
end end
@ -44,9 +44,9 @@ def create(
%User{} = user, %User{} = user,
%Token{} = token, %Token{} = token,
%{ %{
"subscription" => %{ subscription: %{
"endpoint" => endpoint, endpoint: endpoint,
"keys" => %{"auth" => key_auth, "p256dh" => key_p256dh} keys: %{auth: key_auth, p256dh: key_p256dh}
} }
} = params } = params
) do ) do

View file

@ -188,6 +188,7 @@ defmodule Pleroma.Web.Router do
post("/reports/:id/notes", AdminAPIController, :report_notes_create) post("/reports/:id/notes", AdminAPIController, :report_notes_create)
delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)
get("/statuses/:id", AdminAPIController, :status_show)
put("/statuses/:id", AdminAPIController, :status_update) put("/statuses/:id", AdminAPIController, :status_update)
delete("/statuses/:id", AdminAPIController, :status_delete) delete("/statuses/:id", AdminAPIController, :status_delete)
get("/statuses", AdminAPIController, :list_statuses) get("/statuses", AdminAPIController, :list_statuses)
@ -436,7 +437,7 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/unmute", StatusController, :unmute_conversation) post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/push/subscription", SubscriptionController, :create) post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get) get("/push/subscription", SubscriptionController, :show)
put("/push/subscription", SubscriptionController, :update) put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete) delete("/push/subscription", SubscriptionController, :delete)

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:assign_id) plug(:assign_id)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug, plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0 unless_func: &Pleroma.Web.FederatingPlug.federating?/1
) )
@page_keys ["max_id", "min_id", "limit", "since_id", "order"] @page_keys ["max_id", "min_id", "limit", "since_id", "order"]

View file

@ -200,11 +200,17 @@ def skip_plug(conn) do
@impl Plug @impl Plug
@doc """ @doc """
If marked as skipped, returns `conn`, otherwise calls `perform/2`. Before-plug hook that
* ensures the plug is not skipped
* processes `:if_func` / `:unless_func` functional pre-run conditions
* adds plug to the list of called plugs and calls `perform/2` if checks are passed
Note: multiple invocations of the same plug (with different or same options) are allowed. Note: multiple invocations of the same plug (with different or same options) are allowed.
""" """
def call(%Plug.Conn{} = conn, options) do def call(%Plug.Conn{} = conn, options) do
if PlugHelper.plug_skipped?(conn, __MODULE__) do if PlugHelper.plug_skipped?(conn, __MODULE__) ||
(options[:if_func] && !options[:if_func].(conn)) ||
(options[:unless_func] && options[:unless_func].(conn)) do
conn conn
else else
conn = conn =

View file

@ -86,54 +86,24 @@ def represent_user(user, "XML") do
|> XmlBuilder.to_doc() |> XmlBuilder.to_doc()
end end
defp get_magic_key("data:application/magic-public-key," <> magic_key) do
{:ok, magic_key}
end
defp get_magic_key(nil) do
Logger.debug("Undefined magic key.")
{:ok, nil}
end
defp get_magic_key(_) do
{:error, "Missing magic key data."}
end
defp webfinger_from_xml(doc) do defp webfinger_from_xml(doc) do
with magic_key <- XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc), subject = XML.string_from_xpath("//Subject", doc)
{:ok, magic_key} <- get_magic_key(magic_key),
topic <-
XML.string_from_xpath(
~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href},
doc
),
subject <- XML.string_from_xpath("//Subject", doc),
subscribe_address <-
XML.string_from_xpath(
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template},
doc
),
ap_id <-
XML.string_from_xpath(
~s{//Link[@rel="self" and @type="application/activity+json"]/@href},
doc
) do
data = %{
"magic_key" => magic_key,
"topic" => topic,
"subject" => subject,
"subscribe_address" => subscribe_address,
"ap_id" => ap_id
}
{:ok, data} subscribe_address =
else ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
{:error, e} -> |> XML.string_from_xpath(doc)
{:error, e}
e -> ap_id =
{:error, e} ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
end |> XML.string_from_xpath(doc)
data = %{
"subject" => subject,
"subscribe_address" => subscribe_address,
"ap_id" => ap_id
}
{:ok, data}
end end
defp webfinger_from_json(doc) do defp webfinger_from_json(doc) do
@ -146,9 +116,6 @@ defp webfinger_from_json(doc) do
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} -> {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
Map.put(data, "ap_id", link["href"]) Map.put(data, "ap_id", link["href"])
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
Map.put(data, "subscribe_address", link["template"])
_ -> _ ->
Logger.debug("Unhandled type: #{inspect(link["type"])}") Logger.debug("Unhandled type: #{inspect(link["type"])}")
data data
@ -194,13 +161,15 @@ def finger(account) do
URI.parse(account).host URI.parse(account).host
end end
encoded_account = URI.encode("acct:#{account}")
address = address =
case find_lrdd_template(domain) do case find_lrdd_template(domain) do
{:ok, template} -> {:ok, template} ->
String.replace(template, "{uri}", URI.encode(account)) String.replace(template, "{uri}", encoded_account)
_ -> _ ->
"https://#{domain}/.well-known/webfinger?resource=acct:#{account}" "https://#{domain}/.well-known/webfinger?resource=#{encoded_account}"
end end
with response <- with response <-

View file

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.RemoveMagicKeyField do
use Ecto.Migration
def change do
alter table(:users) do
remove(:magic_key, :string)
end
end
end

View file

@ -141,17 +141,15 @@ test "updating a filter" do
context: ["home"] context: ["home"]
} }
query_two = %Pleroma.Filter{ changes = %{
user_id: user.id,
filter_id: 1,
phrase: "who", phrase: "who",
context: ["home", "timeline"] context: ["home", "timeline"]
} }
{:ok, filter_one} = Pleroma.Filter.create(query_one) {:ok, filter_one} = Pleroma.Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.update(query_two) {:ok, filter_two} = Pleroma.Filter.update(filter_one, changes)
assert filter_one != filter_two assert filter_one != filter_two
assert filter_two.phrase == query_two.phrase assert filter_two.phrase == changes.phrase
assert filter_two.context == query_two.context assert filter_two.context == changes.context
end end
end end

View file

@ -27,8 +27,8 @@ test "it continues if a user is assigned", %{conn: conn} do
describe "with :if_func / :unless_func options" do describe "with :if_func / :unless_func options" do
setup do setup do
%{ %{
true_fn: fn -> true end, true_fn: fn _conn -> true end,
false_fn: fn -> false end false_fn: fn _conn -> false end
} }
end end

View file

@ -74,7 +74,7 @@ defp json_response_and_validate_schema(
status = Plug.Conn.Status.code(status) status = Plug.Conn.Status.code(status)
unless lookup[op_id].responses[status] do unless lookup[op_id].responses[status] do
err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}" err = "Response schema not found for #{status} #{conn.method} #{conn.request_path}"
flunk(err) flunk(err)
end end

View file

@ -40,12 +40,18 @@ defmacro __using__(_opts) do
clear_config: 2 clear_config: 2
] ]
def to_datetime(naive_datetime) do def to_datetime(%NaiveDateTime{} = naive_datetime) do
naive_datetime naive_datetime
|> DateTime.from_naive!("Etc/UTC") |> DateTime.from_naive!("Etc/UTC")
|> DateTime.truncate(:second) |> DateTime.truncate(:second)
end end
def to_datetime(datetime) when is_binary(datetime) do
datetime
|> NaiveDateTime.from_iso8601!()
|> to_datetime()
end
def collect_ids(collection) do def collect_ids(collection) do
collection collection
|> Enum.map(& &1.id) |> Enum.map(& &1.id)

View file

@ -211,7 +211,7 @@ def get(
end end
def get( def get(
"https://squeet.me/xrd/?uri=lain@squeet.me", "https://squeet.me/xrd/?uri=acct:lain@squeet.me",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -870,7 +870,7 @@ def get(
end end
def get( def get(
"https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la", "https://social.heldscal.la/.well-known/webfinger?resource=acct:shp@social.heldscal.la",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -883,7 +883,7 @@ def get(
end end
def get( def get(
"https://social.heldscal.la/.well-known/webfinger?resource=invalid_content@social.heldscal.la", "https://social.heldscal.la/.well-known/webfinger?resource=acct:invalid_content@social.heldscal.la",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -900,7 +900,7 @@ def get("http://framatube.org/.well-known/host-meta", _, _, _) do
end end
def get( def get(
"http://framatube.org/main/xrd?uri=framasoft@framatube.org", "http://framatube.org/main/xrd?uri=acct:framasoft@framatube.org",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -959,7 +959,7 @@ def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do
end end
def get( def get(
"https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", "https://gerzilla.de/xrd/?uri=acct:kaniini@gerzilla.de",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -1155,7 +1155,7 @@ def get("http://404.site" <> _, _, _, _) do
end end
def get( def get(
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=lain@zetsubou.xn--q9jyb4c", "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:lain@zetsubou.xn--q9jyb4c",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]
@ -1168,7 +1168,7 @@ def get(
end end
def get( def get(
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=https://zetsubou.xn--q9jyb4c/users/lain", "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:https://zetsubou.xn--q9jyb4c/users/lain",
_, _,
_, _,
[{"accept", "application/xrd+xml,application/jrd+json"}] [{"accept", "application/xrd+xml,application/jrd+json"}]

View file

@ -820,21 +820,29 @@ test "it inserts an incoming sensitive activity into the database", %{
activity: activity activity: activity
} do } do
user = insert(:user) user = insert(:user)
conn = assign(conn, :user, user)
object = Map.put(activity["object"], "sensitive", true) object = Map.put(activity["object"], "sensitive", true)
activity = Map.put(activity, "object", object) activity = Map.put(activity, "object", object)
result = response =
conn conn
|> assign(:user, user)
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/outbox", activity) |> post("/users/#{user.nickname}/outbox", activity)
|> json_response(201) |> json_response(201)
assert Activity.get_by_ap_id(result["id"]) assert Activity.get_by_ap_id(response["id"])
assert result["object"] assert response["object"]
assert %Object{data: object} = Object.normalize(result["object"]) assert %Object{data: response_object} = Object.normalize(response["object"])
assert object["sensitive"] == activity["object"]["sensitive"] assert response_object["sensitive"] == true
assert object["content"] == activity["object"]["content"] assert response_object["content"] == activity["object"]["content"]
representation =
conn
|> put_req_header("accept", "application/activity+json")
|> get(response["id"])
|> json_response(200)
assert representation["object"]["sensitive"] == true
end end
test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do

View file

@ -18,9 +18,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
import ExUnit.CaptureLog
import Mock
import Pleroma.Factory import Pleroma.Factory
import Tesla.Mock import Tesla.Mock
import Mock
setup do setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end) mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -2403,4 +2404,51 @@ defp private_messages(_) do
u3: %{r1: r3_1.id, r2: r3_2.id}, u3: %{r1: r3_1.id, r2: r3_2.id},
u4: %{r1: r4_1.id}} u4: %{r1: r4_1.id}}
end end
describe "maybe_update_follow_information/1" do
setup do
clear_config([:instance, :external_user_synchronization], true)
user = %{
local: false,
ap_id: "https://gensokyo.2hu/users/raymoo",
following_address: "https://gensokyo.2hu/users/following",
follower_address: "https://gensokyo.2hu/users/followers",
type: "Person"
}
%{user: user}
end
test "logs an error when it can't fetch the info", %{user: user} do
assert capture_log(fn ->
ActivityPub.maybe_update_follow_information(user)
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
end
test "just returns the input if the user type is Application", %{
user: user
} do
user =
user
|> Map.put(:type, "Application")
refute capture_log(fn ->
assert ^user = ActivityPub.maybe_update_follow_information(user)
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
end
test "it just returns the input if the user has no following/follower addresses", %{
user: user
} do
user =
user
|> Map.put(:following_address, nil)
|> Map.put(:follower_address, nil)
refute capture_log(fn ->
assert ^user = ActivityPub.maybe_update_follow_information(user)
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
end
end
end end

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
alias Pleroma.Tests.ObanHelpers alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserInviteToken alias Pleroma.UserInviteToken
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
@ -737,6 +738,39 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
} }
end end
test "pagination works correctly with service users", %{conn: conn} do
service1 = insert(:user, ap_id: Web.base_url() <> "/relay")
service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch")
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 service2 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 service2 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]
assert service2 not in [users3]
end
test "renders empty array for the second page", %{conn: conn} do test "renders empty array for the second page", %{conn: conn} do
insert(:user) insert(:user)
@ -1620,6 +1654,25 @@ test "returns 403 when requested by anonymous" do
end end
end end
describe "GET /api/pleroma/admin/statuses/:id" do
test "not found", %{conn: conn} do
assert conn
|> get("/api/pleroma/admin/statuses/not_found")
|> json_response(:not_found)
end
test "shows activity", %{conn: conn} do
activity = insert(:note_activity)
response =
conn
|> get("/api/pleroma/admin/statuses/#{activity.id}")
|> json_response(200)
assert response["id"] == activity.id
end
end
describe "PUT /api/pleroma/admin/statuses/:id" do describe "PUT /api/pleroma/admin/statuses/:id" do
setup do setup do
activity = insert(:note_activity) activity = insert(:note_activity)
@ -3526,7 +3579,7 @@ test "errors", %{conn: conn} do
end end
test "success", %{conn: conn} do test "success", %{conn: conn} do
base_url = Pleroma.Web.base_url() base_url = Web.base_url()
app_name = "Trusted app" app_name = "Trusted app"
response = response =
@ -3547,7 +3600,7 @@ test "success", %{conn: conn} do
end end
test "with trusted", %{conn: conn} do test "with trusted", %{conn: conn} do
base_url = Pleroma.Web.base_url() base_url = Web.base_url()
app_name = "Trusted app" app_name = "Trusted app"
response = response =

View file

@ -36,7 +36,7 @@ test "returns a list of conversations", %{user: user_one, conn: conn} do
res_conn = get(conn, "/api/v1/conversations") res_conn = get(conn, "/api/v1/conversations")
assert response = json_response(res_conn, 200) assert response = json_response_and_validate_schema(res_conn, 200)
assert [ assert [
%{ %{
@ -91,18 +91,18 @@ test "filters conversations by recipients", %{user: user_one, conn: conn} do
"visibility" => "direct" "visibility" => "direct"
}) })
[conversation1, conversation2] = assert [conversation1, conversation2] =
conn conn
|> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) |> get("/api/v1/conversations?recipients[]=#{user_two.id}")
|> json_response(200) |> json_response_and_validate_schema(200)
assert conversation1["last_status"]["id"] == direct5.id assert conversation1["last_status"]["id"] == direct5.id
assert conversation2["last_status"]["id"] == direct1.id assert conversation2["last_status"]["id"] == direct1.id
[conversation1] = [conversation1] =
conn conn
|> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) |> get("/api/v1/conversations?recipients[]=#{user_two.id}&recipients[]=#{user_three.id}")
|> json_response(200) |> json_response_and_validate_schema(200)
assert conversation1["last_status"]["id"] == direct3.id assert conversation1["last_status"]["id"] == direct3.id
end end
@ -126,7 +126,7 @@ test "updates the last_status on reply", %{user: user_one, conn: conn} do
[%{"last_status" => res_last_status}] = [%{"last_status" => res_last_status}] =
conn conn
|> get("/api/v1/conversations") |> get("/api/v1/conversations")
|> json_response(200) |> json_response_and_validate_schema(200)
assert res_last_status["id"] == direct_reply.id assert res_last_status["id"] == direct_reply.id
end end
@ -154,12 +154,12 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
[%{"id" => direct_conversation_id, "unread" => true}] = [%{"id" => direct_conversation_id, "unread" => true}] =
user_two_conn user_two_conn
|> get("/api/v1/conversations") |> get("/api/v1/conversations")
|> json_response(200) |> json_response_and_validate_schema(200)
%{"unread" => false} = %{"unread" => false} =
user_two_conn user_two_conn
|> post("/api/v1/conversations/#{direct_conversation_id}/read") |> post("/api/v1/conversations/#{direct_conversation_id}/read")
|> json_response(200) |> 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_one.id).unread_conversation_count == 0
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
@ -175,7 +175,7 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do
[%{"unread" => true}] = [%{"unread" => true}] =
conn conn
|> get("/api/v1/conversations") |> get("/api/v1/conversations")
|> json_response(200) |> 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_one.id).unread_conversation_count == 1
assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0

View file

@ -15,9 +15,12 @@ test "creating a filter" do
context: ["home"] context: ["home"]
} }
conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
assert response = json_response(conn, 200) assert response = json_response_and_validate_schema(conn, 200)
assert response["phrase"] == filter.phrase assert response["phrase"] == filter.phrase
assert response["context"] == filter.context assert response["context"] == filter.context
assert response["irreversible"] == false assert response["irreversible"] == false
@ -48,12 +51,12 @@ test "fetching a list of filters" do
response = response =
conn conn
|> get("/api/v1/filters") |> get("/api/v1/filters")
|> json_response(200) |> json_response_and_validate_schema(200)
assert response == assert response ==
render_json( render_json(
FilterView, FilterView,
"filters.json", "index.json",
filters: [filter_two, filter_one] filters: [filter_two, filter_one]
) )
end end
@ -72,7 +75,7 @@ test "get a filter" do
conn = get(conn, "/api/v1/filters/#{filter.filter_id}") conn = get(conn, "/api/v1/filters/#{filter.filter_id}")
assert _response = json_response(conn, 200) assert response = json_response_and_validate_schema(conn, 200)
end end
test "update a filter" do test "update a filter" do
@ -82,7 +85,8 @@ test "update a filter" do
user_id: user.id, user_id: user.id,
filter_id: 2, filter_id: 2,
phrase: "knight", phrase: "knight",
context: ["home"] context: ["home"],
hide: true
} }
{:ok, _filter} = Pleroma.Filter.create(query) {:ok, _filter} = Pleroma.Filter.create(query)
@ -93,14 +97,17 @@ test "update a filter" do
} }
conn = conn =
put(conn, "/api/v1/filters/#{query.filter_id}", %{ conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/filters/#{query.filter_id}", %{
phrase: new.phrase, phrase: new.phrase,
context: new.context context: new.context
}) })
assert response = json_response(conn, 200) assert response = json_response_and_validate_schema(conn, 200)
assert response["phrase"] == new.phrase assert response["phrase"] == new.phrase
assert response["context"] == new.context assert response["context"] == new.context
assert response["irreversible"] == true
end end
test "delete a filter" do test "delete a filter" do
@ -117,7 +124,6 @@ test "delete a filter" do
conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") conn = delete(conn, "/api/v1/filters/#{filter.filter_id}")
assert response = json_response(conn, 200) assert json_response_and_validate_schema(conn, 200) == %{}
assert response == %{}
end end
end end

View file

@ -27,7 +27,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do
conn = get(conn, "/api/v1/follow_requests") conn = get(conn, "/api/v1/follow_requests")
assert [relationship] = json_response(conn, 200) assert [relationship] = json_response_and_validate_schema(conn, 200)
assert to_string(other_user.id) == relationship["id"] assert to_string(other_user.id) == relationship["id"]
end end
@ -44,7 +44,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize")
assert relationship = json_response(conn, 200) assert relationship = json_response_and_validate_schema(conn, 200)
assert to_string(other_user.id) == relationship["id"] assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
@ -62,7 +62,7 @@ test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do
conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject")
assert relationship = json_response(conn, 200) assert relationship = json_response_and_validate_schema(conn, 200)
assert to_string(other_user.id) == relationship["id"] assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
test "get instance information", %{conn: conn} do test "get instance information", %{conn: conn} do
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200) assert result = json_response_and_validate_schema(conn, 200)
email = Pleroma.Config.get([:instance, :email]) email = Pleroma.Config.get([:instance, :email])
# Note: not checking for "max_toot_chars" since it's optional # Note: not checking for "max_toot_chars" since it's optional
@ -56,7 +56,7 @@ test "get instance stats", %{conn: conn} do
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200) assert result = json_response_and_validate_schema(conn, 200)
stats = result["stats"] stats = result["stats"]
@ -74,7 +74,7 @@ test "get peers", %{conn: conn} do
conn = get(conn, "/api/v1/instance/peers") conn = get(conn, "/api/v1/instance/peers")
assert result = json_response(conn, 200) assert result = json_response_and_validate_schema(conn, 200)
assert ["peer1.com", "peer2.com"] == Enum.sort(result) assert ["peer1.com", "peer2.com"] == Enum.sort(result)
end end

View file

@ -12,37 +12,44 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
test "creating a list" do test "creating a list" do
%{conn: conn} = oauth_access(["write:lists"]) %{conn: conn} = oauth_access(["write:lists"])
conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) assert %{"title" => "cuties"} =
conn
assert %{"title" => title} = json_response(conn, 200) |> put_req_header("content-type", "application/json")
assert title == "cuties" |> post("/api/v1/lists", %{"title" => "cuties"})
|> json_response_and_validate_schema(:ok)
end end
test "renders error for invalid params" do test "renders error for invalid params" do
%{conn: conn} = oauth_access(["write:lists"]) %{conn: conn} = oauth_access(["write:lists"])
conn = post(conn, "/api/v1/lists", %{"title" => nil}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => nil})
assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) assert %{"error" => "title - null value where string expected."} =
json_response_and_validate_schema(conn, 400)
end end
test "listing a user's lists" do test "listing a user's lists" do
%{conn: conn} = oauth_access(["read:lists", "write:lists"]) %{conn: conn} = oauth_access(["read:lists", "write:lists"])
conn conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => "cuties"}) |> post("/api/v1/lists", %{"title" => "cuties"})
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
conn conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => "cofe"}) |> post("/api/v1/lists", %{"title" => "cofe"})
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
conn = get(conn, "/api/v1/lists") conn = get(conn, "/api/v1/lists")
assert [ assert [
%{"id" => _, "title" => "cofe"}, %{"id" => _, "title" => "cofe"},
%{"id" => _, "title" => "cuties"} %{"id" => _, "title" => "cuties"}
] = json_response(conn, :ok) ] = json_response_and_validate_schema(conn, :ok)
end end
test "adding users to a list" do test "adding users to a list" do
@ -50,9 +57,12 @@ test "adding users to a list" do
other_user = insert(:user) other_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.create("name", user)
conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
|> json_response_and_validate_schema(:ok)
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [other_user.follower_address] assert following == [other_user.follower_address]
end end
@ -65,9 +75,12 @@ test "removing users from a list" do
{:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, other_user)
{:ok, list} = Pleroma.List.follow(list, third_user) {:ok, list} = Pleroma.List.follow(list, third_user)
conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} ==
conn
|> put_req_header("content-type", "application/json")
|> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
|> json_response_and_validate_schema(:ok)
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [third_user.follower_address] assert following == [third_user.follower_address]
end end
@ -83,7 +96,7 @@ test "listing users in a list" do
|> assign(:user, user) |> assign(:user, user)
|> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert [%{"id" => id}] = json_response(conn, 200) assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)
assert id == to_string(other_user.id) assert id == to_string(other_user.id)
end end
@ -96,7 +109,7 @@ test "retrieving a list" do
|> assign(:user, user) |> assign(:user, user)
|> get("/api/v1/lists/#{list.id}") |> get("/api/v1/lists/#{list.id}")
assert %{"id" => id} = json_response(conn, 200) assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
assert id == to_string(list.id) assert id == to_string(list.id)
end end
@ -105,17 +118,18 @@ test "renders 404 if list is not found" do
conn = get(conn, "/api/v1/lists/666") conn = get(conn, "/api/v1/lists/666")
assert %{"error" => "List not found"} = json_response(conn, :not_found) assert %{"error" => "List not found"} = json_response_and_validate_schema(conn, :not_found)
end end
test "renaming a list" do test "renaming a list" do
%{user: user, conn: conn} = oauth_access(["write:lists"]) %{user: user, conn: conn} = oauth_access(["write:lists"])
{:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.create("name", user)
conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) assert %{"title" => "newname"} =
conn
assert %{"title" => name} = json_response(conn, 200) |> put_req_header("content-type", "application/json")
assert name == "newname" |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
|> json_response_and_validate_schema(:ok)
end end
test "validates title when renaming a list" do test "validates title when renaming a list" do
@ -125,9 +139,11 @@ test "validates title when renaming a list" do
conn = conn =
conn conn
|> assign(:user, user) |> assign(:user, user)
|> put_req_header("content-type", "application/json")
|> put("/api/v1/lists/#{list.id}", %{"title" => " "}) |> put("/api/v1/lists/#{list.id}", %{"title" => " "})
assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) assert %{"error" => "can't be blank"} ==
json_response_and_validate_schema(conn, :unprocessable_entity)
end end
test "deleting a list" do test "deleting a list" do
@ -136,7 +152,7 @@ test "deleting a list" do
conn = delete(conn, "/api/v1/lists/#{list.id}") conn = delete(conn, "/api/v1/lists/#{list.id}")
assert %{} = json_response(conn, 200) assert %{} = json_response_and_validate_schema(conn, 200)
assert is_nil(Repo.get(Pleroma.List, list.id)) assert is_nil(Repo.get(Pleroma.List, list.id))
end end
end end

View file

@ -22,8 +22,8 @@ test "gets markers with correct scopes", %{conn: conn} do
conn conn
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> get("/api/v1/markers", %{timeline: ["notifications"]}) |> get("/api/v1/markers?timeline[]=notifications")
|> json_response(200) |> json_response_and_validate_schema(200)
assert response == %{ assert response == %{
"notifications" => %{ "notifications" => %{
@ -45,7 +45,7 @@ test "gets markers with missed scopes", %{conn: conn} do
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> get("/api/v1/markers", %{timeline: ["notifications"]}) |> get("/api/v1/markers", %{timeline: ["notifications"]})
|> json_response(403) |> json_response_and_validate_schema(403)
assert response == %{"error" => "Insufficient permissions: read:statuses."} assert response == %{"error" => "Insufficient permissions: read:statuses."}
end end
@ -60,11 +60,12 @@ test "creates a marker with correct scopes", %{conn: conn} do
conn conn
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> put_req_header("content-type", "application/json")
|> post("/api/v1/markers", %{ |> post("/api/v1/markers", %{
home: %{last_read_id: "777"}, home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69420"} notifications: %{"last_read_id" => "69420"}
}) })
|> json_response(200) |> json_response_and_validate_schema(200)
assert %{ assert %{
"notifications" => %{ "notifications" => %{
@ -89,11 +90,12 @@ test "updates exist marker", %{conn: conn} do
conn conn
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> put_req_header("content-type", "application/json")
|> post("/api/v1/markers", %{ |> post("/api/v1/markers", %{
home: %{last_read_id: "777"}, home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69888"} notifications: %{"last_read_id" => "69888"}
}) })
|> json_response(200) |> json_response_and_validate_schema(200)
assert response == %{ assert response == %{
"notifications" => %{ "notifications" => %{
@ -112,11 +114,12 @@ test "creates a marker with missed scopes", %{conn: conn} do
conn conn
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> put_req_header("content-type", "application/json")
|> post("/api/v1/markers", %{ |> post("/api/v1/markers", %{
home: %{last_read_id: "777"}, home: %{last_read_id: "777"},
notifications: %{"last_read_id" => "69420"} notifications: %{"last_read_id" => "69420"}
}) })
|> json_response(403) |> json_response_and_validate_schema(403)
assert response == %{"error" => "Insufficient permissions: write:statuses."} assert response == %{"error" => "Insufficient permissions: write:statuses."}
end end

View file

@ -24,19 +24,19 @@ test "shows scheduled activities" do
# min_id # min_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200) result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
# since_id # since_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200) result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
# max_id # max_id
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
result = json_response(conn_res, 200) result = json_response_and_validate_schema(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
end end
@ -46,12 +46,12 @@ test "shows a scheduled activity" do
res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200)
assert scheduled_activity_id == scheduled_activity.id |> to_string() assert scheduled_activity_id == scheduled_activity.id |> to_string()
res_conn = get(conn, "/api/v1/scheduled_statuses/404") res_conn = get(conn, "/api/v1/scheduled_statuses/404")
assert %{"error" => "Record not found"} = json_response(res_conn, 404) assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end end
test "updates a scheduled activity" do test "updates a scheduled activity" do
@ -74,22 +74,32 @@ test "updates a scheduled activity" do
assert job.args == %{"activity_id" => scheduled_activity.id} assert job.args == %{"activity_id" => scheduled_activity.id}
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at)
new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) new_scheduled_at =
NaiveDateTime.utc_now()
|> Timex.shift(minutes: 120)
|> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime)
res_conn = res_conn =
put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
scheduled_at: new_scheduled_at scheduled_at: new_scheduled_at
}) })
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) assert %{"scheduled_at" => expected_scheduled_at} =
json_response_and_validate_schema(res_conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
job = refresh_record(job) job = refresh_record(job)
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at)
res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) res_conn =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
assert %{"error" => "Record not found"} = json_response(res_conn, 404) assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end end
test "deletes a scheduled activity" do test "deletes a scheduled activity" do
@ -115,7 +125,7 @@ test "deletes a scheduled activity" do
|> assign(:user, user) |> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{} = json_response(res_conn, 200) assert %{} = json_response_and_validate_schema(res_conn, 200)
refute Repo.get(ScheduledActivity, scheduled_activity.id) refute Repo.get(ScheduledActivity, scheduled_activity.id)
refute Repo.get(Oban.Job, job.id) refute Repo.get(Oban.Job, job.id)
@ -124,6 +134,6 @@ test "deletes a scheduled activity" do
|> assign(:user, user) |> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"error" => "Record not found"} = json_response(res_conn, 404) assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
end end
end end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Web.Push alias Pleroma.Web.Push
alias Pleroma.Web.Push.Subscription alias Pleroma.Web.Push.Subscription
@ -27,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
build_conn() build_conn()
|> assign(:user, user) |> assign(:user, user)
|> assign(:token, token) |> assign(:token, token)
|> put_req_header("content-type", "application/json")
%{conn: conn, user: user, token: token} %{conn: conn, user: user, token: token}
end end
@ -47,8 +49,8 @@ defmacro assert_error_when_disable_push(do: yield) do
test "returns error when push disabled ", %{conn: conn} do test "returns error when push disabled ", %{conn: conn} do
assert_error_when_disable_push do assert_error_when_disable_push do
conn conn
|> post("/api/v1/push/subscription", %{}) |> post("/api/v1/push/subscription", %{subscription: @sub})
|> json_response(403) |> json_response_and_validate_schema(403)
end end
end end
@ -59,7 +61,7 @@ test "successful creation", %{conn: conn} do
"data" => %{"alerts" => %{"mention" => true, "test" => true}}, "data" => %{"alerts" => %{"mention" => true, "test" => true}},
"subscription" => @sub "subscription" => @sub
}) })
|> json_response(200) |> json_response_and_validate_schema(200)
[subscription] = Pleroma.Repo.all(Subscription) [subscription] = Pleroma.Repo.all(Subscription)
@ -77,7 +79,7 @@ test "returns error when push disabled ", %{conn: conn} do
assert_error_when_disable_push do assert_error_when_disable_push do
conn conn
|> get("/api/v1/push/subscription", %{}) |> get("/api/v1/push/subscription", %{})
|> json_response(403) |> json_response_and_validate_schema(403)
end end
end end
@ -85,9 +87,9 @@ test "returns error when user hasn't subscription", %{conn: conn} do
res = res =
conn conn
|> get("/api/v1/push/subscription", %{}) |> get("/api/v1/push/subscription", %{})
|> json_response(404) |> json_response_and_validate_schema(404)
assert "Not found" == res assert %{"error" => "Record not found"} == res
end end
test "returns a user subsciption", %{conn: conn, user: user, token: token} do test "returns a user subsciption", %{conn: conn, user: user, token: token} do
@ -101,7 +103,7 @@ test "returns a user subsciption", %{conn: conn, user: user, token: token} do
res = res =
conn conn
|> get("/api/v1/push/subscription", %{}) |> get("/api/v1/push/subscription", %{})
|> json_response(200) |> json_response_and_validate_schema(200)
expect = %{ expect = %{
"alerts" => %{"mention" => true}, "alerts" => %{"mention" => true},
@ -130,7 +132,7 @@ test "returns error when push disabled ", %{conn: conn} do
assert_error_when_disable_push do assert_error_when_disable_push do
conn conn
|> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}}) |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}})
|> json_response(403) |> json_response_and_validate_schema(403)
end end
end end
@ -140,7 +142,7 @@ test "returns updated subsciption", %{conn: conn, subscription: subscription} do
|> put("/api/v1/push/subscription", %{ |> put("/api/v1/push/subscription", %{
data: %{"alerts" => %{"mention" => false, "follow" => true}} data: %{"alerts" => %{"mention" => false, "follow" => true}}
}) })
|> json_response(200) |> json_response_and_validate_schema(200)
expect = %{ expect = %{
"alerts" => %{"follow" => true, "mention" => false}, "alerts" => %{"follow" => true, "mention" => false},
@ -158,7 +160,7 @@ test "returns error when push disabled ", %{conn: conn} do
assert_error_when_disable_push do assert_error_when_disable_push do
conn conn
|> delete("/api/v1/push/subscription", %{}) |> delete("/api/v1/push/subscription", %{})
|> json_response(403) |> json_response_and_validate_schema(403)
end end
end end
@ -166,9 +168,9 @@ test "returns error when user hasn't subscription", %{conn: conn} do
res = res =
conn conn
|> delete("/api/v1/push/subscription", %{}) |> delete("/api/v1/push/subscription", %{})
|> json_response(404) |> json_response_and_validate_schema(404)
assert "Not found" == res assert %{"error" => "Record not found"} == res
end end
test "returns empty result and delete user subsciption", %{ test "returns empty result and delete user subsciption", %{
@ -186,7 +188,7 @@ test "returns empty result and delete user subsciption", %{
res = res =
conn conn
|> delete("/api/v1/push/subscription", %{}) |> delete("/api/v1/push/subscription", %{})
|> json_response(200) |> json_response_and_validate_schema(200)
assert %{} == res assert %{} == res
refute Pleroma.Repo.get(Subscription, subscription.id) refute Pleroma.Repo.get(Subscription, subscription.id)

View file

@ -402,11 +402,17 @@ test "attachments" do
pleroma: %{mime_type: "image/png"} pleroma: %{mime_type: "image/png"}
} }
api_spec = Pleroma.Web.ApiSpec.spec()
assert expected == StatusView.render("attachment.json", %{attachment: object}) assert expected == StatusView.render("attachment.json", %{attachment: object})
OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec)
# If theres a "id", use that instead of the generated one # If theres a "id", use that instead of the generated one
object = Map.put(object, "id", 2) object = Map.put(object, "id", 2)
assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) result = StatusView.render("attachment.json", %{attachment: object})
assert %{id: "2"} = result
OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec)
end end
test "put the url advertised in the Activity in to the url attribute" do test "put the url advertised in the Activity in to the url attribute" do

View file

@ -0,0 +1,91 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PlugTest do
@moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`"
alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug
alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
alias Pleroma.Plugs.PlugHelper
import Mock
use Pleroma.Web.ConnCase
describe "when plug is skipped, " do
setup_with_mocks(
[
{ExpectPublicOrAuthenticatedCheckPlug, [:passthrough], []}
],
%{conn: conn}
) do
conn = ExpectPublicOrAuthenticatedCheckPlug.skip_plug(conn)
%{conn: conn}
end
test "it neither adds plug to called plugs list nor calls `perform/2`, " <>
"regardless of :if_func / :unless_func options",
%{conn: conn} do
for opts <- [%{}, %{if_func: fn _ -> true end}, %{unless_func: fn _ -> false end}] do
ret_conn = ExpectPublicOrAuthenticatedCheckPlug.call(conn, opts)
refute called(ExpectPublicOrAuthenticatedCheckPlug.perform(:_, :_))
refute PlugHelper.plug_called?(ret_conn, ExpectPublicOrAuthenticatedCheckPlug)
end
end
end
describe "when plug is NOT skipped, " do
setup_with_mocks([{ExpectAuthenticatedCheckPlug, [:passthrough], []}]) do
:ok
end
test "with no pre-run checks, adds plug to called plugs list and calls `perform/2`", %{
conn: conn
} do
ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{})
assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
end
test "when :if_func option is given, calls the plug only if provided function evals tru-ish",
%{conn: conn} do
ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> false end})
refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> true end})
assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
end
test "if :unless_func option is given, calls the plug only if provided function evals falsy",
%{conn: conn} do
ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> true end})
refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> false end})
assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
end
test "allows a plug to be called multiple times (even if it's in called plugs list)", %{
conn: conn
} do
conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value1})
assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value1}))
assert PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug)
conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value2})
assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value2}))
end
end
end

View file

@ -67,7 +67,7 @@ test "it work for AP-only user" do
assert data["magic_key"] == nil assert data["magic_key"] == nil
assert data["salmon"] == nil assert data["salmon"] == nil
assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" assert data["topic"] == nil
assert data["subject"] == "acct:kPherox@mstdn.jp" assert data["subject"] == "acct:kPherox@mstdn.jp"
assert data["ap_id"] == "https://mstdn.jp/users/kPherox" assert data["ap_id"] == "https://mstdn.jp/users/kPherox"
assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}" assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"