diff options
80 files changed, 2138 insertions, 337 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 522285efe..9a15ad1b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.  - Mastodon API: Add support for filtering replies in public and home timelines  - Admin API: endpoints for create/update/delete OAuth Apps. +- Admin API: endpoint for status view.  </details>  ### Fixed @@ -37,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  - **Breaking**: SimplePolicy `:reject` and `:accept` allow deletions again  - 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 @@ -47,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. diff --git a/config/config.exs b/config/config.exs index a6c6d6f99..ca9bbab64 100644 --- a/config/config.exs +++ b/config/config.exs @@ -653,6 +653,8 @@ config :pleroma, :restrict_unauthenticated,    profiles: %{local: false, remote: false},    activities: %{local: false, remote: false} +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false +  # Import environment specific config. This must remain at the bottom  # of this file so it overrides the configuration defined above.  import_config "#{Mix.env()}.exs" diff --git a/config/description.exs b/config/description.exs index 9d8e3b93c..1b2afebef 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2247,6 +2247,7 @@ config :pleroma, :config_description, [          children: [            %{              key: :active, +            label: "Enabled",              type: :boolean,              description: "Globally enable or disable digest emails"            }, @@ -3194,5 +3195,19 @@ config :pleroma, :config_description, [          ]        }      ] +  }, +  %{ +    group: :pleroma, +    key: Pleroma.Web.ApiSpec.CastAndValidate, +    type: :group, +    children: [ +      %{ +        key: :strict, +        type: :boolean, +        description: +          "Enables strict input validation (useful in development, not recommended in production)", +        suggestions: [false] +      } +    ]    }  ] diff --git a/config/dev.exs b/config/dev.exs index 7e1e3b4be..4faaeff5b 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -52,6 +52,8 @@ config :pleroma, Pleroma.Repo,    hostname: "localhost",    pool_size: 10 +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true +  if File.exists?("./config/dev.secret.exs") do    import_config "dev.secret.exs"  else diff --git a/config/test.exs b/config/test.exs index 040e67e4a..cbf775109 100644 --- a/config/test.exs +++ b/config/test.exs @@ -96,6 +96,8 @@ config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true  config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true +  if File.exists?("./config/test.secret.exs") do    import_config "test.secret.exs"  else diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 6202c5a1a..23af08961 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -755,6 +755,17 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret      - 400 Bad Request `"Invalid parameters"` when `status` is missing    - On success: `204`, empty response +## `GET /api/pleroma/admin/statuses/:id` + +### Show status by id + +- Params: +  - `id`: required, status id +- Response: +  - On failure: +    - 404 Not Found `"Not Found"` +  - On success: JSON, Mastodon Status entity +  ## `PUT /api/pleroma/admin/statuses/:id`  ### Change the scope of an individual reported status diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 681ab6b93..705c4c15e 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -924,4 +924,8 @@ Restrict access for unauthenticated users to timelines (public and federate), us    * `remote`  * `activities` - statuses    * `local` -  * `remote`
\ No newline at end of file +  * `remote` + +## Pleroma.Web.ApiSpec.CastAndValidate + +* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`. diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 215265fc9..51bb1bda9 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -128,7 +128,7 @@ defmodule Pleroma.Conversation.Participation 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 @@ defmodule Pleroma.Conversation.Participation do          | last_activity_id: activity_id        }      end) -    |> Enum.filter(& &1.last_activity_id) +    |> Enum.reject(&is_nil(&1.last_activity_id))    end    def get(_, _ \\ []) diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index 7cb49360f..4d61b3650 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -89,11 +89,10 @@ defmodule Pleroma.Filter 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 diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 9c8f5597f..9d5176e2b 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -19,22 +19,7 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlug 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() diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index 7d947339f..09038f3c6 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -19,6 +19,9 @@ defmodule Pleroma.Web.FederatingPlug 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) diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 8d2809bbb..6b3a8a41f 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -91,7 +91,7 @@ defmodule Pleroma.Stats do        peers: peers,        stats: %{          domain_count: domain_count, -        status_count: status_count, +        status_count: status_count || 0,          user_count: user_count        }      } diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 99358ddaf..2c343eb22 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -113,7 +113,6 @@ defmodule Pleroma.User do      field(:is_admin, :boolean, default: false)      field(:show_role, :boolean, default: true)      field(:settings, :map, default: nil) -    field(:magic_key, :string, default: nil)      field(:uri, Types.Uri, default: nil)      field(:hide_followers_count, :boolean, default: false)      field(:hide_follows_count, :boolean, default: false) @@ -387,7 +386,6 @@ defmodule Pleroma.User do          :banner,          :locked,          :last_refreshed_at, -        :magic_key,          :uri,          :follower_address,          :following_address, diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index ac77aab71..3a3b04793 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -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 @@ defmodule Pleroma.User.Query do      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 @@ defmodule Pleroma.User.Query do    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 @@ defmodule Pleroma.User.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)) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index ecb13d76a..697ace488 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1550,21 +1550,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp normalize_counter(counter) when is_integer(counter), do: counter    defp normalize_counter(_), do: 0 -  defp maybe_update_follow_information(data) do +  def maybe_update_follow_information(user_data) do      with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])}, -         {:ok, info} <- fetch_follow_information_for_user(data) do -      info = Map.merge(data[:info] || %{}, info) -      Map.put(data, :info, info) +         {_, true} <- {:user_type_check, user_data[:type] in ["Person", "Service"]}, +         {_, true} <- +           {:collections_available, +            !!(user_data[:following_address] && user_data[:follower_address])}, +         {:ok, info} <- +           fetch_follow_information_for_user(user_data) do +      info = Map.merge(user_data[:info] || %{}, info) + +      user_data +      |> Map.put(:info, info)      else +      {:user_type_check, false} -> +        user_data + +      {:collections_available, false} -> +        user_data +        {:enabled, false} -> -        data +        user_data        e ->          Logger.error( -          "Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e) +          "Follower/Following counter update for #{user_data.ap_id} failed.\n" <> inspect(e)          ) -        data +        user_data      end    end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index f607931ab..ab9d1e0b7 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -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) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index b421612e5..40a505b91 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1205,6 +1205,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 816c11e01..da71e63d9 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug,      %{scopes: ["read:statuses"], admin: true} -    when action in [:list_statuses, :list_user_statuses, :list_instance_statuses] +    when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show]    )    plug( @@ -392,29 +392,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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 -             ) -           ) -  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$/) +    with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do +      json( +        conn, +        AccountView.render("index.json", users: users, count: count, page_size: page_size) +      ) +    end    end    @filters ~w(local external active deactivated is_admin is_moderator) @@ -837,6 +820,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})    end +  def status_show(conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id(id) do +      conn +      |> put_view(StatusView) +      |> render("show.json", %{activity: activity}) +    else +      _ -> errors(conn, {:error, :not_found}) +    end +  end +    def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do      with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do        {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index 29cea1f44..c28efadd5 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.AdminAPI.Search do      query =        params        |> Map.drop([:page, :page_size]) +      |> Map.put(:exclude_service_users, true)        |> User.Query.build()        |> order_by([u], u.nickname) diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index b3c1e3ea2..79fd5f871 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -39,7 +39,12 @@ defmodule Pleroma.Web.ApiSpec 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" +                }                }              }            } diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex new file mode 100644 index 000000000..bd9026237 --- /dev/null +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -0,0 +1,139 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0 +# Copyright © 2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.CastAndValidate do +  @moduledoc """ +  This plug is based on [`OpenApiSpex.Plug.CastAndValidate`] +  (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex). +  The main difference is ignoring unexpected query params instead of throwing +  an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`) +  to disable this behavior. Also, the default rendering error module +  is `Pleroma.Web.ApiSpec.RenderError`. +  """ + +  @behaviour Plug + +  alias Plug.Conn + +  @impl Plug +  def init(opts) do +    opts +    |> Map.new() +    |> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError) +  end + +  @impl Plug +  def call(%{private: %{open_api_spex: private_data}} = conn, %{ +        operation_id: operation_id, +        render_error: render_error +      }) do +    spec = private_data.spec +    operation = private_data.operation_lookup[operation_id] + +    content_type = +      case Conn.get_req_header(conn, "content-type") do +        [header_value | _] -> +          header_value +          |> String.split(";") +          |> List.first() + +        _ -> +          nil +      end + +    private_data = Map.put(private_data, :operation_id, operation_id) +    conn = Conn.put_private(conn, :open_api_spex, private_data) + +    case cast_and_validate(spec, operation, conn, content_type, strict?()) do +      {:ok, conn} -> +        conn + +      {:error, reason} -> +        opts = render_error.init(reason) + +        conn +        |> render_error.call(opts) +        |> Plug.Conn.halt() +    end +  end + +  def call( +        %{ +          private: %{ +            phoenix_controller: controller, +            phoenix_action: action, +            open_api_spex: private_data +          } +        } = conn, +        opts +      ) do +    operation = +      case private_data.operation_lookup[{controller, action}] do +        nil -> +          operation_id = controller.open_api_operation(action).operationId +          operation = private_data.operation_lookup[operation_id] + +          operation_lookup = +            private_data.operation_lookup +            |> Map.put({controller, action}, operation) + +          OpenApiSpex.Plug.Cache.adapter().put( +            private_data.spec_module, +            {private_data.spec, operation_lookup} +          ) + +          operation + +        operation -> +          operation +      end + +    if operation.operationId do +      call(conn, Map.put(opts, :operation_id, operation.operationId)) +    else +      raise "operationId was not found in action API spec" +    end +  end + +  def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts) + +  defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do +    OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) +  end + +  defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do +    case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do +      {:ok, conn} -> +        {:ok, conn} + +      # Remove unexpected query params and cast/validate again +      {:error, errors} -> +        query_params = +          Enum.reduce(errors, conn.query_params, fn +            %{reason: :unexpected_field, name: name, path: [name]}, params -> +              Map.delete(params, name) + +            %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> +              path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() +              update_in(params, path, &List.delete(&1, value)) + +            _, params -> +              params +          end) + +        conn = %Conn{conn | query_params: query_params} +        OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) +    end +  end + +  defp list_items_to_string(list) do +    Enum.map(list, fn +      i when is_atom(i) -> to_string(i) +      i -> i +    end) +  end + +  defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false) +end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index fe9548b1b..470fc0215 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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"} diff --git a/lib/pleroma/web/api_spec/operations/conversation_operation.ex b/lib/pleroma/web/api_spec/operations/conversation_operation.ex new file mode 100644 index 000000000..475468893 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/conversation_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/filter_operation.ex b/lib/pleroma/web/api_spec/operations/filter_operation.ex new file mode 100644 index 000000000..53e57b46b --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/filter_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex new file mode 100644 index 000000000..ac4aee6da --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex new file mode 100644 index 000000000..880bd3f1b --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex new file mode 100644 index 000000000..c88ed5dd0 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/marker_operation.ex b/lib/pleroma/web/api_spec/operations/marker_operation.ex new file mode 100644 index 000000000..06620492a --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/marker_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex new file mode 100644 index 000000000..fe675a923 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex @@ -0,0 +1,96 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.ApiError +  alias Pleroma.Web.ApiSpec.Schemas.FlakeID +  alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus + +  import Pleroma.Web.ApiSpec.Helpers + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def index_operation do +    %Operation{ +      tags: ["Scheduled Statuses"], +      summary: "View scheduled statuses", +      security: [%{"oAuth" => ["read:statuses"]}], +      parameters: pagination_params(), +      operationId: "ScheduledActivity.index", +      responses: %{ +        200 => +          Operation.response("Array of ScheduledStatus", "application/json", %Schema{ +            type: :array, +            items: ScheduledStatus +          }) +      } +    } +  end + +  def show_operation do +    %Operation{ +      tags: ["Scheduled Statuses"], +      summary: "View a single scheduled status", +      security: [%{"oAuth" => ["read:statuses"]}], +      parameters: [id_param()], +      operationId: "ScheduledActivity.show", +      responses: %{ +        200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), +        404 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def update_operation do +    %Operation{ +      tags: ["Scheduled Statuses"], +      summary: "Schedule a status", +      operationId: "ScheduledActivity.update", +      security: [%{"oAuth" => ["write:statuses"]}], +      parameters: [id_param()], +      requestBody: +        request_body("Parameters", %Schema{ +          type: :object, +          properties: %{ +            scheduled_at: %Schema{ +              type: :string, +              format: :"date-time", +              description: +                "ISO 8601 Datetime at which the status will be published. Must be at least 5 minutes into the future." +            } +          } +        }), +      responses: %{ +        200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), +        404 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  def delete_operation do +    %Operation{ +      tags: ["Scheduled Statuses"], +      summary: "Cancel a scheduled status", +      security: [%{"oAuth" => ["write:statuses"]}], +      parameters: [id_param()], +      operationId: "ScheduledActivity.delete", +      responses: %{ +        200 => Operation.response("Empty object", "application/json", %Schema{type: :object}), +        404 => Operation.response("Error", "application/json", ApiError) +      } +    } +  end + +  defp id_param do +    Operation.parameter(:id, :path, FlakeID, "Poll ID", +      example: "123", +      required: true +    ) +  end +end diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex new file mode 100644 index 000000000..663b8fa11 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index b5877ca9c..d476b8ef3 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -17,6 +17,9 @@ defmodule Pleroma.Web.ApiSpec.RenderError do    def call(conn, errors) do      errors =        Enum.map(errors, fn +        %{name: nil, reason: :invalid_enum} = err -> +          %OpenApiSpex.Cast.Error{err | name: err.value} +          %{name: nil} = err ->            %OpenApiSpex.Cast.Error{err | name: List.last(err.path)} diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex new file mode 100644 index 000000000..c146c416e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/attachment.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do +  alias OpenApiSpex.Schema + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "Attachment", +    description: "Represents a file or media attachment that can be added to a status.", +    type: :object, +    requried: [:id, :url, :preview_url], +    properties: %{ +      id: %Schema{type: :string}, +      url: %Schema{ +        type: :string, +        format: :uri, +        description: "The location of the original full-size attachment" +      }, +      remote_url: %Schema{ +        type: :string, +        format: :uri, +        description: +          "The location of the full-size original attachment on the remote website. String (URL), or null if the attachment is local", +        nullable: true +      }, +      preview_url: %Schema{ +        type: :string, +        format: :uri, +        description: "The location of a scaled-down preview of the attachment" +      }, +      text_url: %Schema{ +        type: :string, +        format: :uri, +        description: "A shorter URL for the attachment" +      }, +      description: %Schema{ +        type: :string, +        nullable: true, +        description: +          "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load" +      }, +      type: %Schema{ +        type: :string, +        enum: ["image", "video", "audio", "unknown"], +        description: "The type of the attachment" +      }, +      pleroma: %Schema{ +        type: :object, +        properties: %{ +          mime_type: %Schema{type: :string, description: "mime type of the attachment"} +        } +      } +    }, +    example: %{ +      id: "1638338801", +      type: "image", +      url: "someurl", +      remote_url: "someurl", +      preview_url: "someurl", +      text_url: "someurl", +      description: nil, +      pleroma: %{mime_type: "image/png"} +    } +  }) +end diff --git a/lib/pleroma/web/api_spec/schemas/conversation.ex b/lib/pleroma/web/api_spec/schemas/conversation.ex new file mode 100644 index 000000000..d8ff5ba26 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/conversation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex new file mode 100644 index 000000000..b7d1685c9 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/schemas/push_subscription.ex b/lib/pleroma/web/api_spec/schemas/push_subscription.ex new file mode 100644 index 000000000..cc91b95b8 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/push_subscription.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex new file mode 100644 index 000000000..0520d0848 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.Attachment +  alias Pleroma.Web.ApiSpec.Schemas.Poll +  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "ScheduledStatus", +    description: "Represents a status that will be published at a future scheduled date.", +    type: :object, +    required: [:id, :scheduled_at, :params], +    properties: %{ +      id: %Schema{type: :string}, +      scheduled_at: %Schema{type: :string, format: :"date-time"}, +      media_attachments: %Schema{type: :array, items: Attachment}, +      params: %Schema{ +        type: :object, +        required: [:text, :visibility], +        properties: %{ +          text: %Schema{type: :string, nullable: true}, +          media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, +          sensitive: %Schema{type: :boolean, nullable: true}, +          spoiler_text: %Schema{type: :string, nullable: true}, +          visibility: %Schema{type: VisibilityScope, nullable: true}, +          scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, +          poll: %Schema{type: Poll, nullable: true}, +          in_reply_to_id: %Schema{type: :string, nullable: true} +        } +      } +    }, +    example: %{ +      id: "3221", +      scheduled_at: "2019-12-05T12:33:01.000Z", +      params: %{ +        text: "test content", +        media_ids: nil, +        sensitive: nil, +        spoiler_text: nil, +        visibility: nil, +        scheduled_at: nil, +        poll: nil, +        idempotency: nil, +        in_reply_to_id: nil +      }, +      media_attachments: [Attachment.schema().example] +    } +  }) +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index aef0588d4..7a804461f 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.ApiSpec.Schemas.Status do    alias OpenApiSpex.Schema    alias Pleroma.Web.ApiSpec.Schemas.Account +  alias Pleroma.Web.ApiSpec.Schemas.Attachment    alias Pleroma.Web.ApiSpec.Schemas.Emoji    alias Pleroma.Web.ApiSpec.Schemas.FlakeID    alias Pleroma.Web.ApiSpec.Schemas.Poll @@ -50,22 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do        language: %Schema{type: :string, nullable: true},        media_attachments: %Schema{          type: :array, -        items: %Schema{ -          type: :object, -          properties: %{ -            id: %Schema{type: :string}, -            url: %Schema{type: :string, format: :uri}, -            remote_url: %Schema{type: :string, format: :uri}, -            preview_url: %Schema{type: :string, format: :uri}, -            text_url: %Schema{type: :string, format: :uri}, -            description: %Schema{type: :string}, -            type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]}, -            pleroma: %Schema{ -              type: :object, -              properties: %{mime_type: %Schema{type: :string}} -            } -          } -        } +        items: Attachment        },        mentions: %Schema{          type: :array, @@ -86,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{ diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index e27f85929..1b72e23dc 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.Feed.UserController do        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 diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 61b0e2f63..8458cbdd5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.TwitterAPI.TwitterAPI -  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 408e11474..a516b6c20 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.AppController do    plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) -  plug(OpenApiSpex.Plug.CastAndValidate) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    @local_mastodon_name "Mastodon-Local" diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index c44641526..f35ec3596 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController 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 diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index 000ad743f..c5f47c5df 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do    use Pleroma.Web, :controller -  plug(OpenApiSpex.Plug.CastAndValidate) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(      :skip_plug, diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index c4fa383f2..825b231ab 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do    alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User -  plug(OpenApiSpex.Plug.CastAndValidate) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation    plug( diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index 7fd0562c9..abbf0ce02 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -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 -    } - -    {:ok, response} = Filter.update(query) -    render(conn, "filter.json", filter: response) +    params = +      params +      |> Map.delete(:irreversible) +      |> Map.put(:hide, params[:irreversible]) +      |> Enum.reject(fn {_key, value} -> is_nil(value) end) +      |> Map.new() + +    # 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 diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 25f2269b9..748b6b475 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController 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() diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 237f85677..d8859731d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -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") diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index bfe856025..acdc76fd2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.ListController 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 @@ defmodule Pleroma.Web.MastodonAPI.ListController 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 @@ defmodule Pleroma.Web.MastodonAPI.ListController 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 @@ defmodule Pleroma.Web.MastodonAPI.ListController do    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 @@ defmodule Pleroma.Web.MastodonAPI.ListController do      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() diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 9f9d4574e..85310edfa 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -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}) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index a14c86893..596b85617 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do    @oauth_read_actions [:show, :index] -  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(      OAuthScopesPlug, diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index f65c5c62b..405167108 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) -  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 899b78873..1719c67ea 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do    alias Pleroma.ScheduledActivity    alias Pleroma.Web.MastodonAPI.MastodonAPI -  plug(:assign_scheduled_activity when action != :index) -    @oauth_read_actions [:show, :index] +  plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)    plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) +  plug(:assign_scheduled_activity when action != :index)    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation +    @doc "GET /api/v1/scheduled_statuses"    def index(%{assigns: %{user: user}} = conn, params) do +    params = Map.new(params, fn {key, value} -> {to_string(key), value} end) +      with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do        conn        |> add_link_headers(scheduled_activities) @@ -35,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do    end    @doc "PUT /api/v1/scheduled_statuses/:id" -  def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do +  def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do      with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do        render(conn, "show.json", scheduled_activity: scheduled_activity)      end @@ -48,7 +52,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do      end    end -  defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do +  defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do      case ScheduledActivity.get(user, id) do        %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)        nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index d184ea1d0..34eac97c5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController 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 diff --git a/lib/pleroma/web/mastodon_api/views/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex index 97fd1e83f..aeff646f5 100644 --- a/lib/pleroma/web/mastodon_api/views/filter_view.ex +++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex @@ -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) diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 985368fe5..9705b7a91 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -6,12 +6,13 @@ 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, %{ -        last_read_id: m.last_read_id, -        version: m.lock_version, -        updated_at: NaiveDateTime.to_iso8601(m.updated_at) -      }) +    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) +       }}      end)    end  end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 6fd3cfce5..6971cd9f8 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -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( diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index b99b0c5fb..3e401a490 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -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 @@ defmodule Pleroma.Web.Push.Subscription do          %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 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index d6803e8ac..6b16cfa5d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -188,6 +188,7 @@ defmodule Pleroma.Web.Router do      post("/reports/:id/notes", AdminAPIController, :report_notes_create)      delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) +    get("/statuses/:id", AdminAPIController, :status_show)      put("/statuses/:id", AdminAPIController, :status_update)      delete("/statuses/:id", AdminAPIController, :status_delete)      get("/statuses", AdminAPIController, :list_statuses) @@ -436,7 +437,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) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 7a35238d7..c3efb6651 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -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"] diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 08e42a7e5..4f9281851 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -200,11 +200,17 @@ defmodule Pleroma.Web 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 = diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 7ffd0e51b..71ccf251a 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -86,54 +86,24 @@ defmodule Pleroma.Web.WebFinger do      |> XmlBuilder.to_doc()    end -  defp get_magic_key("data:application/magic-public-key," <> magic_key) do -    {:ok, magic_key} -  end - -  defp get_magic_key(nil) do -    Logger.debug("Undefined magic key.") -    {:ok, nil} -  end +  defp webfinger_from_xml(doc) do +    subject = XML.string_from_xpath("//Subject", doc) -  defp get_magic_key(_) do -    {:error, "Missing magic key data."} -  end +    subscribe_address = +      ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template} +      |> XML.string_from_xpath(doc) -  defp webfinger_from_xml(doc) do -    with magic_key <- XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc), -         {:ok, magic_key} <- get_magic_key(magic_key), -         topic <- -           XML.string_from_xpath( -             ~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, -             doc -           ), -         subject <- XML.string_from_xpath("//Subject", doc), -         subscribe_address <- -           XML.string_from_xpath( -             ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, -             doc -           ), -         ap_id <- -           XML.string_from_xpath( -             ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, -             doc -           ) do -      data = %{ -        "magic_key" => magic_key, -        "topic" => topic, -        "subject" => subject, -        "subscribe_address" => subscribe_address, -        "ap_id" => ap_id -      } +    ap_id = +      ~s{//Link[@rel="self" and @type="application/activity+json"]/@href} +      |> XML.string_from_xpath(doc) -      {:ok, data} -    else -      {:error, e} -> -        {:error, e} +    data = %{ +      "subject" => subject, +      "subscribe_address" => subscribe_address, +      "ap_id" => ap_id +    } -      e -> -        {:error, e} -    end +    {:ok, data}    end    defp webfinger_from_json(doc) do @@ -146,9 +116,6 @@ defmodule Pleroma.Web.WebFinger do            {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->              Map.put(data, "ap_id", link["href"]) -          {_, "http://ostatus.org/schema/1.0/subscribe"} -> -            Map.put(data, "subscribe_address", link["template"]) -            _ ->              Logger.debug("Unhandled type: #{inspect(link["type"])}")              data @@ -194,13 +161,15 @@ defmodule Pleroma.Web.WebFinger 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 <- diff --git a/priv/repo/migrations/20200505072231_remove_magic_key_field.exs b/priv/repo/migrations/20200505072231_remove_magic_key_field.exs new file mode 100644 index 000000000..2635e671b --- /dev/null +++ b/priv/repo/migrations/20200505072231_remove_magic_key_field.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.RemoveMagicKeyField do +  use Ecto.Migration + +  def change do +    alter table(:users) do +      remove(:magic_key, :string) +    end +  end +end diff --git a/test/filter_test.exs b/test/filter_test.exs index b2a8330ee..63a30c736 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -141,17 +141,15 @@ defmodule Pleroma.FilterTest 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 diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs index 689fe757f..4e6142aab 100644 --- a/test/plugs/ensure_authenticated_plug_test.exs +++ b/test/plugs/ensure_authenticated_plug_test.exs @@ -27,8 +27,8 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest 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 diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index fa30a0c41..91c03b1a8 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -74,7 +74,7 @@ defmodule Pleroma.Web.ConnCase do          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 diff --git a/test/support/helpers.ex b/test/support/helpers.ex index e68e9bfd2..26281b45e 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -40,12 +40,18 @@ defmodule Pleroma.Tests.Helpers do            clear_config: 2          ] -      def to_datetime(naive_datetime) do +      def to_datetime(%NaiveDateTime{} = naive_datetime) do          naive_datetime          |> DateTime.from_naive!("Etc/UTC")          |> DateTime.truncate(:second)        end +      def to_datetime(datetime) when is_binary(datetime) do +        datetime +        |> NaiveDateTime.from_iso8601!() +        |> to_datetime() +      end +        def collect_ids(collection) do          collection          |> Enum.map(& &1.id) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 9624cb0f7..3a95e92da 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -211,7 +211,7 @@ defmodule HttpRequestMock do    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 @@ defmodule HttpRequestMock do    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 @@ defmodule HttpRequestMock do    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 @@ defmodule HttpRequestMock 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 @@ defmodule HttpRequestMock 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 @@ defmodule HttpRequestMock 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 @@ defmodule HttpRequestMock do    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"}] diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index a8f1f0e26..5c8d20ac4 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -820,21 +820,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        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 diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index edd7dfb22..84ead93bb 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -18,9 +18,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Federator +  import ExUnit.CaptureLog +  import Mock    import Pleroma.Factory    import Tesla.Mock -  import Mock    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -2403,4 +2404,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do       u3: %{r1: r3_1.id, r2: r3_2.id},       u4: %{r1: r4_1.id}}    end + +  describe "maybe_update_follow_information/1" do +    setup do +      clear_config([:instance, :external_user_synchronization], true) + +      user = %{ +        local: false, +        ap_id: "https://gensokyo.2hu/users/raymoo", +        following_address: "https://gensokyo.2hu/users/following", +        follower_address: "https://gensokyo.2hu/users/followers", +        type: "Person" +      } + +      %{user: user} +    end + +    test "logs an error when it can't fetch the info", %{user: user} do +      assert capture_log(fn -> +               ActivityPub.maybe_update_follow_information(user) +             end) =~ "Follower/Following counter update for #{user.ap_id} failed" +    end + +    test "just returns the input if the user type is Application", %{ +      user: user +    } do +      user = +        user +        |> Map.put(:type, "Application") + +      refute capture_log(fn -> +               assert ^user = ActivityPub.maybe_update_follow_information(user) +             end) =~ "Follower/Following counter update for #{user.ap_id} failed" +    end + +    test "it just returns the input if the user has no following/follower addresses", %{ +      user: user +    } do +      user = +        user +        |> Map.put(:following_address, nil) +        |> Map.put(:follower_address, nil) + +      refute capture_log(fn -> +               assert ^user = ActivityPub.maybe_update_follow_information(user) +             end) =~ "Follower/Following counter update for #{user.ap_id} failed" +    end +  end  end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 1862a9589..78c79bb07 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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) @@ -1620,6 +1654,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end    end +  describe "GET /api/pleroma/admin/statuses/:id" do +    test "not found", %{conn: conn} do +      assert conn +             |> get("/api/pleroma/admin/statuses/not_found") +             |> json_response(:not_found) +    end + +    test "shows activity", %{conn: conn} do +      activity = insert(:note_activity) + +      response = +        conn +        |> get("/api/pleroma/admin/statuses/#{activity.id}") +        |> json_response(200) + +      assert response["id"] == activity.id +    end +  end +    describe "PUT /api/pleroma/admin/statuses/:id" do      setup do        activity = insert(:note_activity) @@ -3526,7 +3579,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "success", %{conn: conn} do -      base_url = Pleroma.Web.base_url() +      base_url = Web.base_url()        app_name = "Trusted app"        response = @@ -3547,7 +3600,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "with trusted", %{conn: conn} do -      base_url = Pleroma.Web.base_url() +      base_url = Web.base_url()        app_name = "Trusted app"        response = diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs index 801b0259b..04695572e 100644 --- a/test/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -36,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do          "visibility" => "direct"        }) -    [conversation1, conversation2] = -      conn -      |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) -      |> json_response(200) +    assert [conversation1, conversation2] = +             conn +             |> 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 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest 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 diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 97ab005e0..f29547d13 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -15,9 +15,12 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest 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 diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs index d8dbe4800..44e12d15a 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest 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) diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index 2c7fd9fd0..90840d5ab 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest 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 diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index c9c4cbb49..57a9ef4a4 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest 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 diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 919f295bd..bce719bea 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -22,8 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest 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" => %{ @@ -45,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest 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 @@ -60,11 +60,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest 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" => %{ @@ -89,11 +90,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest 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" => %{ @@ -112,11 +114,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest 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 diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index f86274d57..1ff871c89 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -24,19 +24,19 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      # min_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result      # since_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result      # max_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result    end @@ -46,12 +46,12 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) +    assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200)      assert scheduled_activity_id == scheduled_activity.id |> to_string()      res_conn = get(conn, "/api/v1/scheduled_statuses/404") -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end    test "updates a scheduled activity" do @@ -74,22 +74,32 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      assert job.args == %{"activity_id" => scheduled_activity.id}      assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) -    new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) +    new_scheduled_at = +      NaiveDateTime.utc_now() +      |> Timex.shift(minutes: 120) +      |> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime)      res_conn = -      put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ +      conn +      |> put_req_header("content-type", "application/json") +      |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{          scheduled_at: new_scheduled_at        }) -    assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) +    assert %{"scheduled_at" => expected_scheduled_at} = +             json_response_and_validate_schema(res_conn, 200) +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)      job = refresh_record(job)      assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) -    res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) +    res_conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end    test "deletes a scheduled activity" do @@ -115,7 +125,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do        |> assign(:user, user)        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{} = json_response(res_conn, 200) +    assert %{} = json_response_and_validate_schema(res_conn, 200)      refute Repo.get(ScheduledActivity, scheduled_activity.id)      refute Repo.get(Oban.Job, job.id) @@ -124,6 +134,6 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do        |> assign(:user, user)        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end  end diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 5682498c0..4aa260663 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        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) diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 6791c2fb0..451723e60 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -402,11 +402,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        pleroma: %{mime_type: "image/png"}      } +    api_spec = Pleroma.Web.ApiSpec.spec() +      assert expected == StatusView.render("attachment.json", %{attachment: object}) +    OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec)      # If theres a "id", use that instead of the generated one      object = Map.put(object, "id", 2) -    assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) +    result = StatusView.render("attachment.json", %{attachment: object}) + +    assert %{id: "2"} = result +    OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec)    end    test "put the url advertised in the Activity in to the url attribute" do diff --git a/test/web/plugs/plug_test.exs b/test/web/plugs/plug_test.exs new file mode 100644 index 000000000..943e484e7 --- /dev/null +++ b/test/web/plugs/plug_test.exs @@ -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 diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 4b4282727..f4884e0a2 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -67,7 +67,7 @@ defmodule Pleroma.Web.WebFingerTest do        assert data["magic_key"] == nil        assert data["salmon"] == nil -      assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" +      assert data["topic"] == nil        assert data["subject"] == "acct:kPherox@mstdn.jp"        assert data["ap_id"] == "https://mstdn.jp/users/kPherox"        assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"  | 
