Merge branch 'develop' into issue/1276-2

This commit is contained in:
Maksim Pechnikov 2020-05-06 14:47:50 +03:00
commit 8b97b6f5ba
54 changed files with 1566 additions and 239 deletions

View file

@ -38,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
- Fix follower/blocks import when nicknames starts with @
- Filtering of push notifications on activities from blocked domains
- Resolving Peertube accounts with Webfinger
## [unreleased-patch]
### Security
@ -48,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Logger configuration through AdminFE
- HTTP Basic Authentication permissions issue
- ObjectAgePolicy didn't filter out old messages
- Transmogrifier: Keep object sensitive settings for outgoing representation (AP C2S)
### Added
- NodeInfo: ObjectAgePolicy settings to the `federation` list.

View file

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

View file

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

View file

@ -19,22 +19,7 @@ def perform(%{assigns: %{user: %User{}}} = conn, _) do
conn
end
def perform(conn, options) 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
def perform(conn, _) do
conn
|> render_error(:forbidden, "Invalid credentials.")
|> halt()

View file

@ -19,6 +19,9 @@ def call(conn, _opts) do
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
conn
|> put_status(404)

View file

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

View file

@ -45,6 +45,7 @@ defmodule Pleroma.User.Query do
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
exclude_service_users: boolean(),
followers: User.t(),
friends: User.t(),
recipients_from_activity: [String.t()],
@ -88,6 +89,10 @@ defp compose_query({key, value}, query)
where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
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)
when key in @equal_criteria and not_empty_string(value) do
where(query, [u], ^[{key, value}])
@ -98,7 +103,7 @@ defp compose_query({key, values}, query) when key in @contains_criteria and is_l
end
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
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 prepare_tag_criteria(tag, query) do
or_where(query, [u], fragment("? = any(?)", ^tag, u.tags))
end
defp location_query(query, local) do
where(query, [u], u.local == ^local)
|> where([u], not is_nil(u.nickname))

View file

@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
plug(
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)

View file

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

View file

@ -392,29 +392,12 @@ def list_users(conn, params) do
email: params["email"]
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
{:ok, users, count} <- filter_service_users(users, count),
do:
conn
|> json(
AccountView.render("index.json",
users: users,
count: count,
page_size: page_size
)
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
json(
conn,
AccountView.render("index.json", users: users, count: count, page_size: page_size)
)
end
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
@filters ~w(local external active deactivated is_admin is_moderator)

View file

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

View file

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

View file

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

@ -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

@ -72,7 +72,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
properties: %{
content: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
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{
type: :array,
items: %Schema{

View file

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

View file

@ -13,9 +13,12 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} 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"
def index(%{assigns: %{user: user}} = conn, params) do
participations = Participation.for_user_with_last_activity_id(user, params)
@ -26,7 +29,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
end
@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 <-
Repo.get_by(Participation, id: participation_id, user_id: user.id),
{:ok, participation} <- Participation.mark_as_read(participation) do

View file

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

View file

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

View file

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

View file

@ -9,20 +9,17 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
plug(:list_by_id_and_user when action not in [:index, :create])
@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: ["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)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation
# GET /api/v1/lists
def index(%{assigns: %{user: user}} = conn, opts) do
lists = Pleroma.List.for_user(user, opts)
@ -30,7 +27,7 @@ def index(%{assigns: %{user: user}} = conn, opts) do
end
# 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
render(conn, "show.json", list: list)
end
@ -42,7 +39,7 @@ def show(%{assigns: %{list: list}} = conn, _) do
end
# 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
render(conn, "show.json", list: list)
end
@ -65,7 +62,7 @@ def list_accounts(%{assigns: %{user: user, list: list}} = conn, _) do
end
# 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 ->
with %User{} = followed <- User.get_cached_by_id(account_id) do
Pleroma.List.follow(list, followed)
@ -76,7 +73,10 @@ def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids
end
# 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 ->
with %User{} = followed <- User.get_cached_by_id(account_id) do
Pleroma.List.unfollow(list, followed)
@ -86,7 +86,7 @@ def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => accoun
json(conn, %{})
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
%Pleroma.List{} = list -> assign(conn, :list, list)
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
alias Pleroma.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]}
@ -16,14 +18,18 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MarkerOperation
# GET /api/v1/markers
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})
end
# 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),
markers <- Map.values(result) do
render(conn, "markers.json", %{markers: markers})

View file

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

View file

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

View file

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

View file

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

View file

@ -25,9 +25,9 @@ defmodule Pleroma.Web.Push.Subscription do
timestamps()
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" => alerts}
end
@ -44,9 +44,9 @@ def create(
%User{} = user,
%Token{} = token,
%{
"subscription" => %{
"endpoint" => endpoint,
"keys" => %{"auth" => key_auth, "p256dh" => key_p256dh}
subscription: %{
endpoint: endpoint,
keys: %{auth: key_auth, p256dh: key_p256dh}
}
} = params
) do

View file

@ -427,7 +427,7 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get)
get("/push/subscription", SubscriptionController, :show)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:assign_id)
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"]

View file

@ -200,11 +200,17 @@ def skip_plug(conn) do
@impl Plug
@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.
"""
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
else
conn =

View file

@ -161,13 +161,15 @@ def finger(account) do
URI.parse(account).host
end
encoded_account = URI.encode("acct:#{account}")
address =
case find_lrdd_template(domain) do
{: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
with response <-

View file

@ -141,17 +141,15 @@ test "updating a filter" do
context: ["home"]
}
query_two = %Pleroma.Filter{
user_id: user.id,
filter_id: 1,
changes = %{
phrase: "who",
context: ["home", "timeline"]
}
{: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_two.phrase == query_two.phrase
assert filter_two.context == query_two.context
assert filter_two.phrase == changes.phrase
assert filter_two.context == changes.context
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
setup do
%{
true_fn: fn -> true end,
false_fn: fn -> false end
true_fn: fn _conn -> true end,
false_fn: fn _conn -> false end
}
end

View file

@ -74,7 +74,7 @@ defp json_response_and_validate_schema(
status = Plug.Conn.Status.code(status)
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)
end

View file

@ -211,7 +211,7 @@ def get(
end
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"}]
@ -870,7 +870,7 @@ def get(
end
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"}]
@ -883,7 +883,7 @@ def get(
end
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"}]
@ -900,7 +900,7 @@ def get("http://framatube.org/.well-known/host-meta", _, _, _) do
end
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"}]
@ -959,7 +959,7 @@ def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do
end
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"}]
@ -1155,7 +1155,7 @@ def get("http://404.site" <> _, _, _, _) do
end
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"}]
@ -1168,7 +1168,7 @@ def get(
end
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"}]

View file

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

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
@ -737,6 +738,39 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do
}
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
insert(:user)
@ -3545,7 +3579,7 @@ test "errors", %{conn: conn} do
end
test "success", %{conn: conn} do
base_url = Pleroma.Web.base_url()
base_url = Web.base_url()
app_name = "Trusted app"
response =
@ -3566,7 +3600,7 @@ test "success", %{conn: conn} do
end
test "with trusted", %{conn: conn} do
base_url = Pleroma.Web.base_url()
base_url = Web.base_url()
app_name = "Trusted app"
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")
assert response = json_response(res_conn, 200)
assert response = json_response_and_validate_schema(res_conn, 200)
assert [
%{
@ -91,18 +91,18 @@ test "filters conversations by recipients", %{user: user_one, conn: conn} do
"visibility" => "direct"
})
[conversation1, conversation2] =
assert [conversation1, conversation2] =
conn
|> get("/api/v1/conversations", %{"recipients" => [user_two.id]})
|> json_response(200)
|> get("/api/v1/conversations?recipients[]=#{user_two.id}")
|> json_response_and_validate_schema(200)
assert conversation1["last_status"]["id"] == direct5.id
assert conversation2["last_status"]["id"] == direct1.id
[conversation1] =
conn
|> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]})
|> json_response(200)
|> get("/api/v1/conversations?recipients[]=#{user_two.id}&recipients[]=#{user_three.id}")
|> json_response_and_validate_schema(200)
assert conversation1["last_status"]["id"] == direct3.id
end
@ -126,7 +126,7 @@ test "updates the last_status on reply", %{user: user_one, conn: conn} do
[%{"last_status" => res_last_status}] =
conn
|> get("/api/v1/conversations")
|> json_response(200)
|> json_response_and_validate_schema(200)
assert res_last_status["id"] == direct_reply.id
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}] =
user_two_conn
|> get("/api/v1/conversations")
|> json_response(200)
|> json_response_and_validate_schema(200)
%{"unread" => false} =
user_two_conn
|> 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_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}] =
conn
|> 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_two.id).unread_conversation_count == 0

View file

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

View file

@ -12,37 +12,44 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
test "creating a list" do
%{conn: conn} = oauth_access(["write:lists"])
conn = post(conn, "/api/v1/lists", %{"title" => "cuties"})
assert %{"title" => title} = json_response(conn, 200)
assert title == "cuties"
assert %{"title" => "cuties"} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => "cuties"})
|> json_response_and_validate_schema(:ok)
end
test "renders error for invalid params" do
%{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
test "listing a user's lists" do
%{conn: conn} = oauth_access(["read:lists", "write:lists"])
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => "cuties"})
|> json_response(:ok)
|> json_response_and_validate_schema(:ok)
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/lists", %{"title" => "cofe"})
|> json_response(:ok)
|> json_response_and_validate_schema(:ok)
conn = get(conn, "/api/v1/lists")
assert [
%{"id" => _, "title" => "cofe"},
%{"id" => _, "title" => "cuties"}
] = json_response(conn, :ok)
] = json_response_and_validate_schema(conn, :ok)
end
test "adding users to a list" do
@ -50,9 +57,12 @@ test "adding users to a list" do
other_user = insert(: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)
assert following == [other_user.follower_address]
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, 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)
assert following == [third_user.follower_address]
end
@ -83,7 +96,7 @@ test "listing users in a list" do
|> assign(:user, user)
|> 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)
end
@ -96,7 +109,7 @@ test "retrieving a list" do
|> assign(:user, user)
|> 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)
end
@ -105,17 +118,18 @@ test "renders 404 if list is not found" do
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
test "renaming a list" do
%{user: user, conn: conn} = oauth_access(["write:lists"])
{:ok, list} = Pleroma.List.create("name", user)
conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"})
assert %{"title" => name} = json_response(conn, 200)
assert name == "newname"
assert %{"title" => "newname"} =
conn
|> put_req_header("content-type", "application/json")
|> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
|> json_response_and_validate_schema(:ok)
end
test "validates title when renaming a list" do
@ -125,9 +139,11 @@ test "validates title when renaming a list" do
conn =
conn
|> assign(:user, user)
|> put_req_header("content-type", "application/json")
|> 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
test "deleting a list" do
@ -136,7 +152,7 @@ test "deleting a list" do
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))
end
end

View file

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

View file

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

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