diff options
117 files changed, 2587 insertions, 507 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e6c33f8..56b235f6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +4,72 @@ All notable changes to this project will be documented in this file.  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  ## [unreleased] -### Changed -- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines -  ### Removed  - **Breaking:** removed `with_move` parameter from notifications timeline.  ### Added  - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. +- NodeInfo: `pleroma_emoji_reactions` to the `features` list.  - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.  - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.  <details>    <summary>API Changes</summary>  - Mastodon API: Support for `include_types` in `/api/v1/notifications`. +- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.  </details> +### Fixed +- Support pagination in conversations API + +## [unreleased-patch] +### Fixed +- Logger configuration through AdminFE + +## [2.0.2] - 2020-04-08 +### Added +- Support for Funkwhale's `Audio` activity +- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials` + +### Fixed +- Blocked/muted users still generating push notifications +- Input textbox for bio ignoring newlines +- OTP: Inability to use PostgreSQL databases with SSL +- `user delete_activities` breaking when trying to delete already deleted posts +- Incorrect URL for Funkwhale channels + +### Upgrade notes +1. Restart Pleroma + +## [2.0.1] - 2020-03-15 +### Security +- Static-FE: Fix remote posts not being sanitized + +### Fixed +- 500 errors when no `Accept` header is present if Static-FE is enabled +- Instance panel not being updated immediately due to wrong `Cache-Control` headers +- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE +- OTP: Fix some settings not being migrated to in-database config properly +- No `Cache-Control` headers on attachment/media proxy requests +- Character limit enforcement being off by 1 +- Mastodon Streaming API: hashtag timelines not working + +### Changed +- BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines +- Mastodon API: Allow registration without email if email verification is not enabled + +### Upgrade notes +#### Nginx only +1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header  Cache-Control;` from your config. + +#### Everyone +1. Run database migrations (inside Pleroma directory): +  - OTP: `./bin/pleroma_ctl migrate` +  - From Source: `mix ecto.migrate` +2. Restart Pleroma +  ## [2.0.0] - 2019-03-08  ### Security -- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. +- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.  ### Removed  - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` @@ -1,4 +1,4 @@ -Unless otherwise stated this repository is copyright © 2017-2019 +Unless otherwise stated this repository is copyright © 2017-2020  Pleroma Authors <https://pleroma.social/>, and is distributed under  The GNU Affero General Public License Version 3, you should have received a  copy of the license file as AGPL-3. @@ -23,7 +23,7 @@ priv/static/images/pleroma-fox-tan-shy.png  --- -The following files are copyright © 2017-2019 Pleroma Authors +The following files are copyright © 2017-2020 Pleroma Authors  <https://pleroma.social/>, and are distributed under the Creative Commons  Attribution-ShareAlike 4.0 International license, you should have received  a copy of the license file as CC-BY-SA-4.0. diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex index bd65ac84f..786929ace 100644 --- a/benchmarks/load_testing/fetcher.ex +++ b/benchmarks/load_testing/fetcher.ex @@ -386,47 +386,56 @@ defmodule Pleroma.LoadTesting.Fetcher do      favourites = ActivityPub.fetch_favourites(user) +    output_relationships = +      !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default]) +      Benchee.run(        %{          "Rendering home timeline" => fn ->            StatusView.render("index.json", %{              activities: home_activities,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: !output_relationships            })          end,          "Rendering direct timeline" => fn ->            StatusView.render("index.json", %{              activities: direct_activities,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: !output_relationships            })          end,          "Rendering public timeline" => fn ->            StatusView.render("index.json", %{              activities: public_activities,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: !output_relationships            })          end,          "Rendering tag timeline" => fn ->            StatusView.render("index.json", %{              activities: tag_activities,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: !output_relationships            })          end,          "Rendering notifications" => fn ->            Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{              notifications: notifications, -            for: user +            for: user, +            skip_relationships: !output_relationships            })          end,          "Rendering favourites timeline" => fn ->            StatusView.render("index.json", %{              activities: favourites,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: !output_relationships            })          end        }, diff --git a/config/config.exs b/config/config.exs index 232a91bf1..7f013aaad 100644 --- a/config/config.exs +++ b/config/config.exs @@ -240,6 +240,8 @@ config :pleroma, :instance,    extended_nickname_format: true,    cleanup_attachments: false +config :pleroma, :extensions, output_relationships_in_statuses_by_default: true +  config :pleroma, :feed,    post_title: %{      max_length: 100, diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 000000000..75e845ade --- /dev/null +++ b/coveralls.json @@ -0,0 +1,6 @@ +{ +  "skip_files": [ +    "test/support", +    "lib/mix/tasks/pleroma/benchmark.ex" +  ] +}
\ No newline at end of file diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 58d702347..57fb6bc6a 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret    - `email`    - `name`, optional +- Response: +  - On success: `204`, empty response +  - On failure: +    - 400 Bad Request, JSON: + +    ```json +      [ +        { +          "error": "Appropriate error message here" +        } +      ] +    ``` +  ## `GET /api/pleroma/admin/users/:nickname/password_reset`  ### Get a password reset token for a given nickname diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 12e63ef9f..90c43c356 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -431,7 +431,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa  # Emoji Reactions -Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. +Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. To detect the presence of this feature, you can check `pleroma_emoji_reactions` entry in the features list of nodeinfo.  ## `PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`  ### React to a post with a unicode emoji diff --git a/docs/administration/CLI_tasks/emoji.md b/docs/administration/CLI_tasks/emoji.md index efec8222c..3d524a52b 100644 --- a/docs/administration/CLI_tasks/emoji.md +++ b/docs/administration/CLI_tasks/emoji.md @@ -39,8 +39,8 @@ mix pleroma.emoji get-packs [option ...] <pack ...>  mix pleroma.emoji gen-pack PACK-URL  ``` -Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.  +Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. -  The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously. +  The manifest entry will either be written to a newly created `pack_name.json` file (pack name is asked in questions) or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.    The file list will be written to the file specified previously, *replacing* that file. You _should_ check that the file list doesn't contain anything you don't need in the pack, that is, anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted). diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 4dfcc32e7..3ad6edbfb 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -5,7 +5,6 @@  defmodule Mix.Pleroma do    @doc "Common functions to be reused in mix tasks"    def start_pleroma do -    Mix.Task.run("app.start")      Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)      if Pleroma.Config.get(:env) != :test do diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index dd2b9c8f2..6ab7fe8ef 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do            Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{              activities: activities,              for: user, -            as: :activity +            as: :activity, +            skip_relationships: true            })          end        }, diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 429d763c7..cdffa88b2 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -14,8 +14,8 @@ defmodule Mix.Tasks.Pleroma.Emoji do      {options, [], []} = parse_global_opts(args) -    manifest = -      fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest()) +    url_or_path = options[:manifest] || default_manifest() +    manifest = fetch_manifest(url_or_path)      Enum.each(manifest, fn {name, info} ->        to_print = [ @@ -40,9 +40,9 @@ defmodule Mix.Tasks.Pleroma.Emoji do      {options, pack_names, []} = parse_global_opts(args) -    manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest() +    url_or_path = options[:manifest] || default_manifest() -    manifest = fetch_manifest(manifest_url) +    manifest = fetch_manifest(url_or_path)      for pack_name <- pack_names do        if Map.has_key?(manifest, pack_name) do @@ -75,7 +75,10 @@ defmodule Mix.Tasks.Pleroma.Emoji do          end          # The url specified in files should be in the same directory -        files_url = Path.join(Path.dirname(manifest_url), pack["files"]) +        files_url = +          url_or_path +          |> Path.dirname() +          |> Path.join(pack["files"])          IO.puts(            IO.ANSI.format([ @@ -133,38 +136,51 @@ defmodule Mix.Tasks.Pleroma.Emoji do      end    end -  def run(["gen-pack", src]) do +  def run(["gen-pack" | args]) do      start_pleroma() -    proposed_name = Path.basename(src) |> Path.rootname() -    name = String.trim(IO.gets("Pack name [#{proposed_name}]: ")) -    # If there's no name, use the default one -    name = if String.length(name) > 0, do: name, else: proposed_name +    {opts, [src], []} = +      OptionParser.parse( +        args, +        strict: [ +          name: :string, +          license: :string, +          homepage: :string, +          description: :string, +          files: :string, +          extensions: :string +        ] +      ) -    license = String.trim(IO.gets("License: ")) -    homepage = String.trim(IO.gets("Homepage: ")) -    description = String.trim(IO.gets("Description: ")) +    proposed_name = Path.basename(src) |> Path.rootname() +    name = get_option(opts, :name, "Pack name:", proposed_name) +    license = get_option(opts, :license, "License:") +    homepage = get_option(opts, :homepage, "Homepage:") +    description = get_option(opts, :description, "Description:") -    proposed_files_name = "#{name}.json" -    files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: ")) -    files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name +    proposed_files_name = "#{name}_files.json" +    files_name = get_option(opts, :files, "Save file list to:", proposed_files_name)      default_exts = [".png", ".gif"] -    default_exts_str = Enum.join(default_exts, " ") -    exts = -      String.trim( -        IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ") +    custom_exts = +      get_option( +        opts, +        :extensions, +        "Emoji file extensions (separated with spaces):", +        Enum.join(default_exts, " ")        ) +      |> String.split(" ", trim: true)      exts = -      if String.length(exts) > 0 do -        String.split(exts, " ") -        |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end) -      else +      if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do          default_exts +      else +        custom_exts        end +    IO.puts("Using #{Enum.join(exts, " ")} extensions") +      IO.puts("Downloading the pack and generating SHA256")      binary_archive = Tesla.get!(client(), src).body @@ -194,14 +210,16 @@ defmodule Mix.Tasks.Pleroma.Emoji do      IO.puts("""      #{files_name} has been created and contains the list of all found emojis in the pack. -    Please review the files in the remove those not needed. +    Please review the files in the pack and remove those not needed.      """) -    if File.exists?("index.json") do -      existing_data = File.read!("index.json") |> Jason.decode!() +    pack_file = "#{name}.json" + +    if File.exists?(pack_file) do +      existing_data = File.read!(pack_file) |> Jason.decode!()        File.write!( -        "index.json", +        pack_file,          Jason.encode!(            Map.merge(              existing_data, @@ -211,11 +229,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do          )        ) -      IO.puts("index.json file has been update with the #{name} pack") +      IO.puts("#{pack_file} has been updated with the #{name} pack")      else -      File.write!("index.json", Jason.encode!(pack_json, pretty: true)) +      File.write!(pack_file, Jason.encode!(pack_json, pretty: true)) -      IO.puts("index.json has been created with the #{name} pack") +      IO.puts("#{pack_file} has been created with the #{name} pack")      end    end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 936bc9ab1..3871e1cbb 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -54,10 +54,19 @@ defmodule Pleroma.Config.TransferTask do            [:pleroma, nil, :prometheus]          end +      {logger, other} = +        (Repo.all(ConfigDB) ++ deleted_settings) +        |> Enum.map(&transform_and_merge/1) +        |> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end) + +      logger +      |> Enum.sort() +      |> Enum.each(&configure/1) +        started_applications = Application.started_applications() -      (Repo.all(ConfigDB) ++ deleted_settings) -      |> Enum.map(&merge_and_update/1) +      other +      |> Enum.map(&update/1)        |> Enum.uniq()        |> Enum.reject(&(&1 in reject_restart))        |> maybe_set_pleroma_last() @@ -81,51 +90,66 @@ defmodule Pleroma.Config.TransferTask do      end    end -  defp group_for_restart(:logger, key, _, merged_value) do -    # change logger configuration in runtime, without restart -    if Keyword.keyword?(merged_value) and -         key not in [:compile_time_application, :backends, :compile_time_purge_matching] do -      Logger.configure_backend(key, merged_value) -    else -      Logger.configure([{key, merged_value}]) -    end +  defp transform_and_merge(%{group: group, key: key, value: value} = setting) do +    group = ConfigDB.from_string(group) +    key = ConfigDB.from_string(key) +    value = ConfigDB.from_binary(value) -    nil -  end +    default = Config.Holder.default_config(group, key) -  defp group_for_restart(group, _, _, _) when group != :pleroma, do: group +    merged = +      cond do +        Ecto.get_meta(setting, :state) == :deleted -> default +        can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value) +        true -> value +      end -  defp group_for_restart(group, key, value, _) do -    if pleroma_need_restart?(group, key, value), do: group +    {group, key, value, merged}    end -  defp merge_and_update(setting) do -    try do -      key = ConfigDB.from_string(setting.key) -      group = ConfigDB.from_string(setting.group) +  # change logger configuration in runtime, without restart +  defp configure({:quack, key, _, merged}) do +    Logger.configure_backend(Quack.Logger, [{key, merged}]) +    :ok = update_env(:quack, key, merged) +  end -      default = Config.Holder.default_config(group, key) -      value = ConfigDB.from_binary(setting.value) +  defp configure({_, :backends, _, merged}) do +    # removing current backends +    Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1) -      merged_value = -        cond do -          Ecto.get_meta(setting, :state) == :deleted -> default -          can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value) -          true -> value -        end +    Enum.each(merged, &Logger.add_backend/1) -      :ok = update_env(group, key, merged_value) +    :ok = update_env(:logger, :backends, merged) +  end -      group_for_restart(group, key, value, merged_value) +  defp configure({group, key, _, merged}) do +    merged = +      if key == :console do +        put_in(merged[:format], merged[:format] <> "\n") +      else +        merged +      end + +    backend = +      if key == :ex_syslogger, +        do: {ExSyslogger, :ex_syslogger}, +        else: key + +    Logger.configure_backend(backend, merged) +    :ok = update_env(:logger, group, merged) +  end + +  defp update({group, key, value, merged}) do +    try do +      :ok = update_env(group, key, merged) + +      if group != :pleroma or pleroma_need_restart?(group, key, value), do: group      rescue        error ->          error_msg = -          "updating env causes error, group: " <> -            inspect(setting.group) <> -            " key: " <> -            inspect(setting.key) <> -            " value: " <> -            inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error) +          "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{ +            inspect(value) +          } error: #{inspect(error)}"          Logger.warn(error_msg) @@ -133,6 +157,9 @@ defmodule Pleroma.Config.TransferTask do      end    end +  defp update_env(group, key, nil), do: Application.delete_env(group, key) +  defp update_env(group, key, value), do: Application.put_env(group, key, value) +    @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()    def pleroma_need_restart?(group, key, value) do      group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value) @@ -150,9 +177,6 @@ defmodule Pleroma.Config.TransferTask do        end)    end -  defp update_env(group, key, nil), do: Application.delete_env(group, key) -  defp update_env(group, key, value), do: Application.put_env(group, key, value) -    defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)    defp restart(started_applications, app, _) do diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex index d9b601223..6fc47620c 100644 --- a/lib/pleroma/ecto_enums.ex +++ b/lib/pleroma/ecto_enums.ex @@ -4,10 +4,16 @@  import EctoEnum -defenum(UserRelationshipTypeEnum, +defenum(Pleroma.UserRelationship.Type,    block: 1,    mute: 2,    reblog_mute: 3,    notification_mute: 4,    inverse_subscription: 5  ) + +defenum(Pleroma.FollowingRelationship.State, +  follow_pending: 1, +  follow_accept: 2, +  follow_reject: 3 +) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a9538ea4e..9ccf40495 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do    import Ecto.Changeset    import Ecto.Query +  alias Ecto.Changeset    alias FlakeId.Ecto.CompatType    alias Pleroma.Repo    alias Pleroma.User    schema "following_relationships" do -    field(:state, :string, default: "accept") +    field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)      belongs_to(:follower, User, type: CompatType)      belongs_to(:following, User, type: CompatType) @@ -27,6 +28,18 @@ defmodule Pleroma.FollowingRelationship do      |> put_assoc(:follower, attrs.follower)      |> put_assoc(:following, attrs.following)      |> validate_required([:state, :follower, :following]) +    |> unique_constraint(:follower_id, +      name: :following_relationships_follower_id_following_id_index +    ) +    |> validate_not_self_relationship() +  end + +  def state_to_enum(state) when state in ["pending", "accept", "reject"] do +    String.to_existing_atom("follow_#{state}") +  end + +  def state_to_enum(state) do +    raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"    end    def get(%User{} = follower, %User{} = following) do @@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do      |> Repo.one()    end -  def update(follower, following, "reject"), do: unfollow(follower, following) +  def update(follower, following, :follow_reject), do: unfollow(follower, following)    def update(%User{} = follower, %User{} = following, state) do      case get(follower, following) do @@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do      end    end -  def follow(%User{} = follower, %User{} = following, state \\ "accept") do +  def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do      %__MODULE__{}      |> changeset(%{follower: follower, following: following, state: state})      |> Repo.insert(on_conflict: :nothing) @@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do    def get_follow_requests(%User{id: id}) do      __MODULE__      |> join(:inner, [r], f in assoc(r, :follower)) -    |> where([r], r.state == "pending") +    |> where([r], r.state == ^:follow_pending)      |> where([r], r.following_id == ^id)      |> select([r, f], f)      |> Repo.all() @@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do    def following?(%User{id: follower_id}, %User{id: followed_id}) do      __MODULE__ -    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept") +    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)      |> Repo.exists?()    end @@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do        __MODULE__        |> join(:inner, [r], u in User, on: r.following_id == u.id)        |> where([r], r.follower_id == ^user.id) -      |> where([r], r.state == "accept") +      |> where([r], r.state == ^:follow_accept)        |> select([r, u], u.follower_address)        |> Repo.all() @@ -157,4 +170,30 @@ defmodule Pleroma.FollowingRelationship do        fr -> fr.follower_id == follower.id and fr.following_id == following.id      end)    end + +  defp validate_not_self_relationship(%Changeset{} = changeset) do +    changeset +    |> validate_follower_id_following_id_inequality() +    |> validate_following_id_follower_id_inequality() +  end + +  defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do +    validate_change(changeset, :follower_id, fn _, follower_id -> +      if follower_id == get_field(changeset, :following_id) do +        [source_id: "can't be equal to following_id"] +      else +        [] +      end +    end) +  end + +  defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do +    validate_change(changeset, :following_id, fn _, following_id -> +      if following_id == get_field(changeset, :follower_id) do +        [target_id: "can't be equal to follower_id"] +      else +        [] +      end +    end) +  end  end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index e2a658cb3..c44e7fc8b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -35,9 +35,19 @@ defmodule Pleroma.Formatter do          nickname_text = get_nickname_text(nickname, opts)          link = -          ~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{ -            nickname_text -          }</span></a></span>) +          Phoenix.HTML.Tag.content_tag( +            :span, +            Phoenix.HTML.Tag.content_tag( +              :a, +              ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)], +              "data-user": id, +              class: "u-url mention", +              href: ap_id, +              rel: "ugc" +            ), +            class: "h-card" +          ) +          |> Phoenix.HTML.safe_to_string()          {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}} @@ -49,7 +59,15 @@ defmodule Pleroma.Formatter do    def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do      tag = String.downcase(tag)      url = "#{Pleroma.Web.base_url()}/tag/#{tag}" -    link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>) + +    link = +      Phoenix.HTML.Tag.content_tag(:a, tag_text, +        class: "hashtag", +        "data-tag": tag, +        href: url, +        rel: "tag ugc" +      ) +      |> Phoenix.HTML.safe_to_string()      {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}    end diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index 20823a765..cd25a2e74 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -49,8 +49,10 @@ defmodule Pleroma.Gun.Conn do      key = "#{uri.scheme}:#{uri.host}:#{uri.port}" +    max_connections = pool_opts[:max_connections] || 250 +      conn_pid = -      if Connections.count(name) < opts[:max_connection] do +      if Connections.count(name) < max_connections do          do_open(uri, opts)        else          close_least_used_and_do_open(name, uri, opts) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 9ae6a5600..99608b8a5 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -32,6 +32,18 @@ defmodule Pleroma.Object.Containment do      get_actor(%{"actor" => actor})    end +  def get_object(%{"object" => id}) when is_binary(id) do +    id +  end + +  def get_object(%{"object" => %{"id" => id}}) when is_binary(id) do +    id +  end + +  def get_object(_) do +    nil +  end +    # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus    # objects being present in the test suite environment.  Once these objects are    # removed, please also remove this. diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex index 4f124ed4d..84b7c5d83 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex @@ -42,13 +42,13 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do      else        {:user_match, false} ->          Logger.debug("Failed to map identity from signature (payload actor mismatch)") -        Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") +        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")          assign(conn, :valid_signature, false)        # remove me once testsuite uses mapped capabilities instead of what we do now        {:user, nil} ->          Logger.debug("Failed to map identity from signature (lookup failure)") -        Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") +        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")          conn      end    end @@ -60,7 +60,7 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do      else        _ ->          Logger.debug("Failed to map identity from signature (no payload actor mismatch)") -        Logger.debug("key_id=#{key_id_from_conn(conn)}") +        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")          assign(conn, :valid_signature, false)      end    end diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index 1529da717..c51e2c634 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -110,20 +110,9 @@ defmodule Pleroma.Plugs.RateLimiter do    end    def disabled?(conn) do -    localhost_or_socket = -      case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do -        {127, 0, 0, 1} -> true -        {0, 0, 0, 0, 0, 0, 0, 1} -> true -        {:local, _} -> true -        _ -> false -      end - -    remote_ip_not_found = -      if Map.has_key?(conn.assigns, :remote_ip_found), -        do: !conn.assigns.remote_ip_found, -        else: false - -    localhost_or_socket and remote_ip_not_found +    if Map.has_key?(conn.assigns, :remote_ip_found), +      do: !conn.assigns.remote_ip_found, +      else: false    end    @inspect_bucket_not_found {:error, :not_found} diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 0ac9050d0..2eca4f8f6 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -7,8 +7,6 @@ defmodule Pleroma.Plugs.RemoteIp do    This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.    """ -  import Plug.Conn -    @behaviour Plug    @headers ~w[ @@ -28,12 +26,11 @@ defmodule Pleroma.Plugs.RemoteIp do    def init(_), do: nil -  def call(%{remote_ip: original_remote_ip} = conn, _) do +  def call(conn, _) do      config = Pleroma.Config.get(__MODULE__, [])      if Keyword.get(config, :enabled, false) do -      %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) -      assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) +      RemoteIp.call(conn, remote_ip_opts(config))      else        conn      end diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex index 36ff024a7..94147e0c4 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/plugs/uploaded_media.ex @@ -41,6 +41,7 @@ defmodule Pleroma.Plugs.UploadedMedia do          conn ->            conn        end +      |> merge_resp_headers([{"content-security-policy", "sandbox"}])      config = Pleroma.Config.get(Pleroma.Upload) diff --git a/lib/pleroma/pool/connections.ex b/lib/pleroma/pool/connections.ex index 4d4ba913c..acafe1bea 100644 --- a/lib/pleroma/pool/connections.ex +++ b/lib/pleroma/pool/connections.ex @@ -243,7 +243,7 @@ defmodule Pleroma.Pool.Connections do    @impl true    def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do -    Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}") +    Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")      state =        with {key, conn} <- find_conn(state.conns, conn_pid) do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ff828aa17..670ce397b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -16,6 +16,7 @@ defmodule Pleroma.User do    alias Pleroma.Conversation.Participation    alias Pleroma.Delivery    alias Pleroma.FollowingRelationship +  alias Pleroma.Formatter    alias Pleroma.HTML    alias Pleroma.Keys    alias Pleroma.Notification @@ -452,7 +453,7 @@ defmodule Pleroma.User do        fields =          raw_fields -        |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) +        |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)        changeset        |> put_change(:raw_fields, raw_fields) @@ -462,6 +463,12 @@ defmodule Pleroma.User do      end    end +  defp parse_fields(value) do +    value +    |> Formatter.linkify(mentions_format: :full) +    |> elem(0) +  end +    defp put_change_if_present(changeset, map_field, value_function) do      if value = get_change(changeset, map_field) do        with {:ok, new_value} <- value_function.(value) do @@ -693,7 +700,7 @@ defmodule Pleroma.User do    @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}    def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do -    follow(follower, followed, "pending") +    follow(follower, followed, :follow_pending)    end    def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do @@ -713,14 +720,14 @@ defmodule Pleroma.User do    def follow_all(follower, followeds) do      followeds      |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) -    |> Enum.each(&follow(follower, &1, "accept")) +    |> Enum.each(&follow(follower, &1, :follow_accept))      set_cache(follower)    end    defdelegate following(user), to: FollowingRelationship -  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do +  def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do      deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])      cond do @@ -747,7 +754,7 @@ defmodule Pleroma.User do    def unfollow(%User{} = follower, %User{} = followed) do      case get_follow_state(follower, followed) do -      state when state in ["accept", "pending"] -> +      state when state in [:follow_pending, :follow_accept] ->          FollowingRelationship.unfollow(follower, followed)          {:ok, followed} = update_follower_count(followed) @@ -765,6 +772,7 @@ defmodule Pleroma.User do    defdelegate following?(follower, followed), to: FollowingRelationship +  @doc "Returns follow state as Pleroma.FollowingRelationship.State value"    def get_follow_state(%User{} = follower, %User{} = following) do      following_relationship = FollowingRelationship.get(follower, following)      get_follow_state(follower, following, following_relationship) @@ -778,8 +786,11 @@ defmodule Pleroma.User do      case {following_relationship, following.local} do        {nil, false} ->          case Utils.fetch_latest_follow(follower, following) do -          %{data: %{"state" => state}} when state in ["pending", "accept"] -> state -          _ -> nil +          %Activity{data: %{"state" => state}} when state in ["pending", "accept"] -> +            FollowingRelationship.state_to_enum(state) + +          _ -> +            nil          end        {%{state: state}, _} -> @@ -1278,7 +1289,7 @@ defmodule Pleroma.User do    def blocks?(%User{} = user, %User{} = target) do      blocks_user?(user, target) || -      (!User.following?(user, target) && blocks_domain?(user, target)) +      (blocks_domain?(user, target) and not User.following?(user, target))    end    def blocks_user?(%User{} = user, %User{} = target) do @@ -1979,17 +1990,6 @@ defmodule Pleroma.User do    def fields(%{fields: fields}), do: fields -  def sanitized_fields(%User{} = user) do -    user -    |> User.fields() -    |> Enum.map(fn %{"name" => name, "value" => value} -> -      %{ -        "name" => name, -        "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) -      } -    end) -  end -    def validate_fields(changeset, remote? \\ false) do      limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields      limit = Pleroma.Config.get([:instance, limit_name], 0) diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 884e33039..ec88088cf 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do        as: :relationships,        on: r.following_id == ^id and r.follower_id == u.id      ) -    |> where([relationships: r], r.state == "accept") +    |> where([relationships: r], r.state == ^:follow_accept)    end    defp compose_query({:friends, %User{id: id}}, query) do @@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do        as: :relationships,        on: r.following_id == u.id and r.follower_id == ^id      ) -    |> where([relationships: r], r.state == "accept") +    |> where([relationships: r], r.state == ^:follow_accept)    end    defp compose_query({:recipients_from_activity, to}, query) do @@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do      )      |> where(        [u, following: f, relationships: r], -      u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept") +      u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)      )      |> distinct(true)    end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 18a5eec72..235ad427c 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do    import Ecto.Changeset    import Ecto.Query +  alias Ecto.Changeset    alias Pleroma.FollowingRelationship    alias Pleroma.Repo    alias Pleroma.User @@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do    schema "user_relationships" do      belongs_to(:source, User, type: FlakeId.Ecto.CompatType)      belongs_to(:target, User, type: FlakeId.Ecto.CompatType) -    field(:relationship_type, UserRelationshipTypeEnum) +    field(:relationship_type, Pleroma.UserRelationship.Type)      timestamps(updated_at: false)    end -  for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do +  for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do      # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,      #   `def create_notification_mute/2`, `def create_inverse_subscription/2`      def unquote(:"create_#{relationship_type}")(source, target), @@ -40,7 +41,7 @@ defmodule Pleroma.UserRelationship do    def user_relationship_types, do: Keyword.keys(user_relationship_mappings()) -  def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__() +  def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()    def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do      user_relationship @@ -129,17 +130,27 @@ defmodule Pleroma.UserRelationship do    end    @doc ":relationships option for StatusView / AccountView / NotificationView" -  def view_relationships_option(nil = _reading_user, _actors) do +  def view_relationships_option(reading_user, actors, opts \\ []) + +  def view_relationships_option(nil = _reading_user, _actors, _opts) do      %{user_relationships: [], following_relationships: []}    end -  def view_relationships_option(%User{} = reading_user, actors) do +  def view_relationships_option(%User{} = reading_user, actors, opts) do +    {source_to_target_rel_types, target_to_source_rel_types} = +      if opts[:source_mutes_only] do +        # This option is used for rendering statuses (FE needs `muted` flag for each one anyways) +        {[:mute], []} +      else +        {[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]} +      end +      user_relationships =        UserRelationship.dictionary(          [reading_user],          actors, -        [:block, :mute, :notification_mute, :reblog_mute], -        [:block, :inverse_subscription] +        source_to_target_rel_types, +        target_to_source_rel_types        )      following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors) @@ -147,18 +158,26 @@ defmodule Pleroma.UserRelationship do      %{user_relationships: user_relationships, following_relationships: following_relationships}    end -  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do +  defp validate_not_self_relationship(%Changeset{} = changeset) do      changeset -    |> validate_change(:target_id, fn _, target_id -> -      if target_id == get_field(changeset, :source_id) do -        [target_id: "can't be equal to source_id"] +    |> validate_source_id_target_id_inequality() +    |> validate_target_id_source_id_inequality() +  end + +  defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do +    validate_change(changeset, :source_id, fn _, source_id -> +      if source_id == get_field(changeset, :target_id) do +        [source_id: "can't be equal to target_id"]        else          []        end      end) -    |> validate_change(:source_id, fn _, source_id -> -      if source_id == get_field(changeset, :target_id) do -        [source_id: "can't be equal to target_id"] +  end + +  defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do +    validate_change(changeset, :target_id, fn _, target_id -> +      if target_id == get_field(changeset, :source_id) do +        [target_id: "can't be equal to source_id"]        else          []        end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 53b6ad654..86b105b7f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -125,6 +125,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def increase_poll_votes_if_vote(_create_data), do: :noop +  @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} +  def persist(object, meta) do +    with local <- Keyword.fetch!(meta, :local), +         {recipients, _, _} <- get_recipients(object), +         {:ok, activity} <- +           Repo.insert(%Activity{ +             data: object, +             local: local, +             recipients: recipients, +             actor: object["actor"] +           }) do +      {:ok, activity, meta} +    end +  end +    @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}    def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do      with nil <- Activity.normalize(map), @@ -706,7 +721,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end -  defp fetch_activities_for_context_query(context, opts) do +  def fetch_activities_for_context_query(context, opts) do      public = [Constants.as_public()]      recipients = diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex new file mode 100644 index 000000000..429a510b8 --- /dev/null +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -0,0 +1,43 @@ +defmodule Pleroma.Web.ActivityPub.Builder do +  @moduledoc """ +  This module builds the objects. Meant to be used for creating local objects. + +  This module encodes our addressing policies and general shape of our objects. +  """ + +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.ActivityPub.Visibility + +  @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()} +  def like(actor, object) do +    object_actor = User.get_cached_by_ap_id(object.data["actor"]) + +    # Address the actor of the object, and our actor's follower collection if the post is public. +    to = +      if Visibility.is_public?(object) do +        [actor.follower_address, object.data["actor"]] +      else +        [object.data["actor"]] +      end + +    # CC everyone who's been addressed in the object, except ourself and the object actor's +    # follower collection +    cc = +      (object.data["to"] ++ (object.data["cc"] || [])) +      |> List.delete(actor.ap_id) +      |> List.delete(object_actor.follower_address) + +    {:ok, +     %{ +       "id" => Utils.generate_activity_id(), +       "actor" => actor.ap_id, +       "type" => "Like", +       "object" => object.data["id"], +       "to" => to, +       "cc" => cc, +       "context" => object.data["context"] +     }, []} +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex new file mode 100644 index 000000000..dc4bce059 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidator do +  @moduledoc """ +  This module is responsible for validating an object (which can be an activity) +  and checking if it is both well formed and also compatible with our view of +  the system. +  """ + +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + +  @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} +  def validate(object, meta) + +  def validate(%{"type" => "Like"} = object, meta) do +    with {:ok, object} <- +           object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do +      object = stringify_keys(object |> Map.from_struct()) +      {:ok, object, meta} +    end +  end + +  def stringify_keys(object) do +    object +    |> Map.new(fn {key, val} -> {to_string(key), val} end) +  end + +  def fetch_actor_and_object(object) do +    User.get_or_fetch_by_ap_id(object["actor"]) +    Object.normalize(object["object"]) +    :ok +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex new file mode 100644 index 000000000..b479c3918 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do +  import Ecto.Changeset + +  alias Pleroma.Object +  alias Pleroma.User + +  def validate_actor_presence(cng, field_name \\ :actor) do +    cng +    |> validate_change(field_name, fn field_name, actor -> +      if User.get_cached_by_ap_id(actor) do +        [] +      else +        [{field_name, "can't find user"}] +      end +    end) +  end + +  def validate_object_presence(cng, field_name \\ :object) do +    cng +    |> validate_change(field_name, fn field_name, object -> +      if Object.get_cached_by_ap_id(object) do +        [] +      else +        [{field_name, "can't find object"}] +      end +    end) +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex new file mode 100644 index 000000000..926804ce7 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do +  use Ecto.Schema + +  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types + +  import Ecto.Changeset + +  @primary_key false + +  embedded_schema do +    field(:id, Types.ObjectID, primary_key: true) +    field(:actor, Types.ObjectID) +    field(:type, :string) +    field(:to, {:array, :string}) +    field(:cc, {:array, :string}) +    field(:bto, {:array, :string}, default: []) +    field(:bcc, {:array, :string}, default: []) + +    embeds_one(:object, NoteValidator) +  end + +  def cast_data(data) do +    cast(%__MODULE__{}, data, __schema__(:fields)) +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex new file mode 100644 index 000000000..49546ceaa --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -0,0 +1,57 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do +  use Ecto.Schema + +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types +  alias Pleroma.Web.ActivityPub.Utils + +  import Ecto.Changeset +  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + +  @primary_key false + +  embedded_schema do +    field(:id, Types.ObjectID, primary_key: true) +    field(:type, :string) +    field(:object, Types.ObjectID) +    field(:actor, Types.ObjectID) +    field(:context, :string) +    field(:to, {:array, :string}) +    field(:cc, {:array, :string}) +  end + +  def cast_and_validate(data) do +    data +    |> cast_data() +    |> validate_data() +  end + +  def cast_data(data) do +    %__MODULE__{} +    |> cast(data, [:id, :type, :object, :actor, :context, :to, :cc]) +  end + +  def validate_data(data_cng) do +    data_cng +    |> validate_inclusion(:type, ["Like"]) +    |> validate_required([:id, :type, :object, :actor, :context, :to, :cc]) +    |> validate_actor_presence() +    |> validate_object_presence() +    |> validate_existing_like() +  end + +  def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do +    if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do +      cng +      |> add_error(:actor, "already liked this object") +      |> add_error(:object, "already liked by this actor") +    else +      cng +    end +  end + +  def validate_existing_like(cng), do: cng +end diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex new file mode 100644 index 000000000..c95b622e4 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do +  use Ecto.Schema + +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types + +  import Ecto.Changeset + +  @primary_key false + +  embedded_schema do +    field(:id, Types.ObjectID, primary_key: true) +    field(:to, {:array, :string}, default: []) +    field(:cc, {:array, :string}, default: []) +    field(:bto, {:array, :string}, default: []) +    field(:bcc, {:array, :string}, default: []) +    # TODO: Write type +    field(:tag, {:array, :map}, default: []) +    field(:type, :string) +    field(:content, :string) +    field(:context, :string) +    field(:actor, Types.ObjectID) +    field(:attributedTo, Types.ObjectID) +    field(:summary, :string) +    field(:published, Types.DateTime) +    # TODO: Write type +    field(:emoji, :map, default: %{}) +    field(:sensitive, :boolean, default: false) +    # TODO: Write type +    field(:attachment, {:array, :map}, default: []) +    field(:replies_count, :integer, default: 0) +    field(:like_count, :integer, default: 0) +    field(:announcement_count, :integer, default: 0) +    field(:inRepyTo, :string) + +    field(:likes, {:array, :string}, default: []) +    field(:announcements, {:array, :string}, default: []) + +    # see if needed +    field(:conversation, :string) +    field(:context_id, :string) +  end + +  def cast_and_validate(data) do +    data +    |> cast_data() +    |> validate_data() +  end + +  def cast_data(data) do +    %__MODULE__{} +    |> cast(data, __schema__(:fields)) +  end + +  def validate_data(data_cng) do +    data_cng +    |> validate_inclusion(:type, ["Note"]) +    |> validate_required([:id, :actor, :to, :cc, :type, :content, :context]) +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex new file mode 100644 index 000000000..4f412fcde --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex @@ -0,0 +1,34 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do +  @moduledoc """ +  The AP standard defines the date fields in AP as xsd:DateTime. Elixir's +  DateTime can't parse this, but it can parse the related iso8601. This +  module punches the date until it looks like iso8601 and normalizes to +  it. + +  DateTimes without a timezone offset are treated as UTC. + +  Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published +  """ +  use Ecto.Type + +  def type, do: :string + +  def cast(datetime) when is_binary(datetime) do +    with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do +      {:ok, DateTime.to_iso8601(datetime)} +    else +      {:error, :missing_offset} -> cast("#{datetime}Z") +      _e -> :error +    end +  end + +  def cast(_), do: :error + +  def dump(data) do +    {:ok, data} +  end + +  def load(data) do +    {:ok, data} +  end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex new file mode 100644 index 000000000..f6e749b33 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex @@ -0,0 +1,29 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do +  use Ecto.Type + +  def type, do: :string + +  def cast(object) when is_binary(object) do +    # Host has to be present and scheme has to be an http scheme (for now) +    case URI.parse(object) do +      %URI{host: nil} -> :error +      %URI{host: ""} -> :error +      %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object} +      _ -> :error +    end +  end + +  def cast(%{"id" => object}), do: cast(object) + +  def cast(_) do +    :error +  end + +  def dump(data) do +    {:ok, data} +  end + +  def load(data) do +    {:ok, data} +  end +end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex new file mode 100644 index 000000000..7ccee54c9 --- /dev/null +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Pipeline do +  alias Pleroma.Activity +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.MRF +  alias Pleroma.Web.ActivityPub.ObjectValidator +  alias Pleroma.Web.ActivityPub.SideEffects +  alias Pleroma.Web.Federator + +  @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()} +  def common_pipeline(object, meta) do +    with {_, {:ok, validated_object, meta}} <- +           {:validate_object, ObjectValidator.validate(object, meta)}, +         {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, +         {_, {:ok, %Activity{} = activity, meta}} <- +           {:persist_object, ActivityPub.persist(mrfd_object, meta)}, +         {_, {:ok, %Activity{} = activity, meta}} <- +           {:execute_side_effects, SideEffects.handle(activity, meta)}, +         {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do +      {:ok, activity, meta} +    else +      {:mrf_object, {:reject, _}} -> {:ok, nil, meta} +      e -> {:error, e} +    end +  end + +  defp maybe_federate(activity, meta) do +    with {:ok, local} <- Keyword.fetch(meta, :local) do +      if local do +        Federator.publish(activity) +        {:ok, :federated} +      else +        {:ok, :not_federated} +      end +    else +      _e -> {:error, :badarg} +    end +  end +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex new file mode 100644 index 000000000..666a4e310 --- /dev/null +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -0,0 +1,28 @@ +defmodule Pleroma.Web.ActivityPub.SideEffects do +  @moduledoc """ +  This module looks at an inserted object and executes the side effects that it +  implies. For example, a `Like` activity will increase the like count on the +  liked object, a `Follow` activity will add the user to the follower +  collection, and so on. +  """ +  alias Pleroma.Notification +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.Utils + +  def handle(object, meta \\ []) + +  # Tasks this handles: +  # - Add like to object +  # - Set up notification +  def handle(%{data: %{"type" => "Like"}} = object, meta) do +    liked_object = Object.get_by_ap_id(object.data["object"]) +    Utils.add_like_to_object(object, liked_object) +    Notification.create_notifications(object) +    {:ok, object, meta} +  end + +  # Nothing to do +  def handle(object, meta) do +    {:ok, object, meta} +  end +end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 09bd9a442..39feae285 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -13,6 +13,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.ObjectValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator +  alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.Federator @@ -202,16 +205,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> Map.put("conversation", context)    end +  defp add_if_present(map, _key, nil), do: map + +  defp add_if_present(map, key, value) do +    Map.put(map, key, value) +  end +    def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do      attachments =        Enum.map(attachment, fn data -> -        media_type = data["mediaType"] || data["mimeType"] -        href = data["url"] || data["href"] -        url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}] +        url = +          cond do +            is_list(data["url"]) -> List.first(data["url"]) +            is_map(data["url"]) -> data["url"] +            true -> nil +          end -        data -        |> Map.put("mediaType", media_type) -        |> Map.put("url", url) +        media_type = +          cond do +            is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"] +            is_binary(data["mediaType"]) -> data["mediaType"] +            is_binary(data["mimeType"]) -> data["mimeType"] +            true -> nil +          end + +        href = +          cond do +            is_map(url) && is_binary(url["href"]) -> url["href"] +            is_binary(data["url"]) -> data["url"] +            is_binary(data["href"]) -> data["href"] +          end + +        attachment_url = +          %{"href" => href} +          |> add_if_present("mediaType", media_type) +          |> add_if_present("type", Map.get(url || %{}, "type")) + +        %{"url" => [attachment_url]} +        |> add_if_present("mediaType", media_type) +        |> add_if_present("type", data["type"]) +        |> add_if_present("name", data["name"])        end)      Map.put(object, "attachment", attachments) @@ -491,7 +524,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do             {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},             {_, {:ok, _}} <-               {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")}, -           {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do +           {:ok, _relationship} <- +             FollowingRelationship.update(follower, followed, :follow_accept) do          ActivityPub.accept(%{            to: [follower.ap_id],            actor: followed, @@ -501,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        else          {:user_blocked, true} ->            {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") +          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)            ActivityPub.reject(%{              to: [follower.ap_id], @@ -512,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          {:follow, {:error, _}} ->            {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") +          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)            ActivityPub.reject(%{              to: [follower.ap_id], @@ -522,7 +556,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            })          {:user_locked, true} -> -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending") +          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)            :noop        end @@ -542,7 +576,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do           {:ok, follow_activity} <- get_follow_activity(follow_object, followed),           {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),           %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), -         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do +         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do        ActivityPub.accept(%{          to: follow_activity.data["to"],          type: "Accept", @@ -565,7 +599,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do           {:ok, follow_activity} <- get_follow_activity(follow_object, followed),           {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),           %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), -         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), +         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),           {:ok, activity} <-             ActivityPub.reject(%{               to: follow_activity.data["to"], @@ -609,17 +643,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> handle_incoming(options)    end -  def handle_incoming( -        %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, -        _options -      ) do -    with actor <- Containment.get_actor(data), -         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id), -         {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do +  def handle_incoming(%{"type" => "Like"} = data, _options) do +    with {_, {:ok, cast_data_sym}} <- +           {:casting_data, +            data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)}, +         cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)), +         :ok <- ObjectValidator.fetch_actor_and_object(cast_data), +         {_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)}, +         {_, {:ok, cast_data}} <- +           {:ensure_recipients_presence, ensure_recipients_presence(cast_data)}, +         {_, {:ok, activity, _meta}} <- +           {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do        {:ok, activity}      else -      _e -> :error +      e -> {:error, e}      end    end @@ -1243,4 +1280,45 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def maybe_fix_user_url(data), do: data    def maybe_fix_user_object(data), do: maybe_fix_user_url(data) + +  defp ensure_context_presence(%{"context" => context} = data) when is_binary(context), +    do: {:ok, data} + +  defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do +    with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do +      {:ok, Map.put(data, "context", context)} +    else +      _ -> +        {:error, :no_context} +    end +  end + +  defp ensure_context_presence(_) do +    {:error, :no_context} +  end + +  defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data), +    do: {:ok, data} + +  defp ensure_recipients_presence(%{"object" => object} = data) do +    case Object.normalize(object) do +      %{data: %{"actor" => actor}} -> +        data = +          data +          |> Map.put("to", [actor]) +          |> Map.put("cc", data["cc"] || []) + +        {:ok, data} + +      nil -> +        {:error, :no_object} + +      _ -> +        {:error, :no_actor} +    end +  end + +  defp ensure_recipients_presence(_) do +    {:error, :no_object} +  end  end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index ca5439920..831c3bd02 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -258,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      conn      |> put_view(Pleroma.Web.AdminAPI.StatusView) -    |> render("index.json", %{activities: activities, as: :activity}) +    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})    end    def list_user_statuses(conn, %{"nickname" => nickname} = params) do @@ -277,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        conn        |> put_view(StatusView) -      |> render("index.json", %{activities: activities, as: :activity}) +      |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})      else        _ -> {:error, :not_found}      end @@ -576,9 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    @doc "Sends registration invite via email"    def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do -    with true <- -           Config.get([:instance, :invites_enabled]) && -             !Config.get([:instance, :registrations_open]), +    with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, +         {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},           {:ok, invite_token} <- UserInviteToken.create_invite(),           email <-             Pleroma.Emails.UserEmail.user_invitation_email( @@ -589,6 +588,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do             ),           {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do        json_response(conn, :no_content, "") +    else +      {:registrations_open, _} -> +        errors( +          conn, +          {:error, "To send invites you need to set the `registrations_open` option to false."} +        ) + +      {:invites_enabled, _} -> +        errors( +          conn, +          {:error, "To send invites you need to set the `invites_enabled` option to true."} +        )      end    end @@ -801,7 +812,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      conn      |> put_view(Pleroma.Web.AdminAPI.StatusView) -    |> render("index.json", %{activities: activities, as: :activity}) +    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})    end    def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca0bcebc7..d50969b2a 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do        actor: merge_account_views(user),        content: content,        created_at: created_at, -      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), +      statuses: +        StatusView.render("index.json", %{ +          activities: statuses, +          as: :activity, +          skip_relationships: false +        }),        state: report.data["state"],        notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})      } diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex new file mode 100644 index 000000000..3890489e3 --- /dev/null +++ b/lib/pleroma/web/api_spec.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec do +  alias OpenApiSpex.OpenApi +  alias Pleroma.Web.Endpoint +  alias Pleroma.Web.Router + +  @behaviour OpenApi + +  @impl OpenApi +  def spec do +    %OpenApi{ +      servers: [ +        # Populate the Server info from a phoenix endpoint +        OpenApiSpex.Server.from_endpoint(Endpoint) +      ], +      info: %OpenApiSpex.Info{ +        title: "Pleroma", +        description: Application.spec(:pleroma, :description) |> to_string(), +        version: Application.spec(:pleroma, :vsn) |> to_string() +      }, +      # populate the paths from a phoenix router +      paths: OpenApiSpex.Paths.from_router(Router), +      components: %OpenApiSpex.Components{ +        securitySchemes: %{ +          "oAuth" => %OpenApiSpex.SecurityScheme{ +            type: "oauth2", +            flows: %OpenApiSpex.OAuthFlows{ +              password: %OpenApiSpex.OAuthFlow{ +                authorizationUrl: "/oauth/authorize", +                tokenUrl: "/oauth/token", +                scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} +              } +            } +          } +        } +      } +    } +    # discover request/response schemas from path specs +    |> OpenApiSpex.resolve_schema_modules() +  end +end diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex new file mode 100644 index 000000000..7348dcbee --- /dev/null +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Helpers do +  def request_body(description, schema_ref, opts \\ []) do +    media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"] + +    content = +      media_types +      |> Enum.map(fn type -> +        {type, +         %OpenApiSpex.MediaType{ +           schema: schema_ref, +           example: opts[:example], +           examples: opts[:examples] +         }} +      end) +      |> Enum.into(%{}) + +    %OpenApiSpex.RequestBody{ +      description: description, +      content: content, +      required: opts[:required] || false +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex new file mode 100644 index 000000000..26d8dbd42 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/app_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.AppOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Helpers +  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest +  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse + +  @spec open_api_operation(atom) :: Operation.t() +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  @spec create_operation() :: Operation.t() +  def create_operation do +    %Operation{ +      tags: ["apps"], +      summary: "Create an application", +      description: "Create a new application to obtain OAuth2 credentials", +      operationId: "AppController.create", +      requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true), +      responses: %{ +        200 => Operation.response("App", "application/json", AppCreateResponse), +        422 => +          Operation.response( +            "Unprocessable Entity", +            "application/json", +            %Schema{ +              type: :object, +              description: +                "If a required parameter is missing or improperly formatted, the request will fail.", +              properties: %{ +                error: %Schema{type: :string} +              }, +              example: %{ +                "error" => "Validation failed: Redirect URI must be an absolute URI." +              } +            } +          ) +      } +    } +  end + +  def verify_credentials_operation do +    %Operation{ +      tags: ["apps"], +      summary: "Verify your app works", +      description: "Confirm that the app's OAuth2 credentials work.", +      operationId: "AppController.verify_credentials", +      security: [ +        %{ +          "oAuth" => ["read"] +        } +      ], +      responses: %{ +        200 => +          Operation.response("App", "application/json", %Schema{ +            type: :object, +            description: +              "If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.", +            properties: %{ +              name: %Schema{type: :string}, +              vapid_key: %Schema{type: :string}, +              website: %Schema{type: :string, nullable: true} +            }, +            example: %{ +              "name" => "My App", +              "vapid_key" => +                "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=", +              "website" => "https://myapp.com/" +            } +          }), +        422 => +          Operation.response( +            "Unauthorized", +            "application/json", +            %Schema{ +              type: :object, +              description: +                "If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.", +              properties: %{ +                error: %Schema{type: :string} +              }, +              example: %{ +                "error" => "The access token is invalid." +              } +            } +          ) +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex new file mode 100644 index 000000000..dd14837c3 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Helpers +  alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest +  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def index_operation do +    %Operation{ +      tags: ["domain_blocks"], +      summary: "Fetch domain blocks", +      description: "View domains the user has blocked.", +      security: [%{"oAuth" => ["follow", "read:blocks"]}], +      operationId: "DomainBlockController.index", +      responses: %{ +        200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse) +      } +    } +  end + +  def create_operation do +    %Operation{ +      tags: ["domain_blocks"], +      summary: "Block a domain", +      description: """ +      Block a domain to: + +      - hide all public posts from it +      - hide all notifications from it +      - remove all followers from it +      - prevent following new users from it (but does not remove existing follows) +      """, +      operationId: "DomainBlockController.create", +      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true), +      security: [%{"oAuth" => ["follow", "write:blocks"]}], +      responses: %{ +        200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) +      } +    } +  end + +  def delete_operation do +    %Operation{ +      tags: ["domain_blocks"], +      summary: "Unblock a domain", +      description: "Remove a domain block, if it exists in the user's array of blocked domains.", +      operationId: "DomainBlockController.delete", +      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true), +      security: [%{"oAuth" => ["follow", "write:blocks"]}], +      responses: %{ +        200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex new file mode 100644 index 000000000..8a83abef3 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/app_create_request.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do +  alias OpenApiSpex.Schema +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "AppCreateRequest", +    description: "POST body for creating an app", +    type: :object, +    properties: %{ +      client_name: %Schema{type: :string, description: "A name for your application."}, +      redirect_uris: %Schema{ +        type: :string, +        description: +          "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." +      }, +      scopes: %Schema{ +        type: :string, +        description: "Space separated list of scopes. If none is provided, defaults to `read`." +      }, +      website: %Schema{type: :string, description: "A URL to the homepage of your app"} +    }, +    required: [:client_name, :redirect_uris], +    example: %{ +      "client_name" => "My App", +      "redirect_uris" => "https://myapp.com/auth/callback", +      "website" => "https://myapp.com/" +    } +  }) +end diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex new file mode 100644 index 000000000..f290fb031 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/app_create_response.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do +  alias OpenApiSpex.Schema + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "AppCreateResponse", +    description: "Response schema for an app", +    type: :object, +    properties: %{ +      id: %Schema{type: :string}, +      name: %Schema{type: :string}, +      client_id: %Schema{type: :string}, +      client_secret: %Schema{type: :string}, +      redirect_uri: %Schema{type: :string}, +      vapid_key: %Schema{type: :string}, +      website: %Schema{type: :string, nullable: true} +    }, +    example: %{ +      "id" => "123", +      "name" => "My App", +      "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM", +      "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw", +      "vapid_key" => +        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=", +      "website" => "https://myapp.com/" +    } +  }) +end diff --git a/lib/pleroma/web/api_spec/schemas/domain_block_request.ex b/lib/pleroma/web/api_spec/schemas/domain_block_request.ex new file mode 100644 index 000000000..ee9238361 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/domain_block_request.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest do +  alias OpenApiSpex.Schema +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "DomainBlockRequest", +    type: :object, +    properties: %{ +      domain: %Schema{type: :string} +    }, +    required: [:domain], +    example: %{ +      "domain" => "facebook.com" +    } +  }) +end diff --git a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex new file mode 100644 index 000000000..d895aca4e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse do +  require OpenApiSpex +  alias OpenApiSpex.Schema + +  OpenApiSpex.schema(%{ +    title: "DomainBlocksResponse", +    description: "Response schema for domain blocks", +    type: :array, +    items: %Schema{type: :string}, +    example: ["google.com", "facebook.com"] +  }) +end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index c4356f93b..c1cd15bb2 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do    end    defp preview?(draft) do -    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false +    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])      %__MODULE__{draft | preview?: preview?}    end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2646b9f7b..c56756a3d 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.CommonAPI do    alias Pleroma.User    alias Pleroma.UserRelationship    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Builder +  alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility @@ -19,6 +21,7 @@ defmodule Pleroma.Web.CommonAPI do    import Pleroma.Web.CommonAPI.Utils    require Pleroma.Constants +  require Logger    def follow(follower, followed) do      timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) @@ -42,7 +45,7 @@ defmodule Pleroma.Web.CommonAPI do      with {:ok, follower} <- User.follow(follower, followed),           %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),           {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), -         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"), +         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),           {:ok, _activity} <-             ActivityPub.accept(%{               to: [follower.ap_id], @@ -57,7 +60,7 @@ defmodule Pleroma.Web.CommonAPI do    def reject_follow_request(follower, followed) do      with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),           {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), -         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), +         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),           {:ok, _activity} <-             ActivityPub.reject(%{               to: [follower.ap_id], @@ -109,18 +112,51 @@ defmodule Pleroma.Web.CommonAPI do      end    end -  def favorite(id_or_ap_id, user) do -    with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)}, -         object <- Object.normalize(activity), -         like_activity <- Utils.get_existing_like(user.ap_id, object) do -      if like_activity do -        {:ok, like_activity, object} -      else -        ActivityPub.like(user, object) -      end +  @spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()} +  def favorite(%User{} = user, id) do +    case favorite_helper(user, id) do +      {:ok, _} = res -> +        res + +      {:error, :not_found} = res -> +        res + +      {:error, e} -> +        Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") +        {:error, dgettext("errors", "Could not favorite")} +    end +  end + +  def favorite_helper(user, id) do +    with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)}, +         {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, +         {_, {:ok, %Activity{} = activity, _meta}} <- +           {:common_pipeline, +            Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do +      {:ok, activity}      else -      {:find_activity, _} -> {:error, :not_found} -      _ -> {:error, dgettext("errors", "Could not favorite")} +      {:find_object, _} -> +        {:error, :not_found} + +      {:common_pipeline, +       { +         :error, +         { +           :validate_object, +           { +             :error, +             changeset +           } +         } +       }} = e -> +        if {:object, {"already liked by this actor", []}} in changeset.errors do +          {:ok, :already_liked} +        else +          {:error, e} +        end + +      e -> +        {:error, e}      end    end diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index b49523ec3..4780081b2 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -5,10 +5,18 @@  defmodule Pleroma.Web.ControllerHelper do    use Pleroma.Web, :controller -  # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html +  alias Pleroma.Config + +  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html    @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] -  def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil -  def truthy_param?(value), do: value not in @falsy_param_values + +  def explicitly_falsy_param?(value), do: value in @falsy_param_values + +  # Note: `nil` and `""` are considered falsy values in Pleroma +  def falsy_param?(value), +    do: explicitly_falsy_param?(value) or value in [nil, ""] + +  def truthy_param?(value), do: not falsy_param?(value)    def json_response(conn, status, json) do      conn @@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do    def put_if_exist(map, _key, nil), do: map    def put_if_exist(map, key, value), do: Map.put(map, key, value) + +  @doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications" +  def skip_relationships?(params) do +    if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do +      false +    else +      # BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships. +      not truthy_param?(params["with_relationships"]) +    end +  end  end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index bd6853d12..fe425c2f4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    use Pleroma.Web, :controller    import Pleroma.Web.ControllerHelper, -    only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] +    only: [ +      add_link_headers: 2, +      truthy_param?: 1, +      assign_account_by_id: 2, +      json_response: 3, +      skip_relationships?: 1 +    ]    alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Plugs.RateLimiter @@ -240,7 +246,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do        conn        |> add_link_headers(activities)        |> put_view(StatusView) -      |> render("index.json", activities: activities, for: reading_user, as: :activity) +      |> render("index.json", +        activities: activities, +        for: reading_user, +        as: :activity, +        skip_relationships: skip_relationships?(params) +      )      else        _e -> render_error(conn, :not_found, "Can't find user")      end diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 5e2871f18..005c60444 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do    action_fallback(Pleroma.Web.MastodonAPI.FallbackController)    plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) +  plug(OpenApiSpex.Plug.CastAndValidate)    @local_mastodon_name "Mastodon-Local" +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation +    @doc "POST /api/v1/apps" -  def create(conn, params) do +  def create(%{body_params: params} = conn, _params) do      scopes = Scopes.fetch_scopes(params, ["read"])      app_attrs =        params -      |> Map.drop(["scope", "scopes"]) -      |> Map.put("scopes", scopes) +      |> Map.take([:client_name, :redirect_uris, :website]) +      |> Map.put(:scopes, scopes)      with cs <- App.register_changeset(%App{}, app_attrs),           false <- cs.changes[:client_name] == @local_mastodon_name, 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 e4156cbe6..84de79413 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,6 +8,9 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do    alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User +  plug(OpenApiSpex.Plug.CastAndValidate) +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation +    plug(      OAuthScopesPlug,      %{scopes: ["follow", "read:blocks"]} when action == :index @@ -26,13 +29,13 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do    end    @doc "POST /api/v1/domain_blocks" -  def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do +  def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do      User.block_domain(blocker, domain)      json(conn, %{})    end    @doc "DELETE /api/v1/domain_blocks" -  def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do +  def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do      User.unblock_domain(blocker, domain)      json(conn, %{})    end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 0c9218454..7fb536b09 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.MastodonAPI.NotificationController do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] +  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]    alias Pleroma.Notification    alias Pleroma.Plugs.OAuthScopesPlug @@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do      conn      |> add_link_headers(notifications) -    |> render("index.json", notifications: notifications, for: user) +    |> render("index.json", +      notifications: notifications, +      for: user, +      skip_relationships: skip_relationships?(params) +    )    end    # GET /api/v1/notifications/:id @@ -66,7 +70,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do      json(conn, %{})    end -  # POST /api/v1/notifications/dismiss +  # POST /api/v1/notifications/:id/dismiss +  # POST /api/v1/notifications/dismiss (deprecated)    def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do      with {:ok, _notif} <- Notification.dismiss(user, id) do        json(conn, %{}) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index fcab4ef63..c258742dd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,13 +5,14 @@  defmodule Pleroma.Web.MastodonAPI.SearchController do    use Pleroma.Web, :controller +  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1] +    alias Pleroma.Activity    alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Plugs.RateLimiter    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web -  alias Pleroma.Web.ControllerHelper    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.StatusView @@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    defp search_options(params, user) do      [ +      skip_relationships: skip_relationships?(params),        resolve: params["resolve"] == "true",        following: params["following"] == "true", -      limit: ControllerHelper.fetch_integer_param(params, "limit"), -      offset: ControllerHelper.fetch_integer_param(params, "offset"), +      limit: fetch_integer_param(params, "limit"), +      offset: fetch_integer_param(params, "offset"),        type: params["type"],        author: get_author(params),        for_user: user @@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    defp resource_search(_, "accounts", query, options) do      accounts = with_fallback(fn -> User.search(query, options) end) -    AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user) + +    AccountView.render("index.json", +      users: accounts, +      for: options[:for_user], +      as: :user, +      skip_relationships: false +    )    end    defp resource_search(_, "statuses", query, options) do      statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) -    StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity) + +    StatusView.render("index.json", +      activities: statuses, +      for: options[:for_user], +      as: :activity, +      skip_relationships: options[:skip_relationships] +    )    end    defp resource_search(:v2, "hashtags", query, _options) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 37afe6949..397dd10e3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -5,7 +5,8 @@  defmodule Pleroma.Web.MastodonAPI.StatusController do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2] +  import Pleroma.Web.ControllerHelper, +    only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]    require Ecto.Query @@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    `ids` query param is required    """ -  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do +  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do      limit = 100      activities = @@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do        |> Activity.all_by_ids_with_object()        |> Enum.filter(&Visibility.visible_for_user?(&1, user)) -    render(conn, "index.json", activities: activities, for: user, as: :activity) +    render(conn, "index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    @doc """ @@ -207,9 +213,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    end    @doc "POST /api/v1/statuses/:id/favourite" -  def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do +  def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do +    with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), +         %Activity{} = activity <- Activity.get_by_id(activity_id) do        try_render(conn, "show.json", activity: activity, for: user, as: :activity)      end    end @@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do      conn      |> add_link_headers(activities) -    |> render("index.json", activities: activities, for: user, as: :activity) +    |> render("index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    @doc "GET /api/v1/bookmarks" @@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do      conn      |> add_link_headers(bookmarks) -    |> render("index.json", %{activities: activities, for: user, as: :activity}) +    |> render("index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end  end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 91f41416d..b3c58005e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    use Pleroma.Web, :controller    import Pleroma.Web.ControllerHelper, -    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] +    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]    alias Pleroma.Pagination    alias Pleroma.Plugs.OAuthScopesPlug @@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  # TODO: Replace with a macro when there is a Phoenix release with +  # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:    # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e -  # in it    plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)    plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) @@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      conn      |> add_link_headers(activities) -    |> render("index.json", activities: activities, for: user, as: :activity) +    |> render("index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    # GET /api/v1/timelines/direct @@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      conn      |> add_link_headers(activities) -    |> render("index.json", activities: activities, for: user, as: :activity) +    |> render("index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    # GET /api/v1/timelines/public @@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do        conn        |> add_link_headers(activities, %{"local" => local_only}) -      |> render("index.json", activities: activities, for: user, as: :activity) +      |> render("index.json", +        activities: activities, +        for: user, +        as: :activity, +        skip_relationships: skip_relationships?(params) +      )      else        render_error(conn, :unauthorized, "authorization required for timeline view")      end @@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      conn      |> add_link_headers(activities, %{"local" => local_only}) -    |> render("index.json", activities: activities, for: user, as: :activity) +    |> render("index.json", +      activities: activities, +      for: user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    # GET /api/v1/timelines/list/:list_id @@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do          |> ActivityPub.fetch_activities_bounded(following, params)          |> Enum.reverse() -      render(conn, "index.json", activities: activities, for: user, as: :activity) +      render(conn, "index.json", +        activities: activities, +        for: user, +        as: :activity, +        skip_relationships: skip_relationships?(params) +      )      else        _e -> render_error(conn, :forbidden, "Error.")      end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 99e62f580..8fb96a22a 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do    def render("index.json", %{users: users} = opts) do      reading_user = opts[:for] +    # Note: :skip_relationships option is currently intentionally not supported for accounts      relationships_opt =        cond do          Map.has_key?(opts, :relationships) -> @@ -73,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      followed_by =        if following_relationships do          case FollowingRelationship.find(following_relationships, target, reading_user) do -          %{state: "accept"} -> true +          %{state: :follow_accept} -> true            _ -> false          end        else @@ -83,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags      %{        id: to_string(target.id), -      following: follow_state == "accept", +      following: follow_state == :follow_accept,        followed_by: followed_by,        blocking:          UserRelationship.exists?( @@ -125,7 +126,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do            reading_user,            &User.subscribed_to?(&2, &1)          ), -      requested: follow_state == "pending", +      requested: follow_state == :follow_pending,        domain_blocking: User.blocks_domain?(reading_user, target),        showing_reblogs:          not UserRelationship.exists?( @@ -192,11 +193,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do        end)      relationship = -      render("relationship.json", %{ -        user: opts[:for], -        target: user, -        relationships: opts[:relationships] -      }) +      if opts[:skip_relationships] do +        %{} +      else +        render("relationship.json", %{ +          user: opts[:for], +          target: user, +          relationships: opts[:relationships] +        }) +      end      %{        id: to_string(user.id), diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ae87d4701..734ffbf39 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do              |> Enum.filter(& &1)              |> Kernel.++(move_activities_targets) -          UserRelationship.view_relationships_option(reading_user, actors) +          UserRelationship.view_relationships_option(reading_user, actors, +            source_mutes_only: opts[:skip_relationships] +          )        end -    opts = %{ -      for: reading_user, -      parent_activities: parent_activities, -      relationships: relationships_opt -    } +    opts = +      opts +      |> Map.put(:parent_activities, parent_activities) +      |> Map.put(:relationships, relationships_opt)      safe_render_many(notifications, NotificationView, "show.json", opts)    end @@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do      mastodon_type = Activity.mastodon_notification_type(activity) +    render_opts = %{ +      relationships: opts[:relationships], +      skip_relationships: opts[:skip_relationships] +    } +      with %{id: _} = account <- -           AccountView.render("show.json", %{ -             user: actor, -             for: reading_user, -             relationships: opts[:relationships] -           }) do +           AccountView.render( +             "show.json", +             Map.merge(render_opts, %{user: actor, for: reading_user}) +           ) do        response = %{          id: to_string(notification.id),          type: mastodon_type, @@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do          }        } -      render_opts = %{relationships: opts[:relationships]} -        case mastodon_type do          "mention" ->            put_status(response, activity, reading_user, render_opts) @@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do            put_status(response, parent_activity_fn.(), reading_user, render_opts)          "move" -> +          # Note: :skip_relationships option being applied to _account_ rendering (here)            put_target(response, activity, reading_user, render_opts)          "follow" -> diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index cea76e735..b5850e1ae 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -99,7 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          true ->            actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"])) -          UserRelationship.view_relationships_option(reading_user, actors) +          UserRelationship.view_relationships_option(reading_user, actors, +            source_mutes_only: opts[:skip_relationships] +          )        end      opts = @@ -153,7 +155,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          AccountView.render("show.json", %{            user: user,            for: opts[:for], -          relationships: opts[:relationships] +          relationships: opts[:relationships], +          skip_relationships: opts[:skip_relationships]          }),        in_reply_to_id: nil,        in_reply_to_account_id: nil, @@ -301,6 +304,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          _ -> []        end +    # Status muted state (would do 1 request per status unless user mutes are preloaded)      muted =        thread_muted? ||          UserRelationship.exists?( @@ -319,7 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          AccountView.render("show.json", %{            user: user,            for: opts[:for], -          relationships: opts[:relationships] +          relationships: opts[:relationships], +          skip_relationships: opts[:skip_relationships]          }),        in_reply_to_id: reply_to && to_string(reply_to.id),        in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 30838b1eb..f9a5ddcc0 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -75,7 +75,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do          end,          if Config.get([:instance, :safe_dm_mentions]) do            "safe_dm_mentions" -        end +        end, +        "pleroma_emoji_reactions"        ]        |> Enum.filter(& &1) diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 8ecf901f3..1023f16d4 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do    Note: `scopes` is used by Mastodon — supporting it but sticking to    OAuth's standard `scope` wherever we control it    """ -  @spec fetch_scopes(map(), list()) :: list() +  @spec fetch_scopes(map() | struct(), list()) :: list() + +  def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do +    parse_scopes(scopes, default) +  end +    def fetch_scopes(params, default) do      parse_scopes(params["scope"] || params["scopes"], default)    end diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index dcba67d03..9d0b3b1e4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do    use Pleroma.Web, :controller    import Pleroma.Web.ControllerHelper, -    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] +    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]    alias Ecto.Changeset    alias Pleroma.Plugs.OAuthScopesPlug @@ -139,7 +139,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do      conn      |> add_link_headers(activities)      |> put_view(StatusView) -    |> render("index.json", activities: activities, for: for_user, as: :activity) +    |> render("index.json", +      activities: activities, +      for: for_user, +      as: :activity, +      skip_relationships: skip_relationships?(params) +    )    end    @doc "POST /api/v1/pleroma/accounts/:id/subscribe" diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 75f61b675..fe1b97a20 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] +  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]    alias Pleroma.Activity    alias Pleroma.Conversation.Participation @@ -110,12 +110,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do    end    def conversation_statuses( -        %{assigns: %{user: user}} = conn, +        %{assigns: %{user: %{id: user_id} = user}} = conn,          %{"id" => participation_id} = params        ) do -    with %Participation{} = participation <- -           Participation.get(participation_id, preload: [:conversation]), -         true <- user.id == participation.user_id do +    with %Participation{user_id: ^user_id} = participation <- +           Participation.get(participation_id, preload: [:conversation]) do        params =          params          |> Map.put("blocking_user", user) @@ -124,13 +123,19 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do        activities =          participation.conversation.ap_id -        |> ActivityPub.fetch_activities_for_context(params) +        |> ActivityPub.fetch_activities_for_context_query(params) +        |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))          |> Enum.reverse()        conn        |> add_link_headers(activities)        |> put_view(StatusView) -      |> render("index.json", %{activities: activities, for: user, as: :activity}) +      |> render("index.json", +        activities: activities, +        for: user, +        as: :activity, +        skip_relationships: skip_relationships?(params) +      )      else        _error ->          conn @@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do      end    end -  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do +  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do      with notifications <- Notification.set_read_up_to(user, max_id) do        notifications = Enum.take(notifications, 80)        conn        |> put_view(NotificationView) -      |> render("index.json", %{notifications: notifications, for: user}) +      |> render("index.json", +        notifications: notifications, +        for: user, +        skip_relationships: skip_relationships?(params) +      )      end    end  end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 0314535d2..9d3d7f978 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -64,5 +64,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do    def fetch_data_for_activity(_), do: %{} -  def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity) +  def perform(:fetch, %Activity{} = activity) do +    fetch_data_for_activity(activity) +    :ok +  end  end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3d57073d0..8d13cd6c9 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do      plug(Pleroma.Plugs.SetUserSessionIdPlug)      plug(Pleroma.Plugs.EnsureUserKeyPlug)      plug(Pleroma.Plugs.IdempotencyPlug) +    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)    end    pipeline :authenticated_api do @@ -45,6 +46,7 @@ defmodule Pleroma.Web.Router do      plug(Pleroma.Plugs.SetUserSessionIdPlug)      plug(Pleroma.Plugs.EnsureAuthenticatedPlug)      plug(Pleroma.Plugs.IdempotencyPlug) +    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)    end    pipeline :admin_api do @@ -62,6 +64,7 @@ defmodule Pleroma.Web.Router do      plug(Pleroma.Plugs.EnsureAuthenticatedPlug)      plug(Pleroma.Plugs.UserIsAdminPlug)      plug(Pleroma.Plugs.IdempotencyPlug) +    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)    end    pipeline :mastodon_html do @@ -95,10 +98,12 @@ defmodule Pleroma.Web.Router do    pipeline :config do      plug(:accepts, ["json", "xml"]) +    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)    end    pipeline :pleroma_api do      plug(:accepts, ["html", "json"]) +    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)    end    pipeline :mailbox_preview do @@ -348,9 +353,11 @@ defmodule Pleroma.Web.Router do      get("/notifications", NotificationController, :index)      get("/notifications/:id", NotificationController, :show) +    post("/notifications/:id/dismiss", NotificationController, :dismiss)      post("/notifications/clear", NotificationController, :clear) -    post("/notifications/dismiss", NotificationController, :dismiss)      delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) +    # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead +    post("/notifications/dismiss", NotificationController, :dismiss)      get("/scheduled_statuses", ScheduledActivityController, :index)      get("/scheduled_statuses/:id", ScheduledActivityController, :show) @@ -501,6 +508,12 @@ defmodule Pleroma.Web.Router do      )    end +  scope "/api" do +    pipe_through(:api) + +    get("/openapi", OpenApiSpex.Plug.RenderSpec, []) +  end +    scope "/api", Pleroma.Web, as: :authenticated_twitter_api do      pipe_through(:authenticated_api) @@ -37,12 +37,21 @@ defmodule Pleroma.Mixfile do          pleroma: [            include_executables_for: [:unix],            applications: [ex_syslogger: :load, syslog: :load], -          steps: [:assemble, ©_files/1, ©_nginx_config/1] +          steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1]          ]        ]      ]    end +  def put_otp_version(%{path: target_path} = release) do +    File.write!( +      Path.join([target_path, "OTP_VERSION"]), +      Pleroma.OTPVersion.version() +    ) + +    release +  end +    def copy_files(%{path: target_path} = release) do      File.cp_r!("./rel/files", target_path)      release @@ -108,7 +117,7 @@ defmodule Pleroma.Mixfile do        {:ecto_enum, "~> 1.4"},        {:ecto_sql, "~> 3.3.2"},        {:postgrex, ">= 0.13.5"}, -      {:oban, "~> 0.12.1"}, +      {:oban, "~> 1.2"},        {:gettext, "~> 0.15"},        {:comeonin, "~> 4.1.1"},        {:pbkdf2_elixir, "~> 0.12.3"}, @@ -174,12 +183,13 @@ defmodule Pleroma.Mixfile do        {:flake_id, "~> 0.1.0"},        {:remote_ip,         git: "https://git.pleroma.social/pleroma/remote_ip.git", -       ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"}, +       ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},        {:captcha,         git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",         ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},        {:mox, "~> 0.5", only: :test}, -      {:restarter, path: "./restarter"} +      {:restarter, path: "./restarter"}, +      {:open_api_spex, "~> 3.6"}      ] ++ oauth_deps()    end @@ -26,7 +26,7 @@    "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},    "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},    "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, -  "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"}, +  "ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},    "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},    "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},    "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, @@ -55,7 +55,7 @@    "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},    "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},    "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, -  "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, +  "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},    "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},    "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},    "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, @@ -73,7 +73,8 @@    "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},    "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},    "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, -  "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"}, +  "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"}, +  "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},    "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},    "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},    "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, @@ -96,7 +97,7 @@    "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},    "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},    "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"}, -  "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]}, +  "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},    "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},    "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},    "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, diff --git a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs index c618ea381..b6f0ac66b 100644 --- a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs +++ b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs @@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do    import Ecto.Query    alias Pleroma.Activity    alias Pleroma.Bookmark -  alias Pleroma.User    alias Pleroma.Repo    def up do diff --git a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs index 2f336a5e8..43d616705 100644 --- a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs +++ b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs @@ -1,6 +1,5 @@  defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do    use Ecto.Migration -  alias Pleroma.User    def change do      execute(""" diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs new file mode 100644 index 000000000..2b0820f3f --- /dev/null +++ b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs @@ -0,0 +1,29 @@ +defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do +  use Ecto.Migration + +  @alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state" + +  def up do +    execute(""" +    #{@alter_following_relationship_state} TYPE integer USING +    CASE +      WHEN state = 'pending' THEN 1 +      WHEN state = 'accept' THEN 2 +      WHEN state = 'reject' THEN 3 +      ELSE 0 +    END; +    """) +  end + +  def down do +    execute(""" +    #{@alter_following_relationship_state} TYPE varchar(255) USING +    CASE +      WHEN state = 1 THEN 'pending' +      WHEN state = 2 THEN 'accept' +      WHEN state = 3 THEN 'reject' +      ELSE '' +    END; +    """) +  end +end diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs new file mode 100644 index 000000000..884832f84 --- /dev/null +++ b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do +  use Ecto.Migration + +  # [:follower_index] index is useless because of [:follower_id, :following_id] index +  # [:following_id] index makes sense because of user's followers-targeted queries +  def change do +    drop_if_exists(index(:following_relationships, [:follower_id])) + +    create_if_not_exists(index(:following_relationships, [:following_id])) +  end +end diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs new file mode 100644 index 000000000..e7ff04008 --- /dev/null +++ b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do +  use Ecto.Migration + +  def up do +    Oban.Migrations.up(version: 8) +  end + +  def down do +    Oban.Migrations.down(version: 7) +  end +end diff --git a/test/fixtures/emoji/packs/blank.png.zip b/test/fixtures/emoji/packs/blank.png.zip Binary files differnew file mode 100644 index 000000000..651daf127 --- /dev/null +++ b/test/fixtures/emoji/packs/blank.png.zip diff --git a/test/fixtures/emoji/packs/default-manifest.json b/test/fixtures/emoji/packs/default-manifest.json new file mode 100644 index 000000000..c8433808d --- /dev/null +++ b/test/fixtures/emoji/packs/default-manifest.json @@ -0,0 +1,10 @@ +{ +  "finmoji": { +    "license": "CC BY-NC-ND 4.0", +    "homepage": "https://finland.fi/emoji/", +    "description": "Finland is the first country in the world to publish its own set of country themed emojis. The Finland emoji collection contains 56 tongue-in-cheek emotions, which were created to explain some hard-to-describe Finnish emotions, Finnish words and customs.", +    "src": "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip", +    "src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D", +    "files": "finmoji.json" +  } +}
\ No newline at end of file diff --git a/test/fixtures/emoji/packs/finmoji.json b/test/fixtures/emoji/packs/finmoji.json new file mode 100644 index 000000000..279770998 --- /dev/null +++ b/test/fixtures/emoji/packs/finmoji.json @@ -0,0 +1,3 @@ +{ +  "blank": "blank.png" +}
\ No newline at end of file diff --git a/test/fixtures/emoji/packs/manifest.json b/test/fixtures/emoji/packs/manifest.json new file mode 100644 index 000000000..2d51a459b --- /dev/null +++ b/test/fixtures/emoji/packs/manifest.json @@ -0,0 +1,10 @@ +{ +  "blobs.gg": { +    "src_sha256": "3a12f3a181678d5b3584a62095411b0d60a335118135910d879920f8ade5a57f", +    "src": "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip", +    "license": "Apache 2.0", +    "homepage": "https://blobs.gg", +    "files": "blobs_gg.json", +    "description": "Blob Emoji from blobs.gg repacked as apng" +  } +}
\ No newline at end of file diff --git a/test/following_relationship_test.exs b/test/following_relationship_test.exs index 865bb3838..17a468abb 100644 --- a/test/following_relationship_test.exs +++ b/test/following_relationship_test.exs @@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do      test "returns following addresses without internal.fetch" do        user = insert(:user)        fetch_actor = InternalFetchActor.get_actor() -      FollowingRelationship.follow(fetch_actor, user, "accept") +      FollowingRelationship.follow(fetch_actor, user, :follow_accept)        assert FollowingRelationship.following(fetch_actor) == [user.follower_address]      end      test "returns following addresses without relay" do        user = insert(:user)        relay_actor = Relay.get_actor() -      FollowingRelationship.follow(relay_actor, user, "accept") +      FollowingRelationship.follow(relay_actor, user, :follow_accept)        assert FollowingRelationship.following(relay_actor) == [user.follower_address]      end      test "returns following addresses without remote user" do        user = insert(:user)        actor = insert(:user, local: false) -      FollowingRelationship.follow(actor, user, "accept") +      FollowingRelationship.follow(actor, user, :follow_accept)        assert FollowingRelationship.following(actor) == [user.follower_address]      end      test "returns following addresses with local user" do        user = insert(:user)        actor = insert(:user, local: true) -      FollowingRelationship.follow(actor, user, "accept") +      FollowingRelationship.follow(actor, user, :follow_accept)        assert FollowingRelationship.following(actor) == [                 actor.follower_address, diff --git a/test/formatter_test.exs b/test/formatter_test.exs index cf8441cf6..93fd8eab7 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -150,13 +150,13 @@ defmodule Pleroma.FormatterTest do        assert length(mentions) == 3        expected_text = -        ~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{ +        ~s(<span class="h-card"><a class="u-url mention" data-user="#{gsimg.id}" href="#{            gsimg.ap_id -        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{ +        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a class="u-url mention" data-user="#{            archaeme.id -        }" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{ +        }" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a class="u-url mention" data-user="#{            archaeme_remote.id -        }" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>) +        }" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)        assert expected_text == text      end @@ -171,7 +171,7 @@ defmodule Pleroma.FormatterTest do        assert length(mentions) == 1        expected_text = -        ~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{ +        ~s(<span class="h-card"><a class="u-url mention" data-user="#{mike.id}" href="#{            mike.ap_id          }" rel="ugc">@<span>mike</span></a></span> test) @@ -187,7 +187,7 @@ defmodule Pleroma.FormatterTest do        assert length(mentions) == 1        expected_text = -        ~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi) +        ~s(<span class="h-card"><a class="u-url mention" data-user="#{o.id}" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)        assert expected_text == text      end @@ -209,17 +209,13 @@ defmodule Pleroma.FormatterTest do        assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]        assert expected_text == -               ~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{ +               ~s(<span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="#{                   user.ap_id -               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{ +               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{                   other_user.id -               }" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{ -                 other_user.nickname -               }</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{ +               }" href="#{other_user.ap_id}" rel="ugc">@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class="h-card"><a class="u-url mention" data-user="#{                   third_user.id -               }" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{ -                 third_user.nickname -               }</span></a></span>) +               }" href="#{third_user.ap_id}" rel="ugc">@<span>#{third_user.nickname}</span></a></span>)      end      test "given the 'safe_mention' option, it will still work without any mention" do diff --git a/test/notification_test.exs b/test/notification_test.exs index 7cfa40c51..837a9dacd 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -537,7 +537,7 @@ defmodule Pleroma.NotificationTest do            "status" => "hey @#{other_user.nickname}!"          }) -      {:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user) +      {:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id)        {enabled_receivers, _disabled_receivers} =          Notification.get_notified_from_activity(activity_two) @@ -620,7 +620,7 @@ defmodule Pleroma.NotificationTest do        assert Enum.empty?(Notification.for_user(user)) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        assert length(Notification.for_user(user)) == 1 @@ -637,7 +637,7 @@ defmodule Pleroma.NotificationTest do        assert Enum.empty?(Notification.for_user(user)) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        assert length(Notification.for_user(user)) == 1 @@ -692,7 +692,7 @@ defmodule Pleroma.NotificationTest do        assert Enum.empty?(Notification.for_user(user)) -      {:error, _} = CommonAPI.favorite(activity.id, other_user) +      {:error, :not_found} = CommonAPI.favorite(other_user, activity.id)        assert Enum.empty?(Notification.for_user(user))      end diff --git a/test/object_test.exs b/test/object_test.exs index fe583decd..198d3b1cf 100644 --- a/test/object_test.exs +++ b/test/object_test.exs @@ -380,7 +380,8 @@ defmodule Pleroma.ObjectTest do        user = insert(:user)        activity = Activity.get_create_by_object_ap_id(object.data["id"]) -      {:ok, _activity, object} = CommonAPI.favorite(activity.id, user) +      {:ok, activity} = CommonAPI.favorite(user, activity.id) +      object = Object.get_by_ap_id(activity.data["object"])        assert object.data["like_count"] == 1 diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index 0ce9f3a0a..4d3d694f4 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -5,8 +5,10 @@  defmodule Pleroma.Plugs.RateLimiterTest do    use Pleroma.Web.ConnCase +  alias Phoenix.ConnTest    alias Pleroma.Config    alias Pleroma.Plugs.RateLimiter +  alias Plug.Conn    import Pleroma.Factory    import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2] @@ -36,8 +38,15 @@ defmodule Pleroma.Plugs.RateLimiterTest do    end    test "it is disabled if it remote ip plug is enabled but no remote ip is found" do -    Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) -    assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false)) +    assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false)) +  end + +  test "it is enabled if remote ip found" do +    refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true)) +  end + +  test "it is enabled if remote_ip_found flag doesn't exist" do +    refute RateLimiter.disabled?(build_conn())    end    test "it restricts based on config values" do @@ -58,7 +67,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do      end      conn = RateLimiter.call(conn, plug_opts) -    assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) +    assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)      assert conn.halted      Process.sleep(50) @@ -68,7 +77,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do      conn = RateLimiter.call(conn, plug_opts)      assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) -    refute conn.status == Plug.Conn.Status.code(:too_many_requests) +    refute conn.status == Conn.Status.code(:too_many_requests)      refute conn.resp_body      refute conn.halted    end @@ -98,7 +107,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do        plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])        conn = build_conn(:get, "/?id=1") -      conn = Plug.Conn.fetch_query_params(conn) +      conn = Conn.fetch_query_params(conn)        conn_2 = build_conn(:get, "/?id=2")        RateLimiter.call(conn, plug_opts) @@ -119,7 +128,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do        id = "100"        conn = build_conn(:get, "/?id=#{id}") -      conn = Plug.Conn.fetch_query_params(conn) +      conn = Conn.fetch_query_params(conn)        conn_2 = build_conn(:get, "/?id=#{101}")        RateLimiter.call(conn, plug_opts) @@ -147,13 +156,13 @@ defmodule Pleroma.Plugs.RateLimiterTest do        conn = RateLimiter.call(conn, plug_opts) -      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) +      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)        assert conn.halted        conn_2 = RateLimiter.call(conn_2, plug_opts)        assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) -      refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) +      refute conn_2.status == Conn.Status.code(:too_many_requests)        refute conn_2.resp_body        refute conn_2.halted      end @@ -187,7 +196,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do        conn = RateLimiter.call(conn, plug_opts) -      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) +      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)        assert conn.halted      end @@ -210,12 +219,12 @@ defmodule Pleroma.Plugs.RateLimiterTest do        end        conn = RateLimiter.call(conn, plug_opts) -      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) +      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)        assert conn.halted        conn_2 = RateLimiter.call(conn_2, plug_opts)        assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) -      refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) +      refute conn_2.status == Conn.Status.code(:too_many_requests)        refute conn_2.resp_body        refute conn_2.halted      end diff --git a/test/stat_test.exs b/test/stat_test.exs index 33b77e7e7..bccc1c8d0 100644 --- a/test/stat_test.exs +++ b/test/stat_test.exs @@ -60,7 +60,7 @@ defmodule Pleroma.StateTest do        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})        _ = CommonAPI.follow(user, other_user) -      CommonAPI.favorite(activity.id, other_user) +      CommonAPI.favorite(other_user, activity.id)        CommonAPI.repeat(activity.id, other_user)        assert %{direct: 0, private: 0, public: 1, unlisted: 0} = diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs index ed1c31d9c..7b05993d3 100644 --- a/test/tasks/database_test.exs +++ b/test/tasks/database_test.exs @@ -102,7 +102,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do        {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})        {:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"}) -      CommonAPI.favorite(id, user2) +      CommonAPI.favorite(user2, id)        likes = %{          "first" => diff --git a/test/tasks/emoji_test.exs b/test/tasks/emoji_test.exs new file mode 100644 index 000000000..f5de3ef0e --- /dev/null +++ b/test/tasks/emoji_test.exs @@ -0,0 +1,226 @@ +defmodule Mix.Tasks.Pleroma.EmojiTest do +  use ExUnit.Case, async: true + +  import ExUnit.CaptureIO +  import Tesla.Mock + +  alias Mix.Tasks.Pleroma.Emoji + +  describe "ls-packs" do +    test "with default manifest as url" do +      mock(fn +        %{ +          method: :get, +          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/default-manifest.json") +          } +      end) + +      capture_io(fn -> Emoji.run(["ls-packs"]) end) =~ +        "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" +    end + +    test "with passed manifest as file" do +      capture_io(fn -> +        Emoji.run(["ls-packs", "-m", "test/fixtures/emoji/packs/manifest.json"]) +      end) =~ "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip" +    end +  end + +  describe "get-packs" do +    test "download pack from default manifest" do +      mock(fn +        %{ +          method: :get, +          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/default-manifest.json") +          } + +        %{ +          method: :get, +          url: "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/blank.png.zip") +          } + +        %{ +          method: :get, +          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/finmoji.json" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/finmoji.json") +          } +      end) + +      assert capture_io(fn -> Emoji.run(["get-packs", "finmoji"]) end) =~ "Writing pack.json for" + +      emoji_path = +        Path.join( +          Pleroma.Config.get!([:instance, :static_dir]), +          "emoji" +        ) + +      assert File.exists?(Path.join([emoji_path, "finmoji", "pack.json"])) +      on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end) +    end + +    test "pack not found" do +      mock(fn +        %{ +          method: :get, +          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/default-manifest.json") +          } +      end) + +      assert capture_io(fn -> Emoji.run(["get-packs", "not_found"]) end) =~ +               "No pack named \"not_found\" found" +    end + +    test "raise on bad sha256" do +      mock(fn +        %{ +          method: :get, +          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip" +        } -> +          %Tesla.Env{ +            status: 200, +            body: File.read!("test/fixtures/emoji/packs/blank.png.zip") +          } +      end) + +      assert_raise RuntimeError, ~r/^Bad SHA256 for blobs.gg/, fn -> +        capture_io(fn -> +          Emoji.run(["get-packs", "blobs.gg", "-m", "test/fixtures/emoji/packs/manifest.json"]) +        end) +      end +    end +  end + +  describe "gen-pack" do +    setup do +      url = "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" + +      mock(fn %{ +                method: :get, +                url: ^url +              } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/emoji/packs/blank.png.zip")} +      end) + +      {:ok, url: url} +    end + +    test "with default extensions", %{url: url} do +      name = "pack1" +      pack_json = "#{name}.json" +      files_json = "#{name}_file.json" +      refute File.exists?(pack_json) +      refute File.exists?(files_json) + +      captured = +        capture_io(fn -> +          Emoji.run([ +            "gen-pack", +            url, +            "--name", +            name, +            "--license", +            "license", +            "--homepage", +            "homepage", +            "--description", +            "description", +            "--files", +            files_json, +            "--extensions", +            ".png .gif" +          ]) +        end) + +      assert captured =~ "#{pack_json} has been created with the pack1 pack" +      assert captured =~ "Using .png .gif extensions" + +      assert File.exists?(pack_json) +      assert File.exists?(files_json) + +      on_exit(fn -> +        File.rm!(pack_json) +        File.rm!(files_json) +      end) +    end + +    test "with custom extensions and update existing files", %{url: url} do +      name = "pack2" +      pack_json = "#{name}.json" +      files_json = "#{name}_file.json" +      refute File.exists?(pack_json) +      refute File.exists?(files_json) + +      captured = +        capture_io(fn -> +          Emoji.run([ +            "gen-pack", +            url, +            "--name", +            name, +            "--license", +            "license", +            "--homepage", +            "homepage", +            "--description", +            "description", +            "--files", +            files_json, +            "--extensions", +            " .png   .gif    .jpeg " +          ]) +        end) + +      assert captured =~ "#{pack_json} has been created with the pack2 pack" +      assert captured =~ "Using .png .gif .jpeg extensions" + +      assert File.exists?(pack_json) +      assert File.exists?(files_json) + +      captured = +        capture_io(fn -> +          Emoji.run([ +            "gen-pack", +            url, +            "--name", +            name, +            "--license", +            "license", +            "--homepage", +            "homepage", +            "--description", +            "description", +            "--files", +            files_json, +            "--extensions", +            " .png   .gif    .jpeg " +          ]) +        end) + +      assert captured =~ "#{pack_json} has been updated with the pack2 pack" + +      on_exit(fn -> +        File.rm!(pack_json) +        File.rm!(files_json) +      end) +    end +  end +end diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index b45f37263..8df835b56 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -140,7 +140,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do      test "user is unsubscribed" do        followed = insert(:user)        user = insert(:user) -      User.follow(user, followed, "accept") +      User.follow(user, followed, :follow_accept)        Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname]) diff --git a/test/user_test.exs b/test/user_test.exs index 8055ebd08..a00b1b5e2 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -194,7 +194,8 @@ defmodule Pleroma.UserTest do      CommonAPI.follow(pending_follower, locked)      CommonAPI.follow(pending_follower, locked)      CommonAPI.follow(accepted_follower, locked) -    Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept") + +    Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept)      assert [^pending_follower] = User.get_follow_requests(locked)    end @@ -319,7 +320,7 @@ defmodule Pleroma.UserTest do            following_address: "http://localhost:4001/users/fuser2/following"          }) -      {:ok, user} = User.follow(user, followed, "accept") +      {:ok, user} = User.follow(user, followed, :follow_accept)        {:ok, user, _activity} = User.unfollow(user, followed) @@ -332,7 +333,7 @@ defmodule Pleroma.UserTest do        followed = insert(:user)        user = insert(:user) -      {:ok, user} = User.follow(user, followed, "accept") +      {:ok, user} = User.follow(user, followed, :follow_accept)        assert User.following(user) == [user.follower_address, followed.follower_address] @@ -353,7 +354,7 @@ defmodule Pleroma.UserTest do    test "test if a user is following another user" do      followed = insert(:user)      user = insert(:user) -    User.follow(user, followed, "accept") +    User.follow(user, followed, :follow_accept)      assert User.following?(user, followed)      refute User.following?(followed, user) @@ -1141,8 +1142,8 @@ defmodule Pleroma.UserTest do        object_two = insert(:note, user: follower)        activity_two = insert(:note_activity, user: follower, note: object_two) -      {:ok, like, _} = CommonAPI.favorite(activity_two.id, user) -      {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) +      {:ok, like} = CommonAPI.favorite(user, activity_two.id) +      {:ok, like_two} = CommonAPI.favorite(follower, activity.id)        {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)        {:ok, job} = User.delete(user) @@ -1404,7 +1405,7 @@ defmodule Pleroma.UserTest do        bio = "A.k.a. @nick@domain.com"        expected_text = -        ~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{ +        ~s(A.k.a. <span class="h-card"><a class="u-url mention" data-user="#{remote_user.id}" href="#{            remote_user.ap_id          }" rel="ugc">@<span>nick@domain.com</span></a></span>) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 573853afa..fbacb3993 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1239,16 +1239,56 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          filename: "an_image.jpg"        } -      conn = +      object =          conn          |> assign(:user, user)          |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) +        |> json_response(:created) -      assert object = json_response(conn, :created)        assert object["name"] == desc        assert object["type"] == "Document"        assert object["actor"] == user.ap_id +      assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"] +      assert is_binary(object_href) +      assert object_mediatype == "image/jpeg" + +      activity_request = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "type" => "Create", +        "object" => %{ +          "type" => "Note", +          "content" => "AP C2S test, attachment", +          "attachment" => [object] +        }, +        "to" => "https://www.w3.org/ns/activitystreams#Public", +        "cc" => [] +      } + +      activity_response = +        conn +        |> assign(:user, user) +        |> post("/users/#{user.nickname}/outbox", activity_request) +        |> json_response(:created) + +      assert activity_response["id"] +      assert activity_response["object"] +      assert activity_response["actor"] == user.ap_id + +      assert %Object{data: %{"attachment" => [attachment]}} = +               Object.normalize(activity_response["object"]) + +      assert attachment["type"] == "Document" +      assert attachment["name"] == desc + +      assert [ +               %{ +                 "href" => ^object_href, +                 "type" => "Link", +                 "mediaType" => ^object_mediatype +               } +             ] = attachment["url"] +      # Fails if unauthenticated        conn        |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})        |> json_response(403) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 049b14498..17e7b97de 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1900,14 +1900,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})        {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "}) -      {:ok, _, _} = CommonAPI.favorite(a4.id, user) -      {:ok, _, _} = CommonAPI.favorite(a3.id, other_user) -      {:ok, _, _} = CommonAPI.favorite(a3.id, user) -      {:ok, _, _} = CommonAPI.favorite(a5.id, other_user) -      {:ok, _, _} = CommonAPI.favorite(a5.id, user) -      {:ok, _, _} = CommonAPI.favorite(a4.id, other_user) -      {:ok, _, _} = CommonAPI.favorite(a1.id, user) -      {:ok, _, _} = CommonAPI.favorite(a1.id, other_user) +      {:ok, _} = CommonAPI.favorite(user, a4.id) +      {:ok, _} = CommonAPI.favorite(other_user, a3.id) +      {:ok, _} = CommonAPI.favorite(user, a3.id) +      {:ok, _} = CommonAPI.favorite(other_user, a5.id) +      {:ok, _} = CommonAPI.favorite(user, a5.id) +      {:ok, _} = CommonAPI.favorite(other_user, a4.id) +      {:ok, _} = CommonAPI.favorite(user, a1.id) +      {:ok, _} = CommonAPI.favorite(other_user, a1.id)        result = ActivityPub.fetch_favourites(user)        assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs new file mode 100644 index 000000000..3c5c3696e --- /dev/null +++ b/test/web/activity_pub/object_validator_test.exs @@ -0,0 +1,83 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.ObjectValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator +  alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "likes" do +    setup do +      user = insert(:user) +      {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) + +      valid_like = %{ +        "to" => [user.ap_id], +        "cc" => [], +        "type" => "Like", +        "id" => Utils.generate_activity_id(), +        "object" => post_activity.data["object"], +        "actor" => user.ap_id, +        "context" => "a context" +      } + +      %{valid_like: valid_like, user: user, post_activity: post_activity} +    end + +    test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do +      {:ok, object, _meta} = ObjectValidator.validate(valid_like, []) + +      assert "id" in Map.keys(object) +    end + +    test "is valid for a valid object", %{valid_like: valid_like} do +      assert LikeValidator.cast_and_validate(valid_like).valid? +    end + +    test "it errors when the actor is missing or not known", %{valid_like: valid_like} do +      without_actor = Map.delete(valid_like, "actor") + +      refute LikeValidator.cast_and_validate(without_actor).valid? + +      with_invalid_actor = Map.put(valid_like, "actor", "invalidactor") + +      refute LikeValidator.cast_and_validate(with_invalid_actor).valid? +    end + +    test "it errors when the object is missing or not known", %{valid_like: valid_like} do +      without_object = Map.delete(valid_like, "object") + +      refute LikeValidator.cast_and_validate(without_object).valid? + +      with_invalid_object = Map.put(valid_like, "object", "invalidobject") + +      refute LikeValidator.cast_and_validate(with_invalid_object).valid? +    end + +    test "it errors when the actor has already like the object", %{ +      valid_like: valid_like, +      user: user, +      post_activity: post_activity +    } do +      _like = CommonAPI.favorite(user, post_activity.id) + +      refute LikeValidator.cast_and_validate(valid_like).valid? +    end + +    test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do +      wrapped_like = +        valid_like +        |> Map.put("actor", %{"id" => valid_like["actor"]}) +        |> Map.put("object", %{"id" => valid_like["object"]}) + +      validated = LikeValidator.cast_and_validate(wrapped_like) + +      assert validated.valid? + +      assert {:actor, valid_like["actor"]} in validated.changes +      assert {:object, valid_like["object"]} in validated.changes +    end +  end +end diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/note_validator_test.exs new file mode 100644 index 000000000..30c481ffb --- /dev/null +++ b/test/web/activity_pub/object_validators/note_validator_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator +  alias Pleroma.Web.ActivityPub.Utils + +  import Pleroma.Factory + +  describe "Notes" do +    setup do +      user = insert(:user) + +      note = %{ +        "id" => Utils.generate_activity_id(), +        "type" => "Note", +        "actor" => user.ap_id, +        "to" => [user.follower_address], +        "cc" => [], +        "content" => "Hellow this is content.", +        "context" => "xxx", +        "summary" => "a post" +      } + +      %{user: user, note: note} +    end + +    test "a basic note validates", %{note: note} do +      %{valid?: true} = NoteValidator.cast_and_validate(note) +    end +  end +end diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs new file mode 100644 index 000000000..3e17a9497 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/date_time_test.exs @@ -0,0 +1,32 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime +  use Pleroma.DataCase + +  test "it validates an xsd:Datetime" do +    valid_strings = [ +      "2004-04-12T13:20:00", +      "2004-04-12T13:20:15.5", +      "2004-04-12T13:20:00-05:00", +      "2004-04-12T13:20:00Z" +    ] + +    invalid_strings = [ +      "2004-04-12T13:00", +      "2004-04-1213:20:00", +      "99-04-12T13:00", +      "2004-04-12" +    ] + +    assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z") + +    Enum.each(valid_strings, fn date_time -> +      result = DateTime.cast(date_time) +      assert {:ok, _} = result +    end) + +    Enum.each(invalid_strings, fn date_time -> +      result = DateTime.cast(date_time) +      assert :error == result +    end) +  end +end diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs new file mode 100644 index 000000000..834213182 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/object_id_test.exs @@ -0,0 +1,37 @@ +defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID +  use Pleroma.DataCase + +  @uris [ +    "http://lain.com/users/lain", +    "http://lain.com", +    "https://lain.com/object/1" +  ] + +  @non_uris [ +    "https://", +    "rin", +    1, +    :x, +    %{"1" => 2} +  ] + +  test "it accepts http uris" do +    Enum.each(@uris, fn uri -> +      assert {:ok, uri} == ObjectID.cast(uri) +    end) +  end + +  test "it accepts an object with a nested uri id" do +    Enum.each(@uris, fn uri -> +      assert {:ok, uri} == ObjectID.cast(%{"id" => uri}) +    end) +  end + +  test "it rejects non-uri strings" do +    Enum.each(@non_uris, fn non_uri -> +      assert :error == ObjectID.cast(non_uri) +      assert :error == ObjectID.cast(%{"id" => non_uri}) +    end) +  end +end diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs new file mode 100644 index 000000000..f3c437498 --- /dev/null +++ b/test/web/activity_pub/pipeline_test.exs @@ -0,0 +1,87 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.PipelineTest do +  use Pleroma.DataCase + +  import Mock +  import Pleroma.Factory + +  describe "common_pipeline/2" do +    test "it goes through validation, filtering, persisting, side effects and federation for local activities" do +      activity = insert(:note_activity) +      meta = [local: true] + +      with_mocks([ +        {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, +        { +          Pleroma.Web.ActivityPub.MRF, +          [], +          [filter: fn o -> {:ok, o} end] +        }, +        { +          Pleroma.Web.ActivityPub.ActivityPub, +          [], +          [persist: fn o, m -> {:ok, o, m} end] +        }, +        { +          Pleroma.Web.ActivityPub.SideEffects, +          [], +          [handle: fn o, m -> {:ok, o, m} end] +        }, +        { +          Pleroma.Web.Federator, +          [], +          [publish: fn _o -> :ok end] +        } +      ]) do +        assert {:ok, ^activity, ^meta} = +                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + +        assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) +        assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) +        assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) +        assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) +        assert_called(Pleroma.Web.Federator.publish(activity)) +      end +    end + +    test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do +      activity = insert(:note_activity) +      meta = [local: false] + +      with_mocks([ +        {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, +        { +          Pleroma.Web.ActivityPub.MRF, +          [], +          [filter: fn o -> {:ok, o} end] +        }, +        { +          Pleroma.Web.ActivityPub.ActivityPub, +          [], +          [persist: fn o, m -> {:ok, o, m} end] +        }, +        { +          Pleroma.Web.ActivityPub.SideEffects, +          [], +          [handle: fn o, m -> {:ok, o, m} end] +        }, +        { +          Pleroma.Web.Federator, +          [], +          [] +        } +      ]) do +        assert {:ok, ^activity, ^meta} = +                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + +        assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) +        assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) +        assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) +        assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) +      end +    end +  end +end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs new file mode 100644 index 000000000..b67bd14b3 --- /dev/null +++ b/test/web/activity_pub/side_effects_test.exs @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.SideEffectsTest do +  use Pleroma.DataCase + +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Builder +  alias Pleroma.Web.ActivityPub.SideEffects +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "like objects" do +    setup do +      user = insert(:user) +      {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) + +      {:ok, like_data, _meta} = Builder.like(user, post.object) +      {:ok, like, _meta} = ActivityPub.persist(like_data, local: true) + +      %{like: like, user: user} +    end + +    test "add the like to the original object", %{like: like, user: user} do +      {:ok, like, _} = SideEffects.handle(like) +      object = Object.get_by_ap_id(like.data["object"]) +      assert object.data["like_count"] == 1 +      assert user.ap_id in object.data["likes"] +    end +  end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index b2cabbd30..2332029e5 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -334,7 +334,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          |> Poison.decode!()          |> Map.put("object", activity.data["object"]) -      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) +      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + +      refute Enum.empty?(activity.recipients)        assert data["actor"] == "http://mastodon.example.org/users/admin"        assert data["type"] == "Like" @@ -1228,19 +1230,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        attachment = %{          "type" => "Link",          "mediaType" => "video/mp4", -        "href" => -          "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", -        "mimeType" => "video/mp4", -        "size" => 5_015_880,          "url" => [            %{              "href" =>                "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", -            "mediaType" => "video/mp4", -            "type" => "Link" +            "mediaType" => "video/mp4"            } -        ], -        "width" => 480 +        ]        }        assert object.data["url"] == @@ -1622,7 +1618,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          })        user_two = insert(:user) -      Pleroma.FollowingRelationship.follow(user_two, user, "accept") +      Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)        {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})        {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) @@ -2061,11 +2057,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do                   %{                     "mediaType" => "video/mp4",                     "url" => [ -                     %{ -                       "href" => "https://peertube.moe/stat-480.mp4", -                       "mediaType" => "video/mp4", -                       "type" => "Link" -                     } +                     %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}                     ]                   }                 ] @@ -2083,23 +2075,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do                   %{                     "mediaType" => "video/mp4",                     "url" => [ -                     %{ -                       "href" => "https://pe.er/stat-480.mp4", -                       "mediaType" => "video/mp4", -                       "type" => "Link" -                     } +                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}                     ]                   },                   %{ -                   "href" => "https://pe.er/stat-480.mp4",                     "mediaType" => "video/mp4", -                   "mimeType" => "video/mp4",                     "url" => [ -                     %{ -                       "href" => "https://pe.er/stat-480.mp4", -                       "mediaType" => "video/mp4", -                       "type" => "Link" -                     } +                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}                     ]                   }                 ] diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index de5ffc5b3..6c006206b 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -59,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do      object = Object.normalize(note)      user = insert(:user) -    {:ok, like_activity, _} = CommonAPI.favorite(note.id, user) +    {:ok, like_activity} = CommonAPI.favorite(user, note.id)      result = ObjectView.render("object.json", %{object: like_activity}) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index fe8a086d8..60ec895f5 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -625,6 +625,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert json_response(conn, :forbidden)      end + +    test "email with +", %{conn: conn, admin: admin} do +      recipient_email = "foo+bar@baz.com" + +      conn +      |> put_req_header("content-type", "application/json;charset=utf-8") +      |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email}) +      |> json_response(:no_content) + +      token_record = +        Pleroma.UserInviteToken +        |> Repo.all() +        |> List.last() + +      assert token_record +      refute token_record.used + +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      email = +        Pleroma.Emails.UserEmail.user_invitation_email( +          admin, +          token_record, +          recipient_email +        ) + +      Swoosh.TestAssertions.assert_email_sent( +        from: {instance_name, notify_email}, +        to: recipient_email, +        html_body: email.html_body +      ) +    end    end    describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do @@ -637,7 +670,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") -      assert json_response(conn, :internal_server_error) +      assert json_response(conn, :bad_request) == +               "To send invites you need to set the `invites_enabled` option to true."      end      test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do @@ -646,7 +680,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") -      assert json_response(conn, :internal_server_error) +      assert json_response(conn, :bad_request) == +               "To send invites you need to set the `registrations_open` option to false."      end    end @@ -2238,13 +2273,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do            value: :erlang.term_to_binary([])          ) +      Pleroma.Config.TransferTask.load_and_update_env([], false) + +      assert Application.get_env(:logger, :backends) == [] +        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{                group: config.group,                key: config.key, -              value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}] +              value: [":console"]              }            ]          }) @@ -2255,8 +2294,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "group" => ":logger",                     "key" => ":backends",                     "value" => [ -                     ":console", -                     %{"tuple" => ["ExSyslogger", ":ex_syslogger"]} +                     ":console"                     ],                     "db" => [":backends"]                   } @@ -2264,14 +2302,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do               }        assert Application.get_env(:logger, :backends) == [ -               :console, -               {ExSyslogger, :ex_syslogger} +               :console               ] - -      capture_log(fn -> -        require Logger -        Logger.warn("Ooops...") -      end) =~ "Ooops..."      end      test "saving full setting if value is not keyword", %{conn: conn} do diff --git a/test/web/api_spec/app_operation_test.exs b/test/web/api_spec/app_operation_test.exs new file mode 100644 index 000000000..5b96abb44 --- /dev/null +++ b/test/web/api_spec/app_operation_test.exs @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.AppOperationTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Web.ApiSpec +  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest +  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse + +  import OpenApiSpex.TestAssertions +  import Pleroma.Factory + +  test "AppCreateRequest example matches schema" do +    api_spec = ApiSpec.spec() +    schema = AppCreateRequest.schema() +    assert_schema(schema.example, "AppCreateRequest", api_spec) +  end + +  test "AppCreateResponse example matches schema" do +    api_spec = ApiSpec.spec() +    schema = AppCreateResponse.schema() +    assert_schema(schema.example, "AppCreateResponse", api_spec) +  end + +  test "AppController produces a AppCreateResponse", %{conn: conn} do +    api_spec = ApiSpec.spec() +    app_attrs = build(:oauth_app) + +    json = +      conn +      |> put_req_header("content-type", "application/json") +      |> post( +        "/api/v1/apps", +        Jason.encode!(%{ +          client_name: app_attrs.client_name, +          redirect_uris: app_attrs.redirect_uris +        }) +      ) +      |> json_response(200) + +    assert_schema(json, "AppCreateResponse", api_spec) +  end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 0da0bd2e2..b12be973f 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -284,9 +284,12 @@ defmodule Pleroma.Web.CommonAPITest do        user = insert(:user)        other_user = insert(:user) -      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) +      {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) -      {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user) +      {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id) +      assert data["type"] == "Like" +      assert data["actor"] == user.ap_id +      assert data["object"] == post_activity.data["object"]      end      test "retweeting a status twice returns the status" do @@ -298,13 +301,13 @@ defmodule Pleroma.Web.CommonAPITest do        {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)      end -    test "favoriting a status twice returns the status" do +    test "favoriting a status twice returns ok, but without the like activity" do        user = insert(:user)        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) -      {:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user) -      {:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user) +      {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id) +      assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)      end    end @@ -562,7 +565,7 @@ defmodule Pleroma.Web.CommonAPITest do        assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =                 CommonAPI.follow(follower, followed) -      assert User.get_follow_state(follower, followed) == "pending" +      assert User.get_follow_state(follower, followed) == :follow_pending        assert {:ok, follower} = CommonAPI.unfollow(follower, followed)        assert User.get_follow_state(follower, followed) == nil @@ -584,7 +587,7 @@ defmodule Pleroma.Web.CommonAPITest do        assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =                 CommonAPI.follow(follower, followed) -      assert User.get_follow_state(follower, followed) == "pending" +      assert User.get_follow_state(follower, followed) == :follow_pending        assert {:ok, follower} = CommonAPI.unfollow(follower, followed)        assert User.get_follow_state(follower, followed) == nil diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index d383d1714..98cf02d49 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -159,11 +159,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        {output, _, _} = Utils.format_input(text, "text/markdown")        assert output == -               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a data-user="#{ +               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{                   user.id -               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{ +               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{                   user.id -               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>) +               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)      end    end diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index b693c1a47..2d256f63c 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -82,9 +82,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do        assert user_data = json_response(conn, 200)        assert user_data["note"] == -               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{ +               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{                   user2.id -               }" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..) +               }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)      end      test "updates the user's locking status", %{conn: conn} do @@ -273,7 +273,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do      test "update fields", %{conn: conn} do        fields = [          %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, -        %{"name" => "link", "value" => "cofe.io"} +        %{"name" => "link.io", "value" => "cofe.io"}        ]        account_data = @@ -283,7 +283,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do        assert account_data["fields"] == [                 %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, -               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} +               %{ +                 "name" => "link.io", +                 "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>) +               }               ]        assert account_data["source"]["fields"] == [ @@ -291,14 +294,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do                   "name" => "<a href=\"http://google.com\">foo</a>",                   "value" => "<script>bar</script>"                 }, -               %{"name" => "link", "value" => "cofe.io"} +               %{"name" => "link.io", "value" => "cofe.io"}               ] +    end +    test "update fields via x-www-form-urlencoded", %{conn: conn} do        fields =          [            "fields_attributes[1][name]=link", -          "fields_attributes[1][value]=cofe.io", -          "fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>", +          "fields_attributes[1][value]=http://cofe.io", +          "fields_attributes[0][name]=foo",            "fields_attributes[0][value]=bar"          ]          |> Enum.join("&") @@ -310,32 +315,49 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do          |> json_response(200)        assert account["fields"] == [ -               %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, -               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} +               %{"name" => "foo", "value" => "bar"}, +               %{ +                 "name" => "link", +                 "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>) +               }               ]        assert account["source"]["fields"] == [ -               %{ -                 "name" => "<a href=\"http://google.com\">foo</a>", -                 "value" => "bar" -               }, -               %{"name" => "link", "value" => "cofe.io"} +               %{"name" => "foo", "value" => "bar"}, +               %{"name" => "link", "value" => "http://cofe.io"}               ] +    end +    test "update fields with empty name", %{conn: conn} do +      fields = [ +        %{"name" => "foo", "value" => ""}, +        %{"name" => "", "value" => "bar"} +      ] + +      account = +        conn +        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +        |> json_response(200) + +      assert account["fields"] == [ +               %{"name" => "foo", "value" => ""} +             ] +    end + +    test "update fields when invalid request", %{conn: conn} do        name_limit = Pleroma.Config.get([:instance, :account_field_name_length])        value_limit = Pleroma.Config.get([:instance, :account_field_value_length]) +      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()        long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join() -      fields = [%{"name" => "<b>foo<b>", "value" => long_value}] +      fields = [%{"name" => "foo", "value" => long_value}]        assert %{"error" => "Invalid request"} ==                 conn                 |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})                 |> json_response(403) -      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join() -        fields = [%{"name" => long_name, "value" => "bar"}]        assert %{"error" => "Invalid request"} == @@ -346,7 +368,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do        Pleroma.Config.put([:instance, :max_account_fields], 1)        fields = [ -        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"}, +        %{"name" => "foo", "value" => "bar"},          %{"name" => "link", "value" => "cofe.io"}        ] @@ -354,20 +376,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do                 conn                 |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})                 |> json_response(403) - -      fields = [ -        %{"name" => "foo", "value" => ""}, -        %{"name" => "", "value" => "bar"} -      ] - -      account = -        conn -        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) -        |> json_response(200) - -      assert account["fields"] == [ -               %{"name" => "foo", "value" => ""} -             ]      end    end  end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index a9fa0ce48..a450a732c 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -794,7 +794,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do      test "Account registration via Application", %{conn: conn} do        conn = -        post(conn, "/api/v1/apps", %{ +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/apps", %{            client_name: "client_name",            redirect_uris: "urn:ietf:wg:oauth:2.0:oob",            scopes: "read, write, follow" diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs index 77d234d67..e7b11d14e 100644 --- a/test/web/mastodon_api/controllers/app_controller_test.exs +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -16,8 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do      conn =        conn -      |> assign(:user, token.user) -      |> assign(:token, token) +      |> put_req_header("authorization", "Bearer #{token.token}")        |> get("/api/v1/apps/verify_credentials")      app = Repo.preload(token, :app).app @@ -37,6 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do      conn =        conn +      |> put_req_header("content-type", "application/json")        |> assign(:user, user)        |> post("/api/v1/apps", %{          client_name: app_attrs.client_name, diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs index 8d24b3b88..d66190c90 100644 --- a/test/web/mastodon_api/controllers/domain_block_controller_test.exs +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -6,20 +6,29 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do    use Pleroma.Web.ConnCase    alias Pleroma.User +  alias Pleroma.Web.ApiSpec +  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse    import Pleroma.Factory +  import OpenApiSpex.TestAssertions    test "blocking / unblocking a domain" do      %{user: user, conn: conn} = oauth_access(["write:blocks"])      other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) -    ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) +    ret_conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})      assert %{} = json_response(ret_conn, 200)      user = User.get_cached_by_ap_id(user.ap_id)      assert User.blocks?(user, other_user) -    ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) +    ret_conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})      assert %{} = json_response(ret_conn, 200)      user = User.get_cached_by_ap_id(user.ap_id) @@ -41,5 +50,12 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do      assert "bad.site" in domain_blocks      assert "even.worse.site" in domain_blocks +    assert_schema(domain_blocks, "DomainBlocksResponse", ApiSpec.spec()) +  end + +  test "DomainBlocksResponse example matches schema" do +    api_spec = ApiSpec.spec() +    schema = DomainBlocksResponse.schema() +    assert_schema(schema.example, "DomainBlocksResponse", api_spec)    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 dd848821a..d8dbe4800 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do        other_user = insert(:user)        {:ok, _activity} = ActivityPub.follow(other_user, user) -      {:ok, other_user} = User.follow(other_user, user, "pending") +      {:ok, other_user} = User.follow(other_user, user, :follow_pending)        assert User.following?(other_user, user) == false @@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do        other_user = insert(:user)        {:ok, _activity} = ActivityPub.follow(other_user, user) -      {:ok, other_user} = User.follow(other_user, user, "pending") +      {:ok, other_user} = User.follow(other_user, user, :follow_pending)        user = User.get_cached_by_id(user.id)        other_user = User.get_cached_by_id(other_user.id) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 23f94e3a6..8c815b415 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do    import Pleroma.Factory +  test "does NOT render account/pleroma/relationship if this is disabled by default" do +    clear_config([:extensions, :output_relationships_in_statuses_by_default], false) + +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, [_notification]} = Notification.create_notifications(activity) + +    response = +      conn +      |> assign(:user, user) +      |> get("/api/v1/notifications") +      |> json_response(200) + +    assert Enum.all?(response, fn n -> +             get_in(n, ["account", "pleroma", "relationship"]) == %{} +           end) +  end +    test "list of notifications" do      %{user: user, conn: conn} = oauth_access(["read:notifications"])      other_user = insert(:user) @@ -26,7 +46,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do        |> get("/api/v1/notifications")      expected_response = -      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ +      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{          user.ap_id        }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" @@ -45,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      conn = get(conn, "/api/v1/notifications/#{notification.id}")      expected_response = -      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ +      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{          user.ap_id        }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" @@ -53,7 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      assert response == expected_response    end -  test "dismissing a single notification" do +  test "dismissing a single notification (deprecated endpoint)" do      %{user: user, conn: conn} = oauth_access(["write:notifications"])      other_user = insert(:user) @@ -69,6 +89,22 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      assert %{} = json_response(conn, 200)    end +  test "dismissing a single notification" do +    %{user: user, conn: conn} = oauth_access(["write:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    {:ok, [notification]} = Notification.create_notifications(activity) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/notifications/#{notification.id}/dismiss") + +    assert %{} = json_response(conn, 200) +  end +    test "clearing all notifications" do      %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])      other_user = insert(:user) @@ -194,10 +230,10 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do        {:ok, private_activity} =          CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"}) -      {:ok, _, _} = CommonAPI.favorite(public_activity.id, user) -      {:ok, _, _} = CommonAPI.favorite(direct_activity.id, user) -      {:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user) -      {:ok, _, _} = CommonAPI.favorite(private_activity.id, user) +      {:ok, _} = CommonAPI.favorite(user, public_activity.id) +      {:ok, _} = CommonAPI.favorite(user, direct_activity.id) +      {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id) +      {:ok, _} = CommonAPI.favorite(user, private_activity.id)        activity_ids =          conn @@ -274,7 +310,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})      {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) -    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) +    {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)      {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)      {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) @@ -310,7 +346,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do      {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})      {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) -    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) +    {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)      {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)      {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index d59974d50..162f7b1b2 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -775,7 +775,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do        user1 = insert(:user)        user2 = insert(:user)        user3 = insert(:user) -      CommonAPI.favorite(activity.id, user2) +      {:ok, _} = CommonAPI.favorite(user2, activity.id)        {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)        {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)        {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) @@ -850,11 +850,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do        activity = insert(:note_activity)        post(conn, "/api/v1/statuses/#{activity.id}/favourite") -      assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200) + +      assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") +             |> json_response(200)      end      test "returns 404 error for a wrong id", %{conn: conn} do -      conn = post(conn, "/api/v1/statuses/1/favourite") +      conn = +        conn +        |> post("/api/v1/statuses/1/favourite")        assert json_response(conn, 404) == %{"error" => "Record not found"}      end @@ -866,7 +870,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      test "unfavorites a status and returns it", %{user: user, conn: conn} do        activity = insert(:note_activity) -      {:ok, _, _} = CommonAPI.favorite(activity.id, user) +      {:ok, _} = CommonAPI.favorite(user, activity.id)        conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite") @@ -1043,6 +1047,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do    end    test "bookmarks" do +    bookmarks_uri = "/api/v1/bookmarks?with_relationships=true" +      %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])      author = insert(:user) @@ -1064,7 +1070,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      assert json_response(response2, 200)["bookmarked"] == true -    bookmarks = get(conn, "/api/v1/bookmarks") +    bookmarks = get(conn, bookmarks_uri)      assert [json_response(response2, 200), json_response(response1, 200)] ==               json_response(bookmarks, 200) @@ -1073,7 +1079,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      assert json_response(response1, 200)["bookmarked"] == false -    bookmarks = get(conn, "/api/v1/bookmarks") +    bookmarks = get(conn, bookmarks_uri)      assert [json_response(response2, 200)] == json_response(bookmarks, 200)    end @@ -1176,7 +1182,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      test "returns users who have favorited the status", %{conn: conn, activity: activity} do        other_user = insert(:user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        response =          conn @@ -1207,7 +1213,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do        other_user = insert(:user)        {:ok, _user_relationship} = User.block(user, other_user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        response =          conn @@ -1219,7 +1225,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      test "does not fail on an unauthenticated request", %{activity: activity} do        other_user = insert(:user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        response =          build_conn() @@ -1239,7 +1245,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do            "visibility" => "direct"          }) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) +      {:ok, _} = CommonAPI.favorite(other_user, activity.id)        favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by" @@ -1399,7 +1405,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) -    {:ok, _, _} = CommonAPI.favorite(activity.id, user) +    {:ok, _} = CommonAPI.favorite(user, activity.id)      first_conn = get(conn, "/api/v1/favourites") @@ -1416,7 +1422,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do            "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."        }) -    {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) +    {:ok, _} = CommonAPI.favorite(user, second_activity.id)      last_like = status["id"] diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index 97b1c3e66..06efdc901 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do    describe "home" do      setup do: oauth_access(["read:statuses"]) +    test "does NOT render account/pleroma/relationship if this is disabled by default", %{ +      user: user, +      conn: conn +    } do +      clear_config([:extensions, :output_relationships_in_statuses_by_default], false) + +      other_user = insert(:user) + +      {:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +      response = +        conn +        |> assign(:user, user) +        |> get("/api/v1/timelines/home") +        |> json_response(200) + +      assert Enum.all?(response, fn n -> +               get_in(n, ["account", "pleroma", "relationship"]) == %{} +             end) +    end +      test "the home timeline", %{user: user, conn: conn} do +      uri = "/api/v1/timelines/home?with_relationships=true" +        following = insert(:user, nickname: "followed")        third_user = insert(:user, nickname: "repeated") @@ -28,13 +51,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do        {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})        {:ok, _, _} = CommonAPI.repeat(activity.id, following) -      ret_conn = get(conn, "/api/v1/timelines/home") +      ret_conn = get(conn, uri)        assert Enum.empty?(json_response(ret_conn, :ok))        {:ok, _user} = User.follow(user, following) -      ret_conn = get(conn, "/api/v1/timelines/home") +      ret_conn = get(conn, uri)        assert [                 %{ @@ -59,7 +82,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do        {:ok, _user} = User.follow(third_user, user) -      ret_conn = get(conn, "/api/v1/timelines/home") +      ret_conn = get(conn, uri)        assert [                 %{ diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 81eefd735..c3ec9dfec 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -54,7 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do      user = insert(:user)      another_user = insert(:user)      {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) -    {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user) +    {:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id)      {:ok, [notification]} = Notification.create_notifications(favorite_activity)      create_activity = Activity.get_by_id(create_activity.id) diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 43f322606..9bcc07b37 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.Web.NodeInfoTest do    import Pleroma.Factory +  alias Pleroma.Config +    setup do: clear_config([:mrf_simple])    setup do: clear_config(:instance) @@ -47,7 +49,7 @@ defmodule Pleroma.Web.NodeInfoTest do      assert result = json_response(conn, 200) -    assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) == +    assert Config.get([Pleroma.User, :restricted_nicknames]) ==               result["metadata"]["restrictedNicknames"]    end @@ -65,10 +67,10 @@ defmodule Pleroma.Web.NodeInfoTest do    end    test "returns fieldsLimits field", %{conn: conn} do -    Pleroma.Config.put([:instance, :max_account_fields], 10) -    Pleroma.Config.put([:instance, :max_remote_account_fields], 15) -    Pleroma.Config.put([:instance, :account_field_name_length], 255) -    Pleroma.Config.put([:instance, :account_field_value_length], 2048) +    Config.put([:instance, :max_account_fields], 10) +    Config.put([:instance, :max_remote_account_fields], 15) +    Config.put([:instance, :account_field_name_length], 255) +    Config.put([:instance, :account_field_value_length], 2048)      response =        conn @@ -82,8 +84,8 @@ defmodule Pleroma.Web.NodeInfoTest do    end    test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do -    option = Pleroma.Config.get([:instance, :safe_dm_mentions]) -    Pleroma.Config.put([:instance, :safe_dm_mentions], true) +    option = Config.get([:instance, :safe_dm_mentions]) +    Config.put([:instance, :safe_dm_mentions], true)      response =        conn @@ -92,7 +94,7 @@ defmodule Pleroma.Web.NodeInfoTest do      assert "safe_dm_mentions" in response["metadata"]["features"] -    Pleroma.Config.put([:instance, :safe_dm_mentions], false) +    Config.put([:instance, :safe_dm_mentions], false)      response =        conn @@ -101,14 +103,14 @@ defmodule Pleroma.Web.NodeInfoTest do      refute "safe_dm_mentions" in response["metadata"]["features"] -    Pleroma.Config.put([:instance, :safe_dm_mentions], option) +    Config.put([:instance, :safe_dm_mentions], option)    end    describe "`metadata/federation/enabled`" do      setup do: clear_config([:instance, :federating])      test "it shows if federation is enabled/disabled", %{conn: conn} do -      Pleroma.Config.put([:instance, :federating], true) +      Config.put([:instance, :federating], true)        response =          conn @@ -117,7 +119,7 @@ defmodule Pleroma.Web.NodeInfoTest do        assert response["metadata"]["federation"]["enabled"] == true -      Pleroma.Config.put([:instance, :federating], false) +      Config.put([:instance, :federating], false)        response =          conn @@ -128,15 +130,39 @@ defmodule Pleroma.Web.NodeInfoTest do      end    end +  test "it shows default features flags", %{conn: conn} do +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    default_features = [ +      "pleroma_api", +      "mastodon_api", +      "mastodon_api_streaming", +      "polls", +      "pleroma_explicit_addressing", +      "shareable_emoji_packs", +      "multifetch", +      "pleroma_emoji_reactions", +      "pleroma:api/v1/notifications:include_types_filter" +    ] + +    assert MapSet.subset?( +             MapSet.new(default_features), +             MapSet.new(response["metadata"]["features"]) +           ) +  end +    test "it shows MRF transparency data if enabled", %{conn: conn} do -    config = Pleroma.Config.get([:instance, :rewrite_policy]) -    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) +    config = Config.get([:instance, :rewrite_policy]) +    Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) -    option = Pleroma.Config.get([:instance, :mrf_transparency]) -    Pleroma.Config.put([:instance, :mrf_transparency], true) +    option = Config.get([:instance, :mrf_transparency]) +    Config.put([:instance, :mrf_transparency], true)      simple_config = %{"reject" => ["example.com"]} -    Pleroma.Config.put(:mrf_simple, simple_config) +    Config.put(:mrf_simple, simple_config)      response =        conn @@ -145,25 +171,25 @@ defmodule Pleroma.Web.NodeInfoTest do      assert response["metadata"]["federation"]["mrf_simple"] == simple_config -    Pleroma.Config.put([:instance, :rewrite_policy], config) -    Pleroma.Config.put([:instance, :mrf_transparency], option) -    Pleroma.Config.put(:mrf_simple, %{}) +    Config.put([:instance, :rewrite_policy], config) +    Config.put([:instance, :mrf_transparency], option) +    Config.put(:mrf_simple, %{})    end    test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do -    config = Pleroma.Config.get([:instance, :rewrite_policy]) -    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) +    config = Config.get([:instance, :rewrite_policy]) +    Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) -    option = Pleroma.Config.get([:instance, :mrf_transparency]) -    Pleroma.Config.put([:instance, :mrf_transparency], true) +    option = Config.get([:instance, :mrf_transparency]) +    Config.put([:instance, :mrf_transparency], true) -    exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions]) -    Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"]) +    exclusions = Config.get([:instance, :mrf_transparency_exclusions]) +    Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])      simple_config = %{"reject" => ["example.com", "other.site"]}      expected_config = %{"reject" => ["example.com"]} -    Pleroma.Config.put(:mrf_simple, simple_config) +    Config.put(:mrf_simple, simple_config)      response =        conn @@ -173,9 +199,9 @@ defmodule Pleroma.Web.NodeInfoTest do      assert response["metadata"]["federation"]["mrf_simple"] == expected_config      assert response["metadata"]["federation"]["exclusions"] == true -    Pleroma.Config.put([:instance, :rewrite_policy], config) -    Pleroma.Config.put([:instance, :mrf_transparency], option) -    Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions) -    Pleroma.Config.put(:mrf_simple, %{}) +    Config.put([:instance, :rewrite_policy], config) +    Config.put([:instance, :mrf_transparency], option) +    Config.put([:instance, :mrf_transparency_exclusions], exclusions) +    Config.put(:mrf_simple, %{})    end  end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 6787b414b..bb349cb19 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -136,7 +136,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do        user = insert(:user) -      {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) +      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)        assert like_activity.data["type"] == "Like" diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs index 2aa87ac30..ae5334015 100644 --- a/test/web/pleroma_api/controllers/account_controller_test.exs +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -138,7 +138,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do        user: user      } do        [activity | _] = insert_pair(:note_activity) -      CommonAPI.favorite(activity.id, user) +      CommonAPI.favorite(user, activity.id)        response =          conn @@ -155,7 +155,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do        user: user      } do        activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) +      CommonAPI.favorite(user, activity.id)        build_conn()        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") @@ -172,7 +172,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do            "visibility" => "direct"          }) -      CommonAPI.favorite(direct.id, user) +      CommonAPI.favorite(user, direct.id)        for u <- [user, current_user] do          response = @@ -202,7 +202,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do            "visibility" => "direct"          }) -      CommonAPI.favorite(direct.id, user) +      CommonAPI.favorite(user, direct.id)        response =          conn @@ -219,7 +219,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do        activities = insert_list(10, :note_activity)        Enum.each(activities, fn activity -> -        CommonAPI.favorite(activity.id, user) +        CommonAPI.favorite(user, activity.id)        end)        third_activity = Enum.at(activities, 2) @@ -245,7 +245,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do        7        |> insert_list(:note_activity)        |> Enum.each(fn activity -> -        CommonAPI.favorite(activity.id, user) +        CommonAPI.favorite(user, activity.id)        end)        response = @@ -277,7 +277,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do      test "returns 403 error when user has hidden own favorites", %{conn: conn} do        user = insert(:user, hide_favorites: true)        activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) +      CommonAPI.favorite(user, activity.id)        conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") @@ -287,7 +287,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do      test "hides favorites for new users by default", %{conn: conn} do        user = insert(:user)        activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) +      CommonAPI.favorite(user, activity.id)        assert user.hide_favorites        conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index 8f0cbe9b2..61a1689b9 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -169,6 +169,23 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do      id_one = activity.id      id_two = activity_two.id      assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result + +    {:ok, %{id: id_three}} = +      CommonAPI.post(other_user, %{ +        "status" => "Bye!", +        "in_reply_to_status_id" => activity.id, +        "in_reply_to_conversation_id" => participation.id +      }) + +    assert [%{"id" => ^id_two}, %{"id" => ^id_three}] = +             conn +             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2") +             |> json_response(:ok) + +    assert [%{"id" => ^id_three}] = +             conn +             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}") +             |> json_response(:ok)    end    test "PATCH /api/v1/pleroma/conversations/:id" do diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 9f931c941..9121d90e7 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -170,7 +170,7 @@ defmodule Pleroma.Web.Push.ImplTest do            "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."        }) -    {:ok, activity, _} = CommonAPI.favorite(activity.id, user) +    {:ok, activity} = CommonAPI.favorite(user, activity.id)      object = Object.normalize(activity)      assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 5b928629b..eb082b79f 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -64,9 +64,6 @@ defmodule Pleroma.Web.StreamerTest do        blocked = insert(:user)        {:ok, _user_relationship} = User.block(user, blocked) -      {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) -      {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) -        task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)        Streamer.add_socket( @@ -74,6 +71,9 @@ defmodule Pleroma.Web.StreamerTest do          %{transport_pid: task.pid, assigns: %{user: user}}        ) +      {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) +      {:ok, notif} = CommonAPI.favorite(blocked, activity.id) +        Streamer.stream("user:notification", notif)        Task.await(task)      end @@ -83,10 +83,6 @@ defmodule Pleroma.Web.StreamerTest do      } do        user2 = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) -      {:ok, activity} = CommonAPI.add_mute(user, activity) -      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) -        task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)        Streamer.add_socket( @@ -94,6 +90,10 @@ defmodule Pleroma.Web.StreamerTest do          %{transport_pid: task.pid, assigns: %{user: user}}        ) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      {:ok, activity} = CommonAPI.add_mute(user, activity) +      {:ok, notif} = CommonAPI.favorite(user2, activity.id) +        Streamer.stream("user:notification", notif)        Task.await(task)      end @@ -103,10 +103,6 @@ defmodule Pleroma.Web.StreamerTest do      } do        user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) -      {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") -      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) -      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) -        task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)        Streamer.add_socket( @@ -114,6 +110,10 @@ defmodule Pleroma.Web.StreamerTest do          %{transport_pid: task.pid, assigns: %{user: user}}        ) +      {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      {:ok, notif} = CommonAPI.favorite(user2, activity.id) +        Streamer.stream("user:notification", notif)        Task.await(task)      end @@ -209,7 +209,7 @@ defmodule Pleroma.Web.StreamerTest do        Pleroma.Config.put([:instance, :skip_thread_containment], false)        author = insert(:user)        user = insert(:user) -      User.follow(user, author, "accept") +      User.follow(user, author, :follow_accept)        activity =          insert(:note_activity, @@ -232,7 +232,7 @@ defmodule Pleroma.Web.StreamerTest do        Pleroma.Config.put([:instance, :skip_thread_containment], true)        author = insert(:user)        user = insert(:user) -      User.follow(user, author, "accept") +      User.follow(user, author, :follow_accept)        activity =          insert(:note_activity, @@ -255,7 +255,7 @@ defmodule Pleroma.Web.StreamerTest do        Pleroma.Config.put([:instance, :skip_thread_containment], false)        author = insert(:user)        user = insert(:user, skip_thread_containment: true) -      User.follow(user, author, "accept") +      User.follow(user, author, :follow_accept)        activity =          insert(:note_activity, @@ -476,7 +476,7 @@ defmodule Pleroma.Web.StreamerTest do      CommonAPI.hide_reblogs(user1, user2)      {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) -    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, user2) +    {:ok, favorite_activity} = CommonAPI.favorite(user2, create_activity.id)      task =        Task.async(fn -> diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 92f9aa0f5..f6e13b661 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -109,7 +109,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, user2} = TwitterAPI.register_user(data2)      expected_text = -      ~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{ +      ~s(<span class="h-card"><a class="u-url mention" data-user="#{user1.id}" href="#{          user1.ap_id        }" rel="ugc">@<span>john</span></a></span> test)  | 
