Merge remote-tracking branch 'remotes/origin/develop' into authenticated-api-oauth-check-enforcement
commit
bedf92e064
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"skip_files": [
|
||||||
|
"test/support",
|
||||||
|
"lib/mix/tasks/pleroma/benchmark.ex"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
|
@moduledoc """
|
||||||
|
This module builds the objects. Meant to be used for creating local objects.
|
||||||
|
|
||||||
|
This module encodes our addressing policies and general shape of our objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
|
def like(actor, object) do
|
||||||
|
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
|
||||||
|
# Address the actor of the object, and our actor's follower collection if the post is public.
|
||||||
|
to =
|
||||||
|
if Visibility.is_public?(object) do
|
||||||
|
[actor.follower_address, object.data["actor"]]
|
||||||
|
else
|
||||||
|
[object.data["actor"]]
|
||||||
|
end
|
||||||
|
|
||||||
|
# CC everyone who's been addressed in the object, except ourself and the object actor's
|
||||||
|
# follower collection
|
||||||
|
cc =
|
||||||
|
(object.data["to"] ++ (object.data["cc"] || []))
|
||||||
|
|> List.delete(actor.ap_id)
|
||||||
|
|> List.delete(object_actor.follower_address)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"actor" => actor.ap_id,
|
||||||
|
"type" => "Like",
|
||||||
|
"object" => object.data["id"],
|
||||||
|
"to" => to,
|
||||||
|
"cc" => cc,
|
||||||
|
"context" => object.data["context"]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,37 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
|
@moduledoc """
|
||||||
|
This module is responsible for validating an object (which can be an activity)
|
||||||
|
and checking if it is both well formed and also compatible with our view of
|
||||||
|
the system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
|
||||||
|
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||||
|
def validate(object, meta)
|
||||||
|
|
||||||
|
def validate(%{"type" => "Like"} = object, meta) do
|
||||||
|
with {:ok, object} <-
|
||||||
|
object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
object = stringify_keys(object |> Map.from_struct())
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stringify_keys(object) do
|
||||||
|
object
|
||||||
|
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_actor_and_object(object) do
|
||||||
|
User.get_or_fetch_by_ap_id(object["actor"])
|
||||||
|
Object.normalize(object["object"])
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,32 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def validate_actor_presence(cng, field_name \\ :actor) do
|
||||||
|
cng
|
||||||
|
|> validate_change(field_name, fn field_name, actor ->
|
||||||
|
if User.get_cached_by_ap_id(actor) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{field_name, "can't find user"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_object_presence(cng, field_name \\ :object) do
|
||||||
|
cng
|
||||||
|
|> validate_change(field_name, fn field_name, object ->
|
||||||
|
if Object.get_cached_by_ap_id(object) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[{field_name, "can't find object"}]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,30 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, Types.ObjectID, primary_key: true)
|
||||||
|
field(:actor, Types.ObjectID)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:to, {:array, :string})
|
||||||
|
field(:cc, {:array, :string})
|
||||||
|
field(:bto, {:array, :string}, default: [])
|
||||||
|
field(:bcc, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
embeds_one(:object, NoteValidator)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
cast(%__MODULE__{}, data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,57 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, Types.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:object, Types.ObjectID)
|
||||||
|
field(:actor, Types.ObjectID)
|
||||||
|
field(:context, :string)
|
||||||
|
field(:to, {:array, :string})
|
||||||
|
field(:cc, {:array, :string})
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, [:id, :type, :object, :actor, :context, :to, :cc])
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(data_cng) do
|
||||||
|
data_cng
|
||||||
|
|> validate_inclusion(:type, ["Like"])
|
||||||
|
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_object_presence()
|
||||||
|
|> validate_existing_like()
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
|
||||||
|
if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
|
||||||
|
cng
|
||||||
|
|> add_error(:actor, "already liked this object")
|
||||||
|
|> add_error(:object, "already liked by this actor")
|
||||||
|
else
|
||||||
|
cng
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_existing_like(cng), do: cng
|
||||||
|
end
|
@ -0,0 +1,63 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, Types.ObjectID, primary_key: true)
|
||||||
|
field(:to, {:array, :string}, default: [])
|
||||||
|
field(:cc, {:array, :string}, default: [])
|
||||||
|
field(:bto, {:array, :string}, default: [])
|
||||||
|
field(:bcc, {:array, :string}, default: [])
|
||||||
|
# TODO: Write type
|
||||||
|
field(:tag, {:array, :map}, default: [])
|
||||||
|
field(:type, :string)
|
||||||
|
field(:content, :string)
|
||||||
|
field(:context, :string)
|
||||||
|
field(:actor, Types.ObjectID)
|
||||||
|
field(:attributedTo, Types.ObjectID)
|
||||||
|
field(:summary, :string)
|
||||||
|
field(:published, Types.DateTime)
|
||||||
|
# TODO: Write type
|
||||||
|
field(:emoji, :map, default: %{})
|
||||||
|
field(:sensitive, :boolean, default: false)
|
||||||
|
# TODO: Write type
|
||||||
|
field(:attachment, {:array, :map}, default: [])
|
||||||
|
field(:replies_count, :integer, default: 0)
|
||||||
|
field(:like_count, :integer, default: 0)
|
||||||
|
field(:announcement_count, :integer, default: 0)
|
||||||
|
field(:inRepyTo, :string)
|
||||||
|
|
||||||
|
field(:likes, {:array, :string}, default: [])
|
||||||
|
field(:announcements, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
# see if needed
|
||||||
|
field(:conversation, :string)
|
||||||
|
field(:context_id, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(data_cng) do
|
||||||
|
data_cng
|
||||||
|
|> validate_inclusion(:type, ["Note"])
|
||||||
|
|> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,34 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
|
||||||
|
@moduledoc """
|
||||||
|
The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
|
||||||
|
DateTime can't parse this, but it can parse the related iso8601. This
|
||||||
|
module punches the date until it looks like iso8601 and normalizes to
|
||||||
|
it.
|
||||||
|
|
||||||
|
DateTimes without a timezone offset are treated as UTC.
|
||||||
|
|
||||||
|
Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
|
||||||
|
"""
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
def type, do: :string
|
||||||
|
|
||||||
|
def cast(datetime) when is_binary(datetime) do
|
||||||
|
with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do
|
||||||
|
{:ok, DateTime.to_iso8601(datetime)}
|
||||||
|
else
|
||||||
|
{:error, :missing_offset} -> cast("#{datetime}Z")
|
||||||
|
_e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(_), do: :error
|
||||||
|
|
||||||
|
def dump(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,29 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
def type, do: :string
|
||||||
|
|
||||||
|
def cast(object) when is_binary(object) do
|
||||||
|
# Host has to be present and scheme has to be an http scheme (for now)
|
||||||
|
case URI.parse(object) do
|
||||||
|
%URI{host: nil} -> :error
|
||||||
|
%URI{host: ""} -> :error
|
||||||
|
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(%{"id" => object}), do: cast(object)
|
||||||
|
|
||||||
|
def cast(_) do
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,42 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
|
@spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()}
|
||||||
|
def common_pipeline(object, meta) do
|
||||||
|
with {_, {:ok, validated_object, meta}} <-
|
||||||
|
{:validate_object, ObjectValidator.validate(object, meta)},
|
||||||
|
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
|
||||||
|
{_, {:ok, %Activity{} = activity, meta}} <-
|
||||||
|
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
|
||||||
|
{_, {:ok, %Activity{} = activity, meta}} <-
|
||||||
|
{:execute_side_effects, SideEffects.handle(activity, meta)},
|
||||||
|
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
||||||
|
{:ok, activity, meta}
|
||||||
|
else
|
||||||
|
{:mrf_object, {:reject, _}} -> {:ok, nil, meta}
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_federate(activity, meta) do
|
||||||
|
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||||
|
if local do
|
||||||
|
Federator.publish(activity)
|
||||||
|
{:ok, :federated}
|
||||||
|
else
|
||||||
|
{:ok, :not_federated}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_e -> {:error, :badarg}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,28 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
|
@moduledoc """
|
||||||
|
This module looks at an inserted object and executes the side effects that it
|
||||||
|
implies. For example, a `Like` activity will increase the like count on the
|
||||||
|
liked object, a `Follow` activity will add the user to the follower
|
||||||
|
collection, and so on.
|
||||||
|
"""
|
||||||
|
alias Pleroma.Notification
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
def handle(object, meta \\ [])
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Add like to object
|
||||||
|
# - Set up notification
|
||||||
|
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
|
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||||
|
Utils.add_like_to_object(object, liked_object)
|
||||||
|
Notification.create_notifications(object)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Nothing to do
|
||||||
|
def handle(object, meta) do
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,44 @@
|
|||||||
|
# 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 do
|
||||||
|
alias OpenApiSpex.OpenApi
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
|
@behaviour OpenApi
|
||||||
|
|
||||||
|
@impl OpenApi
|
||||||
|
def spec do
|
||||||
|
%OpenApi{
|
||||||
|
servers: [
|
||||||
|
# Populate the Server info from a phoenix endpoint
|
||||||
|
OpenApiSpex.Server.from_endpoint(Endpoint)
|
||||||
|
],
|
||||||
|
info: %OpenApiSpex.Info{
|
||||||
|
title: "Pleroma",
|
||||||
|
description: Application.spec(:pleroma, :description) |> to_string(),
|
||||||
|
version: Application.spec(:pleroma, :vsn) |> to_string()
|
||||||
|
},
|
||||||
|
# populate the paths from a phoenix router
|
||||||
|
paths: OpenApiSpex.Paths.from_router(Router),
|
||||||
|
components: %OpenApiSpex.Components{
|
||||||
|
securitySchemes: %{
|
||||||
|
"oAuth" => %OpenApiSpex.SecurityScheme{
|
||||||
|
type: "oauth2",
|
||||||
|
flows: %OpenApiSpex.OAuthFlows{
|
||||||
|
password: %OpenApiSpex.OAuthFlow{
|
||||||
|
authorizationUrl: "/oauth/authorize",
|
||||||
|
tokenUrl: "/oauth/token",
|
||||||
|
scopes: %{"read" => "read", "write" => "write", "follow" => "follow"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# discover request/response schemas from path specs
|
||||||
|
|> OpenApiSpex.resolve_schema_modules()
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,27 @@
|
|||||||
|
# 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.Helpers do
|
||||||
|
def request_body(description, schema_ref, opts \\ []) do
|
||||||
|
media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
|
||||||
|
|
||||||
|
content =
|
||||||
|
media_types
|
||||||
|
|> Enum.map(fn type ->
|
||||||
|
{type,
|
||||||
|
%OpenApiSpex.MediaType{
|
||||||
|
schema: schema_ref,
|
||||||
|
example: opts[:example],
|
||||||
|
examples: opts[:examples]
|
||||||
|
}}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
%OpenApiSpex.RequestBody{
|
||||||
|
description: description,
|
||||||
|
content: content,
|
||||||
|
required: opts[:required] || false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -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.AppOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Helpers
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||||
|
|
||||||
|
@spec open_api_operation(atom) :: Operation.t()
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_operation() :: Operation.t()
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["apps"],
|
||||||
|
summary: "Create an application",
|
||||||
|
description: "Create a new application to obtain OAuth2 credentials",
|
||||||
|
operationId: "AppController.create",
|
||||||
|
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("App", "application/json", AppCreateResponse),
|
||||||
|
422 =>
|
||||||
|
Operation.response(
|
||||||
|
"Unprocessable Entity",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
description:
|
||||||
|
"If a required parameter is missing or improperly formatted, the request will fail.",
|
||||||
|
properties: %{
|
||||||
|
error: %Schema{type: :string}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"error" => "Validation failed: Redirect URI must be an absolute URI."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_credentials_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["apps"],
|
||||||
|
summary: "Verify your app works",
|
||||||
|
description: "Confirm that the app's OAuth2 credentials work.",
|
||||||
|
operationId: "AppController.verify_credentials",
|
||||||
|
security: [
|
||||||
|
%{
|
||||||
|
"oAuth" => ["read"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("App", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
description:
|
||||||
|
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{type: :string},
|
||||||
|
vapid_key: %Schema{type: :string},
|
||||||
|
website: %Schema{type: :string, nullable: true}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"name" => "My App",
|
||||||
|
"vapid_key" =>
|
||||||
|
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||||
|
"website" => "https://myapp.com/"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
422 =>
|
||||||
|
Operation.response(
|
||||||
|
"Unauthorized",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
description:
|
||||||
|
"If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
|
||||||
|
properties: %{
|
||||||
|
error: %Schema{type: :string}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"error" => "The access token is invalid."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,64 @@
|
|||||||
|
# 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.DomainBlockOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Helpers
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Fetch domain blocks",
|
||||||
|
description: "View domains the user has blocked.",
|
||||||
|
security: [%{"oAuth" => ["follow", "read:blocks"]}],
|
||||||
|
operationId: "DomainBlockController.index",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Block a domain",
|
||||||
|
description: """
|
||||||
|
Block a domain to:
|
||||||
|
|
||||||
|
- hide all public posts from it
|
||||||
|
- hide all notifications from it
|
||||||
|
- remove all followers from it
|
||||||
|
- prevent following new users from it (but does not remove existing follows)
|
||||||
|
""",
|
||||||
|
operationId: "DomainBlockController.create",
|
||||||
|
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||||
|
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["domain_blocks"],
|
||||||
|
summary: "Unblock a domain",
|
||||||
|
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
|
||||||
|
operationId: "DomainBlockController.delete",
|
||||||
|
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||||
|
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,33 @@
|
|||||||
|
# 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.AppCreateRequest do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "AppCreateRequest",
|
||||||
|
description: "POST body for creating an app",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
client_name: %Schema{type: :string, description: "A name for your application."},
|
||||||
|
redirect_uris: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||||
|
},
|
||||||
|
scopes: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "Space separated list of scopes. If none is provided, defaults to `read`."
|
||||||
|
},
|
||||||
|
website: %Schema{type: :string, description: "A URL to the homepage of your app"}
|
||||||
|
},
|
||||||
|
required: [:client_name, :redirect_uris],
|
||||||
|
example: %{
|
||||||
|
"client_name" => "My App",
|
||||||
|
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||||
|
"website" => "https://myapp.com/"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
@ -0,0 +1,33 @@
|
|||||||
|
# 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.AppCreateResponse do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "AppCreateResponse",
|
||||||
|
description: "Response schema for an app",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{type: :string},
|
||||||
|
name: %Schema{type: :string},
|
||||||
|
client_id: %Schema{type: :string},
|
||||||
|
client_secret: %Schema{type: :string},
|
||||||
|
redirect_uri: %Schema{type: :string},
|
||||||
|
vapid_key: %Schema{type: :string},
|
||||||
|
website: %Schema{type: :string, nullable: true}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"id" => "123",
|
||||||
|
"name" => "My App",
|
||||||
|
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||||
|
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||||
|
"vapid_key" =>
|
||||||
|
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||||
|
"website" => "https://myapp.com/"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
@ -0,0 +1,20 @@
|
|||||||
|
# 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.DomainBlockRequest do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "DomainBlockRequest",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
domain: %Schema{type: :string}
|
||||||
|
},
|
||||||
|
required: [:domain],
|
||||||
|
example: %{
|
||||||
|
"domain" => "facebook.com"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
@ -0,0 +1,16 @@
|
|||||||
|
# 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.DomainBlocksResponse do
|
||||||
|
require OpenApiSpex
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "DomainBlocksResponse",
|
||||||
|
description: "Response schema for domain blocks",
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
example: ["google.com", "facebook.com"]
|
||||||
|
})
|
||||||
|
end
|
@ -0,0 +1,29 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state"
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("""
|
||||||
|
#{@alter_following_relationship_state} TYPE integer USING
|
||||||
|
CASE
|
||||||
|
WHEN state = 'pending' THEN 1
|
||||||
|
WHEN state = 'accept' THEN 2
|
||||||
|
WHEN state = 'reject' THEN 3
|
||||||
|
ELSE 0
|
||||||
|
END;
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("""
|
||||||
|
#{@alter_following_relationship_state} TYPE varchar(255) USING
|
||||||
|
CASE
|
||||||
|
WHEN state = 1 THEN 'pending'
|
||||||
|
WHEN state = 2 THEN 'accept'
|
||||||
|
WHEN state = 3 THEN 'reject'
|
||||||
|
ELSE ''
|
||||||
|
END;
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,11 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# [:follower_index] index is useless because of [:follower_id, :following_id] index
|
||||||
|
# [:following_id] index makes sense because of user's followers-targeted queries
|
||||||
|
def change do
|
||||||
|
drop_if_exists(index(:following_relationships, [:follower_id]))
|
||||||
|
|
||||||
|
create_if_not_exists(index(:following_relationships, [:following_id]))
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,11 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
Oban.Migrations.up(version: 8)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
Oban.Migrations.down(version: 7)
|
||||||
|
end
|
||||||
|
end
|
Binary file not shown.
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"finmoji": {
|
||||||
|
"license": "CC BY-NC-ND 4.0",
|
||||||
|
"homepage": "https://finland.fi/emoji/",
|
||||||
|
"description": "Finland is the first country in the world to publish its own set of country themed emojis. The Finland emoji collection contains 56 tongue-in-cheek emotions, which were created to explain some hard-to-describe Finnish emotions, Finnish words and customs.",
|
||||||
|
"src": "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip",
|
||||||
|
"src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D",
|
||||||
|
"files": "finmoji.json"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"blank": "blank.png"
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"blobs.gg": {
|
||||||
|
"src_sha256": "3a12f3a181678d5b3584a62095411b0d60a335118135910d879920f8ade5a57f",
|
||||||
|
"src": "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip",
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"homepage": "https://blobs.gg",
|
||||||
|
"files": "blobs_gg.json",
|
||||||
|
"description": "Blob Emoji from blobs.gg repacked as apng"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
defmodule Mix.Tasks.Pleroma.EmojiTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
alias Mix.Tasks.Pleroma.Emoji
|
||||||
|
|
||||||
|
describe "ls-packs" do
|
||||||
|
test "with default manifest as url" do
|
||||||
|
mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
capture_io(fn -> Emoji.run(["ls-packs"]) end) =~
|
||||||
|
"https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with passed manifest as file" do
|
||||||
|
capture_io(fn ->
|
||||||
|
Emoji.run(["ls-packs", "-m", "test/fixtures/emoji/packs/manifest.json"])
|
||||||
|
end) =~ "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get-packs" do
|
||||||
|
test "download pack from default manifest" do
|
||||||
|
mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/finmoji.json"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/finmoji.json")
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert capture_io(fn -> Emoji.run(["get-packs", "finmoji"]) end) =~ "Writing pack.json for"
|
||||||
|
|
||||||
|
emoji_path =
|
||||||
|
Path.join(
|
||||||
|
Pleroma.Config.get!([:instance, :static_dir]),
|
||||||
|
"emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert File.exists?(Path.join([emoji_path, "finmoji", "pack.json"]))
|
||||||
|
on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "pack not found" do
|
||||||
|
mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert capture_io(fn -> Emoji.run(["get-packs", "not_found"]) end) =~
|
||||||
|
"No pack named \"not_found\" found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "raise on bad sha256" do
|
||||||
|
mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_raise RuntimeError, ~r/^Bad SHA256 for blobs.gg/, fn ->
|
||||||
|
capture_io(fn ->
|
||||||
|
Emoji.run(["get-packs", "blobs.gg", "-m", "test/fixtures/emoji/packs/manifest.json"])
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "gen-pack" do
|
||||||
|
setup do
|
||||||
|
url = "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||||
|
|
||||||
|
mock(fn %{
|
||||||
|
method: :get,
|
||||||
|
url: ^url
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/emoji/packs/blank.png.zip")}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, url: url}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with default extensions", %{url: url} do
|
||||||
|
name = "pack1"
|
||||||
|
pack_json = "#{name}.json"
|
||||||
|
files_json = "#{name}_file.json"
|
||||||
|
refute File.exists?(pack_json)
|
||||||
|
refute File.exists?(files_json)
|
||||||
|
|
||||||
|
captured =
|
||||||
|
capture_io(fn ->
|
||||||
|
Emoji.run([
|
||||||
|
"gen-pack",
|
||||||
|
url,
|
||||||
|
"--name",
|
||||||
|
name,
|
||||||
|
"--license",
|
||||||
|
"license",
|
||||||
|
"--homepage",
|
||||||
|
"homepage",
|
||||||
|
"--description",
|
||||||
|
"description",
|
||||||
|
"--files",
|
||||||
|
files_json,
|
||||||
|
"--extensions",
|
||||||
|
".png .gif"
|
||||||
|
])
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert captured =~ "#{pack_json} has been created with the pack1 pack"
|
||||||
|
assert captured =~ "Using .png .gif extensions"
|
||||||
|
|
||||||
|
assert File.exists?(pack_json)
|
||||||
|
assert File.exists?(files_json)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
File.rm!(pack_json)
|
||||||
|
File.rm!(files_json)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with custom extensions and update existing files", %{url: url} do
|
||||||
|
name = "pack2"
|
||||||
|
pack_json = "#{name}.json"
|
||||||
|
files_json = "#{name}_file.json"
|
||||||
|
refute File.exists?(pack_json)
|
||||||
|
refute File.exists?(files_json)
|
||||||
|
|
||||||
|
captured =
|
||||||
|
capture_io(fn ->
|
||||||
|
Emoji.run([
|
||||||
|
"gen-pack",
|
||||||
|
url,
|
||||||
|
"--name",
|
||||||
|
name,
|
||||||
|
"--license",
|
||||||
|
"license",
|
||||||
|
"--homepage",
|
||||||
|
"homepage",
|
||||||
|
"--description",
|
||||||
|
"description",
|
||||||
|
"--files",
|
||||||
|
files_json,
|
||||||
|
"--extensions",
|
||||||
|
" .png .gif .jpeg "
|
||||||
|
])
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert captured =~ "#{pack_json} has been created with the pack2 pack"
|
||||||
|
assert captured =~ "Using .png .gif .jpeg extensions"
|
||||||
|
|
||||||
|
assert File.exists?(pack_json)
|
||||||
|
assert File.exists?(files_json)
|
||||||
|
|
||||||
|
captured =
|
||||||
|
capture_io(fn ->
|
||||||
|
Emoji.run([
|
||||||
|
"gen-pack",
|
||||||
|
url,
|
||||||
|
"--name",
|
||||||
|
name,
|
||||||
|
"--license",
|
||||||
|
"license",
|
||||||
|
"--homepage",
|
||||||
|
"homepage",
|
||||||
|
"--description",
|
||||||
|
"description",
|
||||||
|
"--files",
|
||||||
|
files_json,
|
||||||
|
"--extensions",
|
||||||
|
" .png .gif .jpeg "
|
||||||
|
])
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert captured =~ "#{pack_json} has been updated with the pack2 pack"
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
File.rm!(pack_json)
|
||||||
|
File.rm!(files_json)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,83 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "likes" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
|
||||||
|
|
||||||
|
valid_like = %{
|
||||||
|
"to" => [user.ap_id],
|
||||||
|
"cc" => [],
|
||||||
|
"type" => "Like",
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"object" => post_activity.data["object"],
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"context" => "a context"
|
||||||
|
}
|
||||||
|
|
||||||
|
%{valid_like: valid_like, user: user, post_activity: post_activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
|
||||||
|
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||||
|
|
||||||
|
assert "id" in Map.keys(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "is valid for a valid object", %{valid_like: valid_like} do
|
||||||
|
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
||||||
|
without_actor = Map.delete(valid_like, "actor")
|
||||||
|
|
||||||
|
refute LikeValidator.cast_and_validate(without_actor).valid?
|
||||||
|
|
||||||
|
with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
|
||||||
|
|
||||||
|
refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
||||||
|
without_object = Map.delete(valid_like, "object")
|
||||||
|
|
||||||
|
refute LikeValidator.cast_and_validate(without_object).valid?
|
||||||
|
|
||||||
|
with_invalid_object = Map.put(valid_like, "object", "invalidobject")
|
||||||
|
|
||||||
|
refute LikeValidator.cast_and_validate(with_invalid_object).valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it errors when the actor has already like the object", %{
|
||||||
|
valid_like: valid_like,
|
||||||
|
user: user,
|
||||||
|
post_activity: post_activity
|
||||||
|
} do
|
||||||
|
_like = CommonAPI.favorite(user, post_activity.id)
|
||||||
|
|
||||||
|
refute LikeValidator.cast_and_validate(valid_like).valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
|
||||||
|
wrapped_like =
|
||||||
|
valid_like
|
||||||
|
|> Map.put("actor", %{"id" => valid_like["actor"]})
|
||||||
|
|> Map.put("object", %{"id" => valid_like["object"]})
|
||||||
|
|
||||||
|
validated = LikeValidator.cast_and_validate(wrapped_like)
|
||||||
|
|
||||||
|
assert validated.valid?
|
||||||
|
|
||||||
|
assert {:actor, valid_like["actor"]} in validated.changes
|
||||||
|
assert {:object, valid_like["object"]} in validated.changes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,35 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "Notes" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
note = %{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"type" => "Note",
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"to" => [user.follower_address],
|
||||||
|
"cc" => [],
|
||||||
|
"content" => "Hellow this is content.",
|
||||||
|
"context" => "xxx",
|
||||||
|
"summary" => "a post"
|
||||||
|
}
|
||||||
|
|
||||||
|
%{user: user, note: note}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "a basic note validates", %{note: note} do
|
||||||
|
%{valid?: true} = NoteValidator.cast_and_validate(note)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,32 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
test "it validates an xsd:Datetime" do
|
||||||
|
valid_strings = [
|
||||||
|
"2004-04-12T13:20:00",
|
||||||
|
"2004-04-12T13:20:15.5",
|
||||||
|
"2004-04-12T13:20:00-05:00",
|
||||||
|
"2004-04-12T13:20:00Z"
|
||||||
|
]
|
||||||
|
|
||||||
|
invalid_strings = [
|
||||||
|
"2004-04-12T13:00",
|
||||||
|
"2004-04-1213:20:00",
|
||||||
|
"99-04-12T13:00",
|
||||||
|
"2004-04-12"
|
||||||
|
]
|
||||||
|
|
||||||
|
assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z")
|
||||||
|
|
||||||
|
Enum.each(valid_strings, fn date_time ->
|
||||||
|
result = DateTime.cast(date_time)
|
||||||
|
assert {:ok, _} = result
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(invalid_strings, fn date_time ->
|
||||||
|
result = DateTime.cast(date_time)
|
||||||
|
assert :error == result
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,37 @@
|
|||||||
|
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
@uris [
|
||||||
|
"http://lain.com/users/lain",
|
||||||
|
"http://lain.com",
|
||||||
|
"https://lain.com/object/1"
|
||||||
|
]
|
||||||
|
|
||||||
|
@non_uris [
|
||||||
|
"https://",
|
||||||
|
"rin",
|
||||||
|
1,
|
||||||
|
:x,
|
||||||
|
%{"1" => 2}
|
||||||
|
]
|
||||||
|
|
||||||
|
test "it accepts http uris" do
|
||||||
|
Enum.each(@uris, fn uri ->
|
||||||
|
assert {:ok, uri} == ObjectID.cast(uri)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts an object with a nested uri id" do
|
||||||
|
Enum.each(@uris, fn uri ->
|
||||||
|
assert {:ok, uri} == ObjectID.cast(%{"id" => uri})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects non-uri strings" do
|
||||||
|
Enum.each(@non_uris, fn non_uri ->
|
||||||
|
assert :error == ObjectID.cast(non_uri)
|
||||||
|
assert :error == ObjectID.cast(%{"id" => non_uri})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,87 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.PipelineTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Mock
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "common_pipeline/2" do
|
||||||
|
test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
meta = [local: true]
|
||||||
|
|
||||||
|
with_mocks([
|
||||||
|
{Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.MRF,
|
||||||
|
[],
|
||||||
|
[filter: fn o -> {:ok, o} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.ActivityPub,
|
||||||
|
[],
|
||||||
|
[persist: fn o, m -> {:ok, o, m} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.SideEffects,
|
||||||
|
[],
|
||||||
|
[handle: fn o, m -> {:ok, o, m} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.Federator,
|
||||||
|
[],
|
||||||
|
[publish: fn _o -> :ok end]
|
||||||
|
}
|
||||||
|
]) do
|
||||||
|
assert {:ok, ^activity, ^meta} =
|
||||||
|
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
|
||||||
|
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
|
||||||
|
assert_called(Pleroma.Web.Federator.publish(activity))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
meta = [local: false]
|
||||||
|
|
||||||
|
with_mocks([
|
||||||
|
{Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.MRF,
|
||||||
|
[],
|
||||||
|
[filter: fn o -> {:ok, o} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.ActivityPub,
|
||||||
|
[],
|
||||||
|
[persist: fn o, m -> {:ok, o, m} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.ActivityPub.SideEffects,
|
||||||
|
[],
|
||||||
|
[handle: fn o, m -> {:ok, o, m} end]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pleroma.Web.Federator,
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
]) do
|
||||||
|
assert {:ok, ^activity, ^meta} =
|
||||||
|
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
|
||||||
|
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
|
||||||
|
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,34 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "like objects" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
|
|
||||||
|
{:ok, like_data, _meta} = Builder.like(user, post.object)
|
||||||
|
{:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
|
||||||
|
|
||||||
|
%{like: like, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "add the like to the original object", %{like: like, user: user} do
|
||||||
|
{:ok, like, _} = SideEffects.handle(like)
|
||||||
|
object = Object.get_by_ap_id(like.data["object"])
|
||||||
|
assert object.data["like_count"] == 1
|
||||||
|
assert user.ap_id in object.data["likes"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,45 @@
|
|||||||
|
# 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.AppOperationTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.ApiSpec
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||||
|
|
||||||
|
import OpenApiSpex.TestAssertions
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "AppCreateRequest example matches schema" do
|
||||||
|
api_spec = ApiSpec.spec()
|
||||||
|
schema = AppCreateRequest.schema()
|
||||||
|
assert_schema(schema.example, "AppCreateRequest", api_spec)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "AppCreateResponse example matches schema" do
|
||||||
|
api_spec = ApiSpec.spec()
|
||||||
|
schema = AppCreateResponse.schema()
|
||||||
|
assert_schema(schema.example, "AppCreateResponse", api_spec)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "AppController produces a AppCreateResponse", %{conn: conn} do
|
||||||
|
api_spec = ApiSpec.spec()
|
||||||
|
app_attrs = build(:oauth_app)
|
||||||
|
|
||||||
|
json =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post(
|
||||||
|
"/api/v1/apps",
|
||||||
|
Jason.encode!(%{
|
||||||
|
client_name: app_attrs.client_name,
|
||||||
|
redirect_uris: app_attrs.redirect_uris
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert_schema(json, "AppCreateResponse", api_spec)
|
||||||
|
end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue