From c077dc7af5e2a378223a8d2862df1d52877ea245 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Oct 2019 11:52:21 -0500 Subject: [PATCH 001/337] Initial doc about storing remote media --- docs/administration/storing_remote_media.md | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docs/administration/storing_remote_media.md diff --git a/docs/administration/storing_remote_media.md b/docs/administration/storing_remote_media.md new file mode 100644 index 000000000..7edda2753 --- /dev/null +++ b/docs/administration/storing_remote_media.md @@ -0,0 +1,36 @@ +# Storing Remote Media + +Pleroma does not store remote/federated media by default. The best way to achieve this is to change Nginx to keep its reverse proxy cache +forever and to activate the `MediaProxyWarmingPolicy` MRF policy in Pleroma which will automatically fetch all media through the proxy +as soon as the post is received by your instance. + +## Nginx + +We should be using `proxy_store` here I think??? + +``` + location ~ ^/(media|proxy) { + proxy_cache pleroma_media_cache; + slice 1m; + proxy_cache_key $host$uri$is_args$args$slice_range; + proxy_set_header Range $slice_range; + proxy_http_version 1.1; + proxy_cache_valid 200 206 301 304 1h; + proxy_cache_lock on; + proxy_ignore_client_abort on; + proxy_buffering on; + chunked_transfer_encoding on; + proxy_ignore_headers Cache-Control; + proxy_hide_header Cache-Control; + proxy_pass http://127.0.0.1:4000; + } +``` + +## Pleroma + +Add to your `prod.secret.exs`: + +``` +config :pleroma, :instance, + rewrite_policy: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy] +``` From a1ad8dc34993445033595c8f52e0ee1815e5567d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Oct 2019 14:07:59 -0500 Subject: [PATCH 002/337] Leverage nginx proxy cache to store items with a 1 year TTL with no size limit. It does not purge items when they expire, but will only update them if the origin's copy has changed for some reason. If origin is offline/unavailable or gone forever it will still serve the cached copies. --- docs/administration/storing_remote_media.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/administration/storing_remote_media.md b/docs/administration/storing_remote_media.md index 7edda2753..0abb85a77 100644 --- a/docs/administration/storing_remote_media.md +++ b/docs/administration/storing_remote_media.md @@ -6,22 +6,25 @@ as soon as the post is received by your instance. ## Nginx -We should be using `proxy_store` here I think??? - ``` +proxy_cache_path /long/term/storage/path/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cache:10m + inactive=1y use_temp_path=off; + location ~ ^/(media|proxy) { proxy_cache pleroma_media_cache; slice 1m; proxy_cache_key $host$uri$is_args$args$slice_range; proxy_set_header Range $slice_range; proxy_http_version 1.1; - proxy_cache_valid 200 206 301 304 1h; + proxy_cache_valid 206 301 302 304 1h; + proxy_cache_valid 200 1y; proxy_cache_lock on; + proxy_cache_use_stale error timeout invalid_header updating; proxy_ignore_client_abort on; proxy_buffering on; chunked_transfer_encoding on; - proxy_ignore_headers Cache-Control; - proxy_hide_header Cache-Control; + proxy_ignore_headers Cache-Control Expires; + proxy_hide_header Cache-Control Expires; proxy_pass http://127.0.0.1:4000; } ``` From b9d164fb89af65c2aef83c2867c937ea39a9e995 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Oct 2019 14:12:01 -0500 Subject: [PATCH 003/337] Formatting --- docs/administration/storing_remote_media.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/administration/storing_remote_media.md b/docs/administration/storing_remote_media.md index 0abb85a77..74d333342 100644 --- a/docs/administration/storing_remote_media.md +++ b/docs/administration/storing_remote_media.md @@ -7,8 +7,8 @@ as soon as the post is received by your instance. ## Nginx ``` -proxy_cache_path /long/term/storage/path/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cache:10m - inactive=1y use_temp_path=off; + proxy_cache_path /long/term/storage/path/pleroma-media-cache levels=1:2 + keys_zone=pleroma_media_cache:10m inactive=1y use_temp_path=off; location ~ ^/(media|proxy) { proxy_cache pleroma_media_cache; From 47a551837ade9b5c5b7291c83bc3e787e9c6a17d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Oct 2019 15:13:42 -0500 Subject: [PATCH 004/337] Remove proxy_cache_lock suggestion --- docs/administration/storing_remote_media.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/administration/storing_remote_media.md b/docs/administration/storing_remote_media.md index 74d333342..619300e7e 100644 --- a/docs/administration/storing_remote_media.md +++ b/docs/administration/storing_remote_media.md @@ -18,7 +18,6 @@ as soon as the post is received by your instance. proxy_http_version 1.1; proxy_cache_valid 206 301 302 304 1h; proxy_cache_valid 200 1y; - proxy_cache_lock on; proxy_cache_use_stale error timeout invalid_header updating; proxy_ignore_client_abort on; proxy_buffering on; From 752d0c683357277f5926b7b7011b3f945a7610d1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Oct 2019 15:14:04 -0500 Subject: [PATCH 005/337] Relocate to configuration subdir --- docs/{administration => configuration}/storing_remote_media.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{administration => configuration}/storing_remote_media.md (100%) diff --git a/docs/administration/storing_remote_media.md b/docs/configuration/storing_remote_media.md similarity index 100% rename from docs/administration/storing_remote_media.md rename to docs/configuration/storing_remote_media.md From effb4a3d48462060e31db23bfcfd3e7c989d3141 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 21 Sep 2019 03:15:09 +0200 Subject: [PATCH 006/337] init.d/pleroma: Add option to attach an elixir console --- installation/init.d/pleroma | 48 +++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/installation/init.d/pleroma b/installation/init.d/pleroma index ed50bb551..384536f7e 100755 --- a/installation/init.d/pleroma +++ b/installation/init.d/pleroma @@ -1,21 +1,45 @@ #!/sbin/openrc-run - -# Requires OpenRC >= 0.35 -directory=/opt/pleroma - -command=/usr/bin/mix -command_args="phx.server" +supervisor=supervise-daemon command_user=pleroma:pleroma command_background=1 - -export PORT=4000 -export MIX_ENV=prod - # Ask process to terminate within 30 seconds, otherwise kill it retry="SIGTERM/30/SIGKILL/5" - pidfile="/var/run/pleroma.pid" +directory=/opt/pleroma +healthcheck_delay=60 +healthcheck_timer=30 + +: ${pleroma_port:-4000} + +# Needs OpenRC >= 0.42 +#respawn_max=0 +#respawn_delay=5 + +# put pleroma_console=YES in /etc/conf.d/pleroma if you want to be able to +# connect to pleroma via an elixir console +if yesno "${pleroma_console}"; then + command=elixir + command_args="--name pleroma@127.0.0.1 --erl '-kernel inet_dist_listen_min 9001 inet_dist_listen_max 9001 inet_dist_use_interface {127,0,0,1}' -S mix phx.server" + + start_post() { + einfo "You can get a console by using this command as pleroma's user:" + einfo "iex --name console@127.0.0.1 --remsh pleroma@127.0.0.1" + } +else + command=/usr/bin/mix + command_args="phx.server" +fi + +export MIX_ENV=prod depend() { - need nginx postgresql + need nginx postgresql +} + +healthcheck() { + # put pleroma_health=YES in /etc/conf.d/pleroma if you want healthchecking + # and make sure you have curl installed + yesno "$pleroma_health" || return 0 + + curl -q "localhost:${pleroma_port}/api/pleroma/healthcheck" } From 4b3b1fec4e57bd07ac75700bf34cd188ce43b545 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 15 Apr 2020 21:19:43 +0300 Subject: [PATCH 007/337] added an endpoint for getting unread notification count --- CHANGELOG.md | 1 + docs/API/differences_in_mastoapi_responses.md | 17 +++++-- lib/pleroma/marker.ex | 45 ++++++++++++++++- lib/pleroma/notification.ex | 47 ++++++++++++----- .../web/mastodon_api/views/marker_view.ex | 5 +- mix.lock | 50 ++++++++++--------- .../20200415181818_update_markers.exs | 40 +++++++++++++++ test/marker_test.exs | 29 ++++++++++- test/notification_test.exs | 13 +++++ .../controllers/marker_controller_test.exs | 10 ++-- .../mastodon_api/views/marker_view_test.exs | 8 +-- 11 files changed, 214 insertions(+), 51 deletions(-) create mode 100644 priv/repo/migrations/20200415181818_update_markers.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b235f6d..3f7fc1802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: `pleroma.thread_muted` to the Status entity - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload. +- Mastodon API: Add `pleroma.unread_count` to the Marker entity - Admin API: Render whole status in grouped reports - Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise). - Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try. diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 1059155cf..0a7520f9e 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -185,8 +185,15 @@ Post here request with `grant_type=refresh_token` to obtain new access token. Re Has theses additional parameters (which are the same as in Pleroma-API): -- `fullname`: optional -- `bio`: optional -- `captcha_solution`: optional, contains provider-specific captcha solution, -- `captcha_token`: optional, contains provider-specific captcha token -- `token`: invite token required when the registrations aren't public. + `fullname`: optional + `bio`: optional + `captcha_solution`: optional, contains provider-specific captcha solution, + `captcha_token`: optional, contains provider-specific captcha token + `token`: invite token required when the registrations aren't public. + + +## Markers + +Has these additional fields under the `pleroma` object: + +- `unread_count`: contains number unread notifications diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 443927392..4d82860f5 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -9,24 +9,34 @@ defmodule Pleroma.Marker do import Ecto.Query alias Ecto.Multi + alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.User + alias __MODULE__ @timelines ["notifications"] + @type t :: %__MODULE__{} schema "markers" do field(:last_read_id, :string, default: "") field(:timeline, :string, default: "") field(:lock_version, :integer, default: 0) + field(:unread_count, :integer, default: 0, virtual: true) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() end + @doc "Gets markers by user and timeline." + @spec get_markers(User.t(), list(String)) :: list(t()) def get_markers(user, timelines \\ []) do - Repo.all(get_query(user, timelines)) + user + |> get_query(timelines) + |> unread_count_query() + |> Repo.all() end + @spec upsert(User.t(), map()) :: {:ok | :error, any()} def upsert(%User{} = user, attrs) do attrs |> Map.take(@timelines) @@ -45,6 +55,27 @@ def upsert(%User{} = user, attrs) do |> Repo.transaction() end + @spec multi_set_last_read_id(Multi.t(), User.t(), String.t()) :: Multi.t() + def multi_set_last_read_id(multi, %User{} = user, "notifications") do + multi + |> Multi.run(:counters, fn _repo, _changes -> + {:ok, %{last_read_id: Repo.one(Notification.last_read_query(user))}} + end) + |> Multi.insert( + :marker, + fn %{counters: attrs} -> + %Marker{timeline: "notifications", user_id: user.id} + |> struct(attrs) + |> Ecto.Changeset.change() + end, + returning: true, + on_conflict: {:replace, [:last_read_id]}, + conflict_target: [:user_id, :timeline] + ) + end + + def multi_set_last_read_id(multi, _, _), do: multi + defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do {:ok, marker} -> %__MODULE__{marker | user: user} @@ -71,4 +102,16 @@ defp get_query(user, timelines) do |> by_user_id(user.id) |> by_timeline(timelines) end + + defp unread_count_query(query) do + from( + q in query, + left_join: n in "notifications", + on: n.user_id == q.user_id and n.seen == false, + group_by: [:id], + select_merge: %{ + unread_count: fragment("count(?)", n.id) + } + ) + end end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 04ee510b9..3084bac3b 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -5,7 +5,9 @@ defmodule Pleroma.Notification do use Ecto.Schema + alias Ecto.Multi alias Pleroma.Activity + alias Pleroma.Marker alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination @@ -38,6 +40,17 @@ def changeset(%Notification{} = notification, attrs) do |> cast(attrs, [:seen]) end + @spec last_read_query(User.t()) :: Ecto.Queryable.t() + def last_read_query(user) do + from(q in Pleroma.Notification, + where: q.user_id == ^user.id, + where: q.seen == true, + select: type(q.id, :string), + limit: 1, + order_by: [desc: :id] + ) + end + defp for_user_query_ap_id_opts(user, opts) do ap_id_relationships = [:block] ++ @@ -186,25 +199,23 @@ def for_user_since(user, date) do |> Repo.all() end - def set_read_up_to(%{id: user_id} = _user, id) do + def set_read_up_to(%{id: user_id} = user, id) do query = from( n in Notification, where: n.user_id == ^user_id, where: n.id <= ^id, where: n.seen == false, - update: [ - set: [ - seen: true, - updated_at: ^NaiveDateTime.utc_now() - ] - ], # Ideally we would preload object and activities here # but Ecto does not support preloads in update_all select: n.id ) - {_, notification_ids} = Repo.update_all(query, []) + {:ok, %{ids: {_, notification_ids}}} = + Multi.new() + |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()]) + |> Marker.multi_set_last_read_id(user, "notifications") + |> Repo.transaction() Notification |> where([n], n.id in ^notification_ids) @@ -221,11 +232,18 @@ def set_read_up_to(%{id: user_id} = _user, id) do |> Repo.all() end + @spec read_one(User.t(), String.t()) :: + {:ok, Notification.t()} | {:error, Ecto.Changeset.t()} | nil def read_one(%User{} = user, notification_id) do with {:ok, %Notification{} = notification} <- get(user, notification_id) do - notification - |> changeset(%{seen: true}) - |> Repo.update() + Multi.new() + |> Multi.update(:update, changeset(notification, %{seen: true})) + |> Marker.multi_set_last_read_id(user, "notifications") + |> Repo.transaction() + |> case do + {:ok, %{update: notification}} -> {:ok, notification} + {:error, :update, changeset, _} -> {:error, changeset} + end end end @@ -307,8 +325,11 @@ defp do_create_notifications(%Activity{} = activity) do # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do unless skip?(activity, user) do - notification = %Notification{user_id: user.id, activity: activity} - {:ok, notification} = Repo.insert(notification) + {:ok, %{notification: notification}} = + Multi.new() + |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity}) + |> Marker.multi_set_last_read_id(user, "notifications") + |> Repo.transaction() if do_send do Streamer.stream(["user", "user:notification"], notification) diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 985368fe5..415dae93b 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -10,7 +10,10 @@ def render("markers.json", %{markers: markers}) do Map.put_new(acc, m.timeline, %{ last_read_id: m.last_read_id, version: m.lock_version, - updated_at: NaiveDateTime.to_iso8601(m.updated_at) + updated_at: NaiveDateTime.to_iso8601(m.updated_at), + pleroma: %{ + unread_count: m.unread_count + } }) end) end diff --git a/mix.lock b/mix.lock index 2b9c54548..38adc45e3 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,8 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, + "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, @@ -19,47 +19,47 @@ "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, - "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, + "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "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.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"}, + "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, + "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, "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"}, + "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "f3b4b184dcdd5f356b7c26c6cd72ab0918ba9dfb4061ccfaf519e562942af87b"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, - "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, + "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, + "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, + "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"}, + "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm", "f7d97341e536f95b96eef2988d6d4230f7262cf239cda0e2e63123ee0b717222"}, "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, - "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, + "httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "89149056039084024a284cd703b2d1900d584958dba432132cb21ef35aed7487"}, "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.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"}, + "joken": {:hex, :joken, "2.1.0", "bf21a73105d82649f617c5e59a7f8919aa47013d2519ebcc39d998d8d12adda9", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "eb02df7d5526df13063397e051b926b7006d5986d66f399eefc474f560cdad6a"}, + "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm", "1feaf05ee886815ad047cad7ede17d6910710986148ae09cf73eee2989717b81"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, @@ -71,35 +71,37 @@ "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, "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"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "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"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, - "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, + "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [: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", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fe15d9fee5b82f5e64800502011ffe530650d42e1710ae9b14bc4c9be38bf303"}, + "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b01b3d6d39731ab18aa548d928b5796166d2500755f553725cfe967bafba7d9"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6cd8ddd1bd1fbfa54d3fc61d4719c2057dae67615395d58d40437a919a46f132"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, - "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, + "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "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"}, + "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm", "6de553ba9ac0668d3728b699d5065543f3e40c854154017461ee8c09038752da"}, "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", "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"}, + "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm", "769ddfabd0d2a16f3f9c17eb7509951e0ca4f68363fb26f2ee51a8ec4a49881a"}, diff --git a/priv/repo/migrations/20200415181818_update_markers.exs b/priv/repo/migrations/20200415181818_update_markers.exs new file mode 100644 index 000000000..976363565 --- /dev/null +++ b/priv/repo/migrations/20200415181818_update_markers.exs @@ -0,0 +1,40 @@ +defmodule Pleroma.Repo.Migrations.UpdateMarkers do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Repo + + def up do + update_markers() + end + + def down do + :ok + end + + defp update_markers do + now = NaiveDateTime.utc_now() + + markers_attrs = + from(q in "notifications", + select: %{ + timeline: "notifications", + user_id: q.user_id, + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + }, + group_by: [q.user_id] + ) + |> Repo.all() + |> Enum.map(fn %{last_read_id: last_read_id} = attrs -> + attrs + |> Map.put(:last_read_id, last_read_id || "") + |> Map.put_new(:inserted_at, now) + |> Map.put_new(:updated_at, now) + end) + + Repo.insert_all("markers", markers_attrs, + on_conflict: {:replace, [:last_read_id]}, + conflict_target: [:user_id, :timeline] + ) + end +end diff --git a/test/marker_test.exs b/test/marker_test.exs index c80ae16b6..5b6d0b4a4 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -8,12 +8,39 @@ defmodule Pleroma.MarkerTest do import Pleroma.Factory + describe "multi_set_unread_count/3" do + test "returns multi" do + user = insert(:user) + + assert %Ecto.Multi{ + operations: [marker: {:run, _}, counters: {:run, _}] + } = + Marker.multi_set_last_read_id( + Ecto.Multi.new(), + user, + "notifications" + ) + end + + test "return empty multi" do + user = insert(:user) + multi = Ecto.Multi.new() + assert Marker.multi_set_last_read_id(multi, user, "home") == multi + end + end + describe "get_markers/2" do test "returns user markers" do user = insert(:user) marker = insert(:marker, user: user) + insert(:notification, user: user) + insert(:notification, user: user) insert(:marker, timeline: "home", user: user) - assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)] + + assert Marker.get_markers( + user, + ["notifications"] + ) == [%Marker{refresh_record(marker) | unread_count: 2}] end end diff --git a/test/notification_test.exs b/test/notification_test.exs index 837a9dacd..f78a47af6 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -45,6 +45,9 @@ test "notifies someone when they are directly addressed" do assert notified_ids == [other_user.id, third_user.id] assert notification.activity_id == activity.id assert other_notification.activity_id == activity.id + + assert [%Pleroma.Marker{unread_count: 2}] = + Pleroma.Marker.get_markers(other_user, ["notifications"]) end test "it creates a notification for subscribed users" do @@ -410,6 +413,16 @@ test "it sets all notifications as read up to a specified notification ID" do assert n1.seen == true assert n2.seen == true assert n3.seen == false + + assert %Pleroma.Marker{} = + m = + Pleroma.Repo.get_by( + Pleroma.Marker, + user_id: other_user.id, + timeline: "notifications" + ) + + assert m.last_read_id == to_string(n2.id) end end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 919f295bd..7280abd10 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do test "gets markers with correct scopes", %{conn: conn} do user = insert(:user) token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) + insert_list(7, :notification, user: user) {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( @@ -29,7 +30,8 @@ test "gets markers with correct scopes", %{conn: conn} do "notifications" => %{ "last_read_id" => "69420", "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0 + "version" => 0, + "pleroma" => %{"unread_count" => 7} } } end @@ -70,7 +72,8 @@ test "creates a marker with correct scopes", %{conn: conn} do "notifications" => %{ "last_read_id" => "69420", "updated_at" => _, - "version" => 0 + "version" => 0, + "pleroma" => %{"unread_count" => 0} } } = response end @@ -99,7 +102,8 @@ test "updates exist marker", %{conn: conn} do "notifications" => %{ "last_read_id" => "69888", "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0 + "version" => 0, + "pleroma" => %{"unread_count" => 0} } } end diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs index 893cf8857..48a0a6d33 100644 --- a/test/web/mastodon_api/views/marker_view_test.exs +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -8,19 +8,21 @@ defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do import Pleroma.Factory test "returns markers" do - marker1 = insert(:marker, timeline: "notifications", last_read_id: "17") + marker1 = insert(:marker, timeline: "notifications", last_read_id: "17", unread_count: 5) marker2 = insert(:marker, timeline: "home", last_read_id: "42") assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ "home" => %{ last_read_id: "42", updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), - version: 0 + version: 0, + pleroma: %{unread_count: 0} }, "notifications" => %{ last_read_id: "17", updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), - version: 0 + version: 0, + pleroma: %{unread_count: 5} } } end From 66acfa6882152de9f7b5181e708de02bb97d42a8 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 27 Apr 2020 10:28:05 +0300 Subject: [PATCH 008/337] descriptions that module names are shortened --- config/description.exs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/config/description.exs b/config/description.exs index 7fac1e561..62e4cda7a 100644 --- a/config/description.exs +++ b/config/description.exs @@ -28,7 +28,8 @@ %{ key: :filters, type: {:list, :module}, - description: "List of filter modules for uploads", + description: + "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom MRF module you need to use full name.", suggestions: Generator.list_modules_in_dir( "lib/pleroma/upload/filter", @@ -681,7 +682,8 @@ %{ key: :federation_publisher_modules, type: {:list, :module}, - description: "List of modules for federation publishing", + description: + "List of modules for federation publishing. Module names are shortened (removed leading `Pleroma.Web.` part), but on adding custom MRF module you need to use full name.", suggestions: [ Pleroma.Web.ActivityPub.Publisher ] @@ -694,7 +696,8 @@ %{ key: :rewrite_policy, type: [:module, {:list, :module}], - description: "A list of MRF policies enabled", + description: + "A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom MRF module you need to use full name.", suggestions: Generator.list_modules_in_dir( "lib/pleroma/web/activity_pub/mrf", @@ -1975,7 +1978,8 @@ %{ key: :parsers, type: {:list, :module}, - description: "List of Rich Media parsers.", + description: + "List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom MRF module you need to use full name.", suggestions: [ Pleroma.Web.RichMedia.Parsers.MetaTagsParser, Pleroma.Web.RichMedia.Parsers.OEmbed, @@ -1987,7 +1991,8 @@ key: :ttl_setters, label: "TTL setters", type: {:list, :module}, - description: "List of rich media TTL setters.", + description: + "List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom MRF module you need to use full name.", suggestions: [ Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl ] @@ -2674,6 +2679,8 @@ %{ key: :scrub_policy, type: {:list, :module}, + description: + "Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom MRF module you need to use full name.", suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default] } ] From 270c3fe446a374202b6d64ce487f7df29ecb1c14 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 28 Apr 2020 06:45:59 +0300 Subject: [PATCH 009/337] fix markdown format --- docs/API/differences_in_mastoapi_responses.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 0a7520f9e..a56a74064 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -185,11 +185,11 @@ Post here request with `grant_type=refresh_token` to obtain new access token. Re Has theses additional parameters (which are the same as in Pleroma-API): - `fullname`: optional - `bio`: optional - `captcha_solution`: optional, contains provider-specific captcha solution, - `captcha_token`: optional, contains provider-specific captcha token - `token`: invite token required when the registrations aren't public. +- `fullname`: optional +- `bio`: optional +- `captcha_solution`: optional, contains provider-specific captcha solution, +- `captcha_token`: optional, contains provider-specific captcha token +- `token`: invite token required when the registrations aren't public. ## Markers From b8056e69e0a2505fc466dd5742b0986b7c1895ae Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 29 Apr 2020 19:08:08 +0200 Subject: [PATCH 010/337] Object Validator Types: Add Recipients. --- .../object_validators/types/recipients.ex | 34 +++++++++++++++++++ .../types/recipients_test.exs | 27 +++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/object_validators/types/recipients.ex create mode 100644 test/web/activity_pub/object_validators/types/recipients_test.exs diff --git a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex b/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex new file mode 100644 index 000000000..48fe61e1a --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex @@ -0,0 +1,34 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do + use Ecto.Type + + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID + + def type, do: {:array, ObjectID} + + def cast(object) when is_binary(object) do + cast([object]) + end + + def cast(data) when is_list(data) do + data + |> Enum.reduce({:ok, []}, fn element, acc -> + case {acc, ObjectID.cast(element)} do + {:error, _} -> :error + {_, :error} -> :error + {{:ok, list}, {:ok, id}} -> {:ok, [id | list]} + end + end) + end + + def cast(_) do + :error + end + + def dump(data) do + {:ok, data} + end + + def load(data) do + {:ok, data} + end +end diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs new file mode 100644 index 000000000..f278f039b --- /dev/null +++ b/test/web/activity_pub/object_validators/types/recipients_test.exs @@ -0,0 +1,27 @@ +defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients + use Pleroma.DataCase + + test "it asserts that all elements of the list are object ids" do + list = ["https://lain.com/users/lain", "invalid"] + + assert :error == Recipients.cast(list) + end + + test "it works with a list" do + list = ["https://lain.com/users/lain"] + assert {:ok, list} == Recipients.cast(list) + end + + test "it works with a list with whole objects" do + list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}] + resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"] + assert {:ok, resulting_list} == Recipients.cast(list) + end + + test "it turns a single string into a list" do + recipient = "https://lain.com/users/lain" + + assert {:ok, [recipient]} == Recipients.cast(recipient) + end +end From 78c864cbeed8fcdbe80e2842377d4fabc9362f3c Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 29 Apr 2020 19:08:36 +0200 Subject: [PATCH 011/337] LikeValidator: Use Recipients Type. --- .../web/activity_pub/object_validators/like_validator.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 49546ceaa..eeb0da192 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -19,8 +19,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do field(:object, Types.ObjectID) field(:actor, Types.ObjectID) field(:context, :string) - field(:to, {:array, :string}) - field(:cc, {:array, :string}) + field(:to, Types.Recipients) + field(:cc, Types.Recipients) end def cast_and_validate(data) do From 503de4b8df0bfc34008c3c856edc488633290f0e Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 29 Apr 2020 19:09:51 +0200 Subject: [PATCH 012/337] ObjectValidator: Add validation for `Delete`s. --- lib/pleroma/web/activity_pub/builder.ex | 16 +++++ .../web/activity_pub/object_validator.ex | 17 +++++ .../object_validators/common_validations.ex | 20 ++++++ .../object_validators/delete_validator.ex | 64 ++++++++++++++++++ .../activity_pub/object_validator_test.exs | 67 +++++++++++++++++++ 5 files changed, 184 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/object_validators/delete_validator.ex diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 429a510b8..5cc46c3ea 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -10,6 +10,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()} + def delete(actor, object_id) do + object = Object.normalize(object_id) + + to = (object.data["to"] || []) ++ (object.data["cc"] || []) + + {:ok, + %{ + "id" => Utils.generate_activity_id(), + "actor" => actor.ap_id, + "object" => object_id, + "to" => to, + "type" => "Delete" + }, []} + end + @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"]) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index dc4bce059..f476c6f72 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,10 +12,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) + def validate(%{"type" => "Delete"} = object, meta) do + with {:ok, object} <- + object + |> DeleteValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + def validate(%{"type" => "Like"} = object, meta) do with {:ok, object} <- object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do @@ -24,6 +35,12 @@ def validate(%{"type" => "Like"} = object, meta) do end end + def stringify_keys(%{__struct__: _} = object) do + object + |> Map.from_struct() + |> stringify_keys + end + def stringify_keys(object) do object |> Map.new(fn {key, val} -> {to_string(key), val} 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 index b479c3918..e115d9526 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -8,6 +8,26 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do alias Pleroma.Object alias Pleroma.User + def validate_recipients_presence(cng, fields \\ [:to, :cc]) do + non_empty = + fields + |> Enum.map(fn field -> get_field(cng, field) end) + |> Enum.any?(fn + [] -> false + _ -> true + end) + + if non_empty do + cng + else + fields + |> Enum.reduce(cng, fn field, cng -> + cng + |> add_error(field, "no recipients in any field") + end) + end + end + def validate_actor_presence(cng, field_name \\ :actor) do cng |> validate_change(field_name, fn field_name, actor -> diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex new file mode 100644 index 000000000..8dd5c19ad --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do + use Ecto.Schema + + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + + 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(:actor, Types.ObjectID) + field(:to, Types.Recipients, default: []) + field(:cc, Types.Recipients, default: []) + field(:object, Types.ObjectID) + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, __schema__(:fields)) + end + + def validate_data(cng) do + cng + |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_inclusion(:type, ["Delete"]) + |> validate_same_domain() + |> validate_object_presence() + |> validate_recipients_presence() + end + + def validate_same_domain(cng) do + actor_domain = + cng + |> get_field(:actor) + |> URI.parse() + |> (& &1.host).() + + object_domain = + cng + |> get_field(:object) + |> URI.parse() + |> (& &1.host).() + + if object_domain != actor_domain do + cng + |> add_error(:actor, "is not allowed to delete object") + else + cng + end + end + + def cast_and_validate(data) do + data + |> cast_data + |> validate_data + end +end diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 3c5c3696e..64b9ee1ec 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.Utils @@ -8,6 +9,72 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do import Pleroma.Factory + describe "deletes" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"}) + + {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) + + %{user: user, valid_post_delete: valid_post_delete} + end + + test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do + assert match?({:ok, _, _}, ObjectValidator.validate(valid_post_delete, [])) + end + + test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do + no_id = + valid_post_delete + |> Map.delete("id") + + {:error, cng} = ObjectValidator.validate(no_id, []) + + assert {:id, {"can't be blank", [validation: :required]}} in cng.errors + end + + test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do + missing_object = + valid_post_delete + |> Map.put("object", "http://does.not/exist") + + {:error, cng} = ObjectValidator.validate(missing_object, []) + + assert {:object, {"can't find object", []}} in cng.errors + end + + test "it's invalid if the actor of the object and the actor of delete are from different domains", + %{valid_post_delete: valid_post_delete} do + valid_other_actor = + valid_post_delete + |> Map.put("actor", valid_post_delete["actor"] <> "1") + + assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, [])) + + invalid_other_actor = + valid_post_delete + |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + + {:error, cng} = ObjectValidator.validate(invalid_other_actor, []) + + assert {:actor, {"is not allowed to delete object", []}} in cng.errors + end + + test "it's invalid if all the recipient fields are empty", %{ + valid_post_delete: valid_post_delete + } do + empty_recipients = + valid_post_delete + |> Map.put("to", []) + |> Map.put("cc", []) + + {:error, cng} = ObjectValidator.validate(empty_recipients, []) + + assert {:to, {"no recipients in any field", []}} in cng.errors + assert {:cc, {"no recipients in any field", []}} in cng.errors + end + end + describe "likes" do setup do user = insert(:user) From 64bb72f98a91261158b36e63f6c9634ac9f423a6 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 13:57:47 +0200 Subject: [PATCH 013/337] Typo fix. --- lib/pleroma/web/activity_pub/utils.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2d685ecc0..1a3b0b3c1 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -512,7 +512,7 @@ def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do #### Announce-related helpers @doc """ - Retruns an existing announce activity if the notice has already been announced + Returns an existing announce activity if the notice has already been announced """ @spec get_existing_announce(String.t(), map()) :: Activity.t() | nil def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do From 42ce7c5164326aa577bc7bd18e98c5d0a9d6fea5 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 14:13:08 +0200 Subject: [PATCH 014/337] ObjectValidator: Add actor fetcher. --- lib/pleroma/web/activity_pub/object_validator.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index f476c6f72..016f6e7a2 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -46,8 +46,14 @@ def stringify_keys(object) do |> Map.new(fn {key, val} -> {to_string(key), val} end) end + def fetch_actor(object) do + with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do + User.get_or_fetch_by_ap_id(actor) + end + end + def fetch_actor_and_object(object) do - User.get_or_fetch_by_ap_id(object["actor"]) + fetch_actor(object) Object.normalize(object["object"]) :ok end From bd219ba7e884d694cc1c8747f0b48cd646821222 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 14:14:00 +0200 Subject: [PATCH 015/337] Transmogrifier Tests: Extract deletion tests. --- .../transmogrifier/delete_handling_test.exs | 106 ++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 77 ------------- 2 files changed, 106 insertions(+), 77 deletions(-) create mode 100644 test/web/activity_pub/transmogrifier/delete_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs new file mode 100644 index 000000000..c15de5a95 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Pleroma.Factory + import ExUnit.CaptureLog + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "it works for incoming deletes" do + activity = insert(:note_activity) + deleting_user = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.decode!() + + object = + data["object"] + |> Map.put("id", activity.data["object"]) + + data = + data + |> Map.put("object", object) + |> Map.put("actor", deleting_user.ap_id) + + {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = + Transmogrifier.handle_incoming(data) + + assert id == data["id"] + + # We delete the Create activity because base our timelines on it. + # This should be changed after we unify objects and activities + refute Activity.get_by_id(activity.id) + assert actor == deleting_user.ap_id + + # Objects are replaced by a tombstone object. + object = Object.normalize(activity.data["object"]) + assert object.data["type"] == "Tombstone" + end + + test "it fails for incoming deletes with spoofed origin" do + activity = insert(:note_activity) + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.decode!() + + object = + data["object"] + |> Map.put("id", activity.data["object"]) + + data = + data + |> Map.put("object", object) + + assert capture_log(fn -> + :error = Transmogrifier.handle_incoming(data) + end) =~ + "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" + + assert Activity.get_by_id(activity.id) + end + + @tag capture_log: true + test "it works for incoming user deletes" do + %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + + {:ok, _} = Transmogrifier.handle_incoming(data) + ObanHelpers.perform_all() + + refute User.get_cached_by_ap_id(ap_id) + end + + test "it fails for incoming user deletes with spoofed origin" do + %{ap_id: ap_id} = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + |> Map.put("actor", ap_id) + + assert capture_log(fn -> + assert :error == Transmogrifier.handle_incoming(data) + end) =~ "Object containment failed" + + assert User.get_cached_by_ap_id(ap_id) + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6057e360a..64e56d378 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -822,83 +822,6 @@ test "it works for incoming update activities which lock the account" do assert user.locked == true end - test "it works for incoming deletes" do - activity = insert(:note_activity) - deleting_user = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - |> Map.put("actor", deleting_user.ap_id) - - {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = - Transmogrifier.handle_incoming(data) - - assert id == data["id"] - refute Activity.get_by_id(activity.id) - assert actor == deleting_user.ap_id - end - - test "it fails for incoming deletes with spoofed origin" do - activity = insert(:note_activity) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - - assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) - end) =~ - "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" - - assert Activity.get_by_id(activity.id) - end - - @tag capture_log: true - test "it works for incoming user deletes" do - %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - - {:ok, _} = Transmogrifier.handle_incoming(data) - ObanHelpers.perform_all() - - refute User.get_cached_by_ap_id(ap_id) - end - - test "it fails for incoming user deletes with spoofed origin" do - %{ap_id: ap_id} = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - |> Map.put("actor", ap_id) - - assert capture_log(fn -> - assert :error == Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" - - assert User.get_cached_by_ap_id(ap_id) - end - test "it works for incoming unannounces with an existing notice" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) From db184a8eb495865334f47a24f8c5b1fec65450b6 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 14:37:14 +0200 Subject: [PATCH 016/337] DeleteValidator: Mastodon sends unaddressed deletes. --- .../object_validators/delete_validator.ex | 1 - test/web/activity_pub/object_validator_test.exs | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 8dd5c19ad..0eb31451c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -32,7 +32,6 @@ def validate_data(cng) do |> validate_inclusion(:type, ["Delete"]) |> validate_same_domain() |> validate_object_presence() - |> validate_recipients_presence() end def validate_same_domain(cng) do diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 64b9ee1ec..ab26d3501 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -59,20 +59,6 @@ test "it's invalid if the actor of the object and the actor of delete are from d assert {:actor, {"is not allowed to delete object", []}} in cng.errors end - - test "it's invalid if all the recipient fields are empty", %{ - valid_post_delete: valid_post_delete - } do - empty_recipients = - valid_post_delete - |> Map.put("to", []) - |> Map.put("cc", []) - - {:error, cng} = ObjectValidator.validate(empty_recipients, []) - - assert {:to, {"no recipients in any field", []}} in cng.errors - assert {:cc, {"no recipients in any field", []}} in cng.errors - end end describe "likes" do From 4dc5302f455e56d3c2cb669e8a70f52457690a86 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 15:26:23 +0200 Subject: [PATCH 017/337] Transmogrifier: Handle incoming deletes for non-user objects. --- .../web/activity_pub/object_validator.ex | 3 +- lib/pleroma/web/activity_pub/side_effects.ex | 12 ++++++++ .../web/activity_pub/transmogrifier.ex | 29 ++----------------- test/web/activity_pub/side_effects_test.exs | 23 +++++++++++++++ .../transmogrifier/delete_handling_test.exs | 6 ++-- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 016f6e7a2..32f606917 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -11,8 +11,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.Types @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5981e7545..93698a834 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -28,6 +28,18 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do result end + # Tasks this handles: + # - Delete create activity + # - Replace object with Tombstone + # - Set up notification + def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do + with %Object{} = deleted_object <- Object.normalize(deleted_object), + {:ok, _, _} <- Object.delete(deleted_object) do + Notification.create_notifications(object) + {:ok, object, meta} + end + end + # Nothing to do def handle(object, meta) do {:ok, object, meta} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 09119137b..855aab8d4 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -729,36 +729,13 @@ def handle_incoming( end end - # TODO: We presently assume that any actor on the same origin domain as the object being - # deleted has the rights to delete that object. A better way to validate whether or not - # the object should be deleted is to refetch the object URI, which should return either - # an error or a tombstone. This would allow us to verify that a deletion actually took - # place. def handle_incoming( - %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data, + %{"type" => "Delete"} = data, _options ) do - object_id = Utils.get_ap_id(object_id) - - 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 <- Containment.contain_origin(actor.ap_id, object.data), - {:ok, activity} <- - ActivityPub.delete(object, local: false, activity_id: id, actor: actor.ap_id) do + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), + {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} - else - nil -> - case User.get_cached_by_ap_id(object_id) do - %User{ap_id: ^actor} = user -> - User.delete(user) - - nil -> - :error - end - - _e -> - :error end end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 0b6b55156..eec9488e7 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do use Pleroma.DataCase + alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo @@ -15,6 +16,28 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do import Pleroma.Factory + describe "delete objects" do + setup do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) + object = Object.normalize(post) + {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) + {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) + %{user: user, delete: delete, post: post, object: object} + end + + test "it handles object deletions", %{delete: delete, post: post, object: object} do + # In object deletions, the object is replaced by a tombstone and the + # create activity is deleted + + {:ok, _delete, _} = SideEffects.handle(delete) + + object = Object.get_by_id(object.id) + assert object.data["type"] == "Tombstone" + refute Activity.get_by_id(post.id) + end + end + describe "like objects" do setup do poster = insert(:user) diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs index c15de5a95..64c908a05 100644 --- a/test/web/activity_pub/transmogrifier/delete_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -68,7 +68,7 @@ test "it fails for incoming deletes with spoofed origin" do |> Map.put("object", object) assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) + {:error, _} = Transmogrifier.handle_incoming(data) end) =~ "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" @@ -97,9 +97,7 @@ test "it fails for incoming user deletes with spoofed origin" do |> Poison.decode!() |> Map.put("actor", ap_id) - assert capture_log(fn -> - assert :error == Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" + assert match?({:error, _}, Transmogrifier.handle_incoming(data)) assert User.get_cached_by_ap_id(ap_id) end From 1fb383f368b861d7aea77770ba7be6e3dfe3468e Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 15:42:30 +0200 Subject: [PATCH 018/337] DeleteValidator: Deleting a user is valid. --- lib/pleroma/web/activity_pub/builder.ex | 15 +++++++++++++-- .../object_validators/common_validations.ex | 11 +++++++++++ .../object_validators/delete_validator.ex | 2 +- test/web/activity_pub/object_validator_test.exs | 7 ++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 5cc46c3ea..1345a3a3e 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -12,9 +12,20 @@ defmodule Pleroma.Web.ActivityPub.Builder do @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()} def delete(actor, object_id) do - object = Object.normalize(object_id) + object = Object.normalize(object_id, false) - to = (object.data["to"] || []) ++ (object.data["cc"] || []) + user = !object && User.get_cached_by_ap_id(object_id) + + to = + case {object, user} do + {%Object{}, _} -> + # We are deleting an object, address everyone who was originally mentioned + (object.data["to"] || []) ++ (object.data["cc"] || []) + + {_, %User{follower_address: follower_address}} -> + # We are deleting a user, address the followers of that user + [follower_address] + end {:ok, %{ diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index e115d9526..d9a629a34 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -49,4 +49,15 @@ def validate_object_presence(cng, field_name \\ :object) do end end) end + + def validate_object_or_user_presence(cng, field_name \\ :object) do + cng + |> validate_change(field_name, fn field_name, object -> + if Object.get_cached_by_ap_id(object) || User.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/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 0eb31451c..fa1713b50 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -31,7 +31,7 @@ def validate_data(cng) do |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Delete"]) |> validate_same_domain() - |> validate_object_presence() + |> validate_object_or_user_presence() end def validate_same_domain(cng) do diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index ab26d3501..83b21a9bc 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -15,14 +15,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do {:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"}) {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) + {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id) - %{user: user, valid_post_delete: valid_post_delete} + %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete} end test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do assert match?({:ok, _, _}, ObjectValidator.validate(valid_post_delete, [])) end + test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do + assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, [])) + end + test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do no_id = valid_post_delete From 417eed4a2b10b0a1fd916839ddb03d0345966123 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 15:57:27 +0200 Subject: [PATCH 019/337] SideEffects: Handle deletions. --- lib/pleroma/web/activity_pub/side_effects.ex | 22 ++++++++++++++++++-- test/web/activity_pub/side_effects_test.exs | 14 ++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 93698a834..ac1d4c222 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do """ alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils def handle(object, meta \\ []) @@ -33,10 +34,27 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do # - Replace object with Tombstone # - Set up notification def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do - with %Object{} = deleted_object <- Object.normalize(deleted_object), - {:ok, _, _} <- Object.delete(deleted_object) do + deleted_object = + Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object) + + result = + case deleted_object do + %Object{} -> + with {:ok, _, _} <- Object.delete(deleted_object) do + :ok + end + + %User{} -> + with {:ok, _} <- User.delete(deleted_object) do + :ok + end + end + + if result == :ok do Notification.create_notifications(object) {:ok, object, meta} + else + {:error, result} end end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index eec9488e7..b3d0addc7 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -3,12 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.SideEffectsTest do + use Oban.Testing, repo: Pleroma.Repo use Pleroma.DataCase alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Tests.ObanHelpers alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.SideEffects @@ -22,8 +25,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) object = Object.normalize(post) {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) + {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) - %{user: user, delete: delete, post: post, object: object} + {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) + %{user: user, delete: delete, post: post, object: object, delete_user: delete_user} end test "it handles object deletions", %{delete: delete, post: post, object: object} do @@ -36,6 +41,13 @@ test "it handles object deletions", %{delete: delete, post: post, object: object assert object.data["type"] == "Tombstone" refute Activity.get_by_id(post.id) end + + test "it handles user deletions", %{delete_user: delete, user: user} do + {:ok, _delete, _} = SideEffects.handle(delete) + ObanHelpers.perform_all() + + refute User.get_cached_by_ap_id(user.ap_id) + end end describe "like objects" do From c9bfa51ea9c0048ffa4c0d3e28c196da2f38e384 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 15:58:37 +0200 Subject: [PATCH 020/337] Credo fixes. --- test/web/activity_pub/side_effects_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index b3d0addc7..fffe0ca38 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -10,8 +10,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo - alias Pleroma.User alias Pleroma.Tests.ObanHelpers + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.SideEffects From fdd8e7f27697a7128e4e92020cdff6389c999acc Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 16:15:38 +0200 Subject: [PATCH 021/337] CommonAPI: Use common pipeline for deletions. --- lib/pleroma/web/activity_pub/side_effects.ex | 6 ++++-- lib/pleroma/web/common_api/common_api.ex | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index ac1d4c222..ef58fa399 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -30,7 +30,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do end # Tasks this handles: - # - Delete create activity + # - Delete and unpins the create activity # - Replace object with Tombstone # - Set up notification def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do @@ -40,7 +40,9 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, result = case deleted_object do %Object{} -> - with {:ok, _, _} <- Object.delete(deleted_object) do + with {:ok, _, activity} <- Object.delete(deleted_object), + %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do + User.remove_pinnned_activity(user, activity) :ok end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index d1efe0c36..7cb8e47d0 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -77,8 +77,8 @@ def delete(activity_id, user) do {:find_activity, Activity.get_by_id_with_object(activity_id)}, %Object{} = object <- Object.normalize(activity), true <- User.superuser?(user) || user.ap_id == object.data["actor"], - {:ok, _} <- unpin(activity_id, user), - {:ok, delete} <- ActivityPub.delete(object) do + {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), + {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do {:ok, delete} else {:find_activity, _} -> {:error, :not_found} From 14c667219334c492ae0549ad0f1e062085d7d412 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 16:49:41 +0200 Subject: [PATCH 022/337] AP C2S: Use common pipelin for deletes. --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index d625530ec..e68d0763e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -414,7 +414,8 @@ defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do with %Object{} = object <- Object.normalize(params["object"]), true <- user.is_moderator || user.ap_id == object.data["actor"], - {:ok, delete} <- ActivityPub.delete(object) do + {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), + {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do {:ok, delete} else _ -> {:error, dgettext("errors", "Can't delete object")} From 2c4844237f294d27f58737f9694f77b1cfcb10e7 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 30 Apr 2020 18:19:51 +0300 Subject: [PATCH 023/337] Refactoring of :if_func / :unless_func plug options (general availability). Added tests for Pleroma.Web.Plug. --- .../plugs/ensure_authenticated_plug.ex | 17 +--- lib/pleroma/plugs/federating_plug.ex | 3 + .../activity_pub/activity_pub_controller.ex | 2 +- lib/pleroma/web/feed/user_controller.ex | 2 +- lib/pleroma/web/ostatus/ostatus_controller.ex | 2 +- .../web/static_fe/static_fe_controller.ex | 2 +- lib/pleroma/web/web.ex | 10 +- test/plugs/ensure_authenticated_plug_test.exs | 4 +- test/web/plugs/plug_test.exs | 91 +++++++++++++++++++ 9 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 test/web/plugs/plug_test.exs diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 9c8f5597f..9d5176e2b 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -19,22 +19,7 @@ def perform(%{assigns: %{user: %User{}}} = conn, _) do conn end - def perform(conn, options) do - perform = - cond do - options[:if_func] -> options[:if_func].() - options[:unless_func] -> !options[:unless_func].() - true -> true - end - - if perform do - fail(conn) - else - conn - end - end - - def fail(conn) do + def perform(conn, _) do conn |> render_error(:forbidden, "Invalid credentials.") |> halt() diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index 7d947339f..09038f3c6 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -19,6 +19,9 @@ def call(conn, _opts) do def federating?, do: Pleroma.Config.get([:instance, :federating]) + # Definition for the use in :if_func / :unless_func plug options + def federating?(_conn), do: federating?() + defp fail(conn) do conn |> put_status(404) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index d625530ec..a909516be 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -34,7 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do plug( EnsureAuthenticatedPlug, - [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions + [unless_func: &FederatingPlug.federating?/1] when action not in @federating_only_actions ) plug( diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index e27f85929..1b72e23dc 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -27,7 +27,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do with %{halted: false} = conn <- Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, - unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + unless_func: &Pleroma.Web.FederatingPlug.federating?/1 ) do ActivityPubController.call(conn, :user) end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 6fd3cfce5..6971cd9f8 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.Router plug(Pleroma.Plugs.EnsureAuthenticatedPlug, - unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + unless_func: &Pleroma.Web.FederatingPlug.federating?/1 ) plug( diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 7a35238d7..c3efb6651 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do plug(:assign_id) plug(Pleroma.Plugs.EnsureAuthenticatedPlug, - unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + unless_func: &Pleroma.Web.FederatingPlug.federating?/1 ) @page_keys ["max_id", "min_id", "limit", "since_id", "order"] diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 08e42a7e5..4f9281851 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -200,11 +200,17 @@ def skip_plug(conn) do @impl Plug @doc """ - If marked as skipped, returns `conn`, otherwise calls `perform/2`. + Before-plug hook that + * ensures the plug is not skipped + * processes `:if_func` / `:unless_func` functional pre-run conditions + * adds plug to the list of called plugs and calls `perform/2` if checks are passed + Note: multiple invocations of the same plug (with different or same options) are allowed. """ def call(%Plug.Conn{} = conn, options) do - if PlugHelper.plug_skipped?(conn, __MODULE__) do + if PlugHelper.plug_skipped?(conn, __MODULE__) || + (options[:if_func] && !options[:if_func].(conn)) || + (options[:unless_func] && options[:unless_func].(conn)) do conn else conn = diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs index 689fe757f..4e6142aab 100644 --- a/test/plugs/ensure_authenticated_plug_test.exs +++ b/test/plugs/ensure_authenticated_plug_test.exs @@ -27,8 +27,8 @@ test "it continues if a user is assigned", %{conn: conn} do describe "with :if_func / :unless_func options" do setup do %{ - true_fn: fn -> true end, - false_fn: fn -> false end + true_fn: fn _conn -> true end, + false_fn: fn _conn -> false end } end diff --git a/test/web/plugs/plug_test.exs b/test/web/plugs/plug_test.exs new file mode 100644 index 000000000..943e484e7 --- /dev/null +++ b/test/web/plugs/plug_test.exs @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PlugTest do + @moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`" + + alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug + alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug + alias Pleroma.Plugs.PlugHelper + + import Mock + + use Pleroma.Web.ConnCase + + describe "when plug is skipped, " do + setup_with_mocks( + [ + {ExpectPublicOrAuthenticatedCheckPlug, [:passthrough], []} + ], + %{conn: conn} + ) do + conn = ExpectPublicOrAuthenticatedCheckPlug.skip_plug(conn) + %{conn: conn} + end + + test "it neither adds plug to called plugs list nor calls `perform/2`, " <> + "regardless of :if_func / :unless_func options", + %{conn: conn} do + for opts <- [%{}, %{if_func: fn _ -> true end}, %{unless_func: fn _ -> false end}] do + ret_conn = ExpectPublicOrAuthenticatedCheckPlug.call(conn, opts) + + refute called(ExpectPublicOrAuthenticatedCheckPlug.perform(:_, :_)) + refute PlugHelper.plug_called?(ret_conn, ExpectPublicOrAuthenticatedCheckPlug) + end + end + end + + describe "when plug is NOT skipped, " do + setup_with_mocks([{ExpectAuthenticatedCheckPlug, [:passthrough], []}]) do + :ok + end + + test "with no pre-run checks, adds plug to called plugs list and calls `perform/2`", %{ + conn: conn + } do + ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{}) + + assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) + assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + end + + test "when :if_func option is given, calls the plug only if provided function evals tru-ish", + %{conn: conn} do + ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> false end}) + + refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_)) + refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + + ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> true end}) + + assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) + assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + end + + test "if :unless_func option is given, calls the plug only if provided function evals falsy", + %{conn: conn} do + ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> true end}) + + refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_)) + refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + + ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> false end}) + + assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) + assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + end + + test "allows a plug to be called multiple times (even if it's in called plugs list)", %{ + conn: conn + } do + conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value1}) + assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value1})) + + assert PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) + + conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value2}) + assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value2})) + end + end +end From 143353432a562c49f4432e74a549321c5b43650d Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 17:52:29 +0200 Subject: [PATCH 024/337] StreamerTest: Separate deletion test. --- test/web/streamer/streamer_test.exs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 8b8d8af6c..3c0f240f5 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -210,6 +210,12 @@ test "it sends to public" do Worker.push_to_socket(topics, "public", activity) Task.await(task) + end + + test "works for deletions" do + user = insert(:user) + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) task = Task.async(fn -> From 4500fdc04c528331f7289745dc08a34ce18d4da7 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 17:53:02 +0200 Subject: [PATCH 025/337] DeleteValidator: Add internal helper field after validation. --- .../object_validators/delete_validator.ex | 16 ++++++++++++++++ test/web/activity_pub/object_validator_test.exs | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index fa1713b50..951cc1414 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do use Ecto.Schema + alias Pleroma.Activity alias Pleroma.Web.ActivityPub.ObjectValidators.Types import Ecto.Changeset @@ -18,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do field(:actor, Types.ObjectID) field(:to, Types.Recipients, default: []) field(:cc, Types.Recipients, default: []) + field(:deleted_activity_id) field(:object, Types.ObjectID) end @@ -26,12 +28,26 @@ def cast_data(data) do |> cast(data, __schema__(:fields)) end + def add_deleted_activity_id(cng) do + object = + cng + |> get_field(:object) + + with %Activity{id: id} <- Activity.get_create_by_object_ap_id(object) do + cng + |> put_change(:deleted_activity_id, id) + else + _ -> cng + end + end + def validate_data(cng) do cng |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Delete"]) |> validate_same_domain() |> validate_object_or_user_presence() + |> add_deleted_activity_id() end def validate_same_domain(cng) do diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 83b21a9bc..9e0589722 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -21,7 +21,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do end test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do - assert match?({:ok, _, _}, ObjectValidator.validate(valid_post_delete, [])) + {:ok, valid_post_delete_u, _} = ObjectValidator.validate(valid_post_delete, []) + + assert valid_post_delete_u["deleted_activity_id"] end test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do From c832d96fc9fc0b93befdf3a7064a8c9236e96d07 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 17:58:09 +0200 Subject: [PATCH 026/337] SideEffects: Stream out deletes. --- lib/pleroma/web/activity_pub/side_effects.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index ef58fa399..d260e0069 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.ActivityPub def handle(object, meta \\ []) @@ -40,9 +41,12 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, result = case deleted_object do %Object{} -> - with {:ok, _, activity} <- Object.delete(deleted_object), + with {:ok, deleted_object, activity} <- Object.delete(deleted_object), %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do User.remove_pinnned_activity(user, activity) + + ActivityPub.stream_out(object) + ActivityPub.stream_out_participations(deleted_object, user) :ok end From 315b773dd9fa185aef75b115efd90ac92113e6c3 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 17:58:31 +0200 Subject: [PATCH 027/337] ObjectValidator: Refactor. --- test/web/activity_pub/object_validator_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 9e0589722..1d3646487 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -21,9 +21,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do end test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do - {:ok, valid_post_delete_u, _} = ObjectValidator.validate(valid_post_delete, []) + {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, []) - assert valid_post_delete_u["deleted_activity_id"] + assert valid_post_delete["deleted_activity_id"] end test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do From 3d0dc58e2e0a84cb46df5339596205f7baceb0a4 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 18:10:36 +0200 Subject: [PATCH 028/337] SideEffectsTest: Test streaming. --- test/web/activity_pub/side_effects_test.exs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index fffe0ca38..f5c57d887 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Web.CommonAPI import Pleroma.Factory + import Mock describe "delete objects" do setup do @@ -33,9 +34,16 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do test "it handles object deletions", %{delete: delete, post: post, object: object} do # In object deletions, the object is replaced by a tombstone and the - # create activity is deleted + # create activity is deleted. - {:ok, _delete, _} = SideEffects.handle(delete) + with_mock Pleroma.Web.ActivityPub.ActivityPub, + stream_out: fn _ -> nil end, + stream_out_participations: fn _, _ -> nil end do + {:ok, delete, _} = SideEffects.handle(delete) + user = User.get_cached_by_ap_id(object.data["actor"]) + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) + end object = Object.get_by_id(object.id) assert object.data["type"] == "Tombstone" From ab60ee17765ee9d7dcb69cbf9c0630b97d4f5a93 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 18:19:39 +0200 Subject: [PATCH 029/337] SideEffects: On deletion, reduce the User note count. --- lib/pleroma/web/activity_pub/side_effects.ex | 2 ++ test/web/activity_pub/side_effects_test.exs | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index d260e0069..4fec3a797 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -34,6 +34,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do # - Delete and unpins the create activity # - Replace object with Tombstone # - Set up notification + # - Reduce the user note count def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object) @@ -45,6 +46,7 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do User.remove_pinnned_activity(user, activity) + {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object) ActivityPub.stream_out(object) ActivityPub.stream_out_participations(deleted_object, user) :ok diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index f5c57d887..06b3400d8 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -32,15 +32,16 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do %{user: user, delete: delete, post: post, object: object, delete_user: delete_user} end - test "it handles object deletions", %{delete: delete, post: post, object: object} do + test "it handles object deletions", %{delete: delete, post: post, object: object, user: user} do # In object deletions, the object is replaced by a tombstone and the # create activity is deleted. - with_mock Pleroma.Web.ActivityPub.ActivityPub, + with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end, stream_out_participations: fn _, _ -> nil end do {:ok, delete, _} = SideEffects.handle(delete) user = User.get_cached_by_ap_id(object.data["actor"]) + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) end @@ -48,6 +49,9 @@ test "it handles object deletions", %{delete: delete, post: post, object: object object = Object.get_by_id(object.id) assert object.data["type"] == "Tombstone" refute Activity.get_by_id(post.id) + + user = User.get_by_id(user.id) + assert user.note_count == 0 end test "it handles user deletions", %{delete_user: delete, user: user} do From 60db58a1c6a2f139960d3db19cba08a496e6ccf4 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 18:38:37 +0200 Subject: [PATCH 030/337] Credo fixes. --- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4fec3a797..cf31de120 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Utils def handle(object, meta \\ []) From 500f5ec14eb02cd1c5a07970a557756b590caab0 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 19:47:13 +0200 Subject: [PATCH 031/337] SideEffects: On deletion, reduce the reply count cache --- lib/pleroma/web/activity_pub/side_effects.ex | 6 ++++++ test/web/activity_pub/side_effects_test.exs | 22 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index cf31de120..39b0f384b 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -35,6 +35,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do # - Replace object with Tombstone # - Set up notification # - Reduce the user note count + # - TODO: Reduce the reply count def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object) @@ -47,6 +48,11 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, User.remove_pinnned_activity(user, activity) {:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object) + + if in_reply_to = deleted_object.data["inReplyTo"] do + Object.decrease_replies_count(in_reply_to) + end + ActivityPub.stream_out(object) ActivityPub.stream_out_participations(deleted_object, user) :ok diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 06b3400d8..ce34eed4c 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -23,19 +23,25 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do describe "delete objects" do setup do user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) + other_user = insert(:user) + + {:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"}) + {:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op}) object = Object.normalize(post) {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) - %{user: user, delete: delete, post: post, object: object, delete_user: delete_user} + %{user: user, delete: delete, post: post, object: object, delete_user: delete_user, op: op} end - test "it handles object deletions", %{delete: delete, post: post, object: object, user: user} do - # In object deletions, the object is replaced by a tombstone and the - # create activity is deleted. - + test "it handles object deletions", %{ + delete: delete, + post: post, + object: object, + user: user, + op: op + } do with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end, stream_out_participations: fn _, _ -> nil end do @@ -52,6 +58,10 @@ test "it handles object deletions", %{delete: delete, post: post, object: object user = User.get_by_id(user.id) assert user.note_count == 0 + + object = Object.normalize(op.data["object"], false) + + assert object.data["repliesCount"] == 0 end test "it handles user deletions", %{delete_user: delete, user: user} do From 5da08c2b73f9ce1f369434fbd2c11092007e4910 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 19:53:30 +0200 Subject: [PATCH 032/337] SideEffects: Fix comment --- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- test/user_test.exs | 28 +------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 39b0f384b..139e609f4 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -35,7 +35,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do # - Replace object with Tombstone # - Set up notification # - Reduce the user note count - # - TODO: Reduce the reply count + # - Reduce the reply count def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object) diff --git a/test/user_test.exs b/test/user_test.exs index 347c5be72..23afc605c 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -15,7 +15,6 @@ defmodule Pleroma.UserTest do use Pleroma.DataCase use Oban.Testing, repo: Pleroma.Repo - import Mock import Pleroma.Factory import ExUnit.CaptureLog @@ -1131,7 +1130,7 @@ test ".delete_user_activities deletes all create activities", %{user: user} do User.delete_user_activities(user) - # TODO: Remove favorites, repeats, delete activities. + # TODO: Test removal favorites, repeats, delete activities. refute Activity.get_by_id(activity.id) end @@ -1180,31 +1179,6 @@ test "it deletes a user, all follow relationships and all activities", %{user: u refute Activity.get_by_id(like_two.id) refute Activity.get_by_id(repeat.id) end - - test_with_mock "it sends out User Delete activity", - %{user: user}, - Pleroma.Web.ActivityPub.Publisher, - [:passthrough], - [] do - Pleroma.Config.put([:instance, :federating], true) - - {:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin") - {:ok, _} = User.follow(follower, user) - - {:ok, job} = User.delete(user) - {:ok, _user} = ObanHelpers.perform(job) - - assert ObanHelpers.member?( - %{ - "op" => "publish_one", - "params" => %{ - "inbox" => "http://mastodon.example.org/inbox", - "id" => "pleroma:fakeid" - } - }, - all_enqueued(worker: Pleroma.Workers.PublisherWorker) - ) - end end test "get_public_key_for_ap_id fetches a user that's not in the db" do From 3b443cbc1dd79b0450e17192aa51a00282b54d2e Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 20:08:25 +0200 Subject: [PATCH 033/337] User: Use common pipeline to delete user activities --- lib/pleroma/user.ex | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b451202b2..c780f99eb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -29,7 +29,9 @@ defmodule Pleroma.User do alias Pleroma.UserRelationship alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils @@ -1427,8 +1429,6 @@ def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do - {:ok, _user} = ActivityPub.delete(user) - # Remove all relationships user |> get_followers() @@ -1531,21 +1531,23 @@ def follow_import(%User{} = follower, followed_identifiers) }) end - def delete_user_activities(%User{ap_id: ap_id}) do + def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id |> Activity.Queries.by_actor() |> RepoStreamer.chunk_stream(50) - |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end) + |> Stream.each(fn activities -> + Enum.each(activities, fn activity -> delete_activity(activity, user) end) + end) |> Stream.run() end - defp delete_activity(%{data: %{"type" => "Create"}} = activity) do - activity - |> Object.normalize() - |> ActivityPub.delete() + defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do + {:ok, delete_data, _} = Builder.delete(user, object) + + Pipeline.common_pipeline(delete_data, local: true) end - defp delete_activity(%{data: %{"type" => "Like"}} = activity) do + defp delete_activity(%{data: %{"type" => "Like"}} = activity, _user) do object = Object.normalize(activity) activity.actor @@ -1553,7 +1555,7 @@ defp delete_activity(%{data: %{"type" => "Like"}} = activity) do |> ActivityPub.unlike(object) end - defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do + defp delete_activity(%{data: %{"type" => "Announce"}} = activity, _user) do object = Object.normalize(activity) activity.actor @@ -1561,7 +1563,7 @@ defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do |> ActivityPub.unannounce(object) end - defp delete_activity(_activity), do: "Doing nothing" + defp delete_activity(_activity, _user), do: "Doing nothing" def html_filter_policy(%User{no_rich_text: true}) do Pleroma.HTML.Scrubber.TwitterText From 999d639873b70f75c340dbac3360d25bca27a998 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 20:13:47 +0200 Subject: [PATCH 034/337] ActivityPub: Remove `delete` function. This is handled by the common pipeline now. --- lib/pleroma/web/activity_pub/activity_pub.ex | 61 --------- test/web/activity_pub/activity_pub_test.exs | 137 ------------------- 2 files changed, 198 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1f4a09370..51f002129 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -519,67 +519,6 @@ defp do_unfollow(follower, followed, activity_id, local) do end end - @spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()} - def delete(entity, options \\ []) do - with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do - result - end - end - - defp do_delete(%User{ap_id: ap_id, follower_address: follower_address} = user, _) do - with data <- %{ - "to" => [follower_address], - "type" => "Delete", - "actor" => ap_id, - "object" => %{"type" => "Person", "id" => ap_id} - }, - {:ok, activity} <- insert(data, true, true, true), - :ok <- maybe_federate(activity) do - {:ok, user} - end - end - - defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do - local = Keyword.get(options, :local, true) - activity_id = Keyword.get(options, :activity_id, nil) - actor = Keyword.get(options, :actor, actor) - - user = User.get_cached_by_ap_id(actor) - to = (object.data["to"] || []) ++ (object.data["cc"] || []) - - with create_activity <- Activity.get_create_by_object_ap_id(id), - data <- - %{ - "type" => "Delete", - "actor" => actor, - "object" => id, - "to" => to, - "deleted_activity_id" => create_activity && create_activity.id - } - |> maybe_put("id", activity_id), - {:ok, activity} <- insert(data, local, false), - {:ok, object, _create_activity} <- Object.delete(object), - stream_out_participations(object, user), - _ <- decrease_replies_count_if_reply(object), - {:ok, _actor} <- decrease_note_count_if_public(user, object), - :ok <- maybe_federate(activity) do - {:ok, activity} - else - {:error, error} -> - Repo.rollback(error) - end - end - - defp do_delete(%Object{data: %{"type" => "Tombstone", "id" => ap_id}}, _) do - activity = - ap_id - |> Activity.Queries.by_object_id() - |> Activity.Queries.by_type("Delete") - |> Repo.one() - - {:ok, activity} - end - @spec block(User.t(), User.t(), String.t() | nil, boolean()) :: {:ok, Activity.t()} | {:error, any()} def block(blocker, blocked, activity_id \\ nil, local \\ true) do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index edd7dfb22..b93ee708e 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1331,143 +1331,6 @@ test "creates an undo activity for the last block" do end end - describe "deletion" do - setup do: clear_config([:instance, :rewrite_policy]) - - test "it reverts deletion on error" do - note = insert(:note_activity) - object = Object.normalize(note) - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.delete(object) - end - - assert Repo.aggregate(Activity, :count, :id) == 1 - assert Repo.get(Object, object.id) == object - assert Activity.get_by_id(note.id) == note - end - - test "it creates a delete activity and deletes the original object" do - note = insert(:note_activity) - object = Object.normalize(note) - {:ok, delete} = ActivityPub.delete(object) - - assert delete.data["type"] == "Delete" - assert delete.data["actor"] == note.data["actor"] - assert delete.data["object"] == object.data["id"] - - assert Activity.get_by_id(delete.id) != nil - - assert Repo.get(Object, object.id).data["type"] == "Tombstone" - end - - test "it doesn't fail when an activity was already deleted" do - {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete() - - assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete() - end - - test "decrements user note count only for public activities" do - user = insert(:user, note_count: 10) - - {:ok, a1} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "public" - }) - - {:ok, a2} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "unlisted" - }) - - {:ok, a3} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "private" - }) - - {:ok, a4} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "direct" - }) - - {:ok, _} = Object.normalize(a1) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a2) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a3) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a4) |> ActivityPub.delete() - - user = User.get_cached_by_id(user.id) - assert user.note_count == 10 - end - - test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do - user = insert(:user) - note = insert(:note_activity) - object = Object.normalize(note) - - {:ok, object} = - object - |> Object.change(%{ - data: %{ - "actor" => object.data["actor"], - "id" => object.data["id"], - "to" => [user.ap_id], - "type" => "Note" - } - }) - |> Object.update_and_set_cache() - - {:ok, delete} = ActivityPub.delete(object) - - assert user.ap_id in delete.data["to"] - end - - test "decreases reply count" do - user = insert(:user) - user2 = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) - reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} - ap_id = activity.data["id"] - - {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) - {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) - {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) - {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) - - _ = CommonAPI.delete(direct_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 2 - - _ = CommonAPI.delete(private_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 2 - - _ = CommonAPI.delete(public_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 1 - - _ = CommonAPI.delete(unlisted_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 0 - end - - test "it passes delete activity through MRF before deleting the object" do - Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) - - note = insert(:note_activity) - object = Object.normalize(note) - - {:error, {:reject, _}} = ActivityPub.delete(object) - - assert Activity.get_by_id(note.id) - assert Repo.get(Object, object.id).data["type"] == object.data["type"] - end - end - describe "timeline post-processing" do test "it filters broken threads" do user1 = insert(:user) From 32b8386edeec3e9b24123c3ccc81a22f1edd5a1c Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 30 Apr 2020 21:23:18 +0200 Subject: [PATCH 035/337] DeleteValidator: Don't federate local deletions of remote objects. Closes #1497 --- .../web/activity_pub/object_validator.ex | 8 +- .../object_validators/delete_validator.ex | 20 ++++- lib/pleroma/web/activity_pub/pipeline.ex | 4 +- .../activity_pub/object_validator_test.exs | 17 +++- test/web/common_api/common_api_test.exs | 80 +++++++++++++++++++ 5 files changed, 119 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 32f606917..479f922f5 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -19,11 +19,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def validate(object, meta) def validate(%{"type" => "Delete"} = object, meta) do - with {:ok, object} <- - object - |> DeleteValidator.cast_and_validate() - |> Ecto.Changeset.apply_action(:insert) do + with cng <- DeleteValidator.cast_and_validate(object), + do_not_federate <- DeleteValidator.do_not_federate?(cng), + {:ok, object} <- Ecto.Changeset.apply_action(cng, :insert) do object = stringify_keys(object) + meta = Keyword.put(meta, :do_not_federate, do_not_federate) {:ok, object, meta} end end diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 951cc1414..a2eff7b69 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do use Ecto.Schema alias Pleroma.Activity + alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.Types import Ecto.Changeset @@ -45,12 +46,17 @@ def validate_data(cng) do cng |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Delete"]) - |> validate_same_domain() + |> validate_actor_presence() + |> validate_deletion_rights() |> validate_object_or_user_presence() |> add_deleted_activity_id() end - def validate_same_domain(cng) do + def do_not_federate?(cng) do + !same_domain?(cng) + end + + defp same_domain?(cng) do actor_domain = cng |> get_field(:actor) @@ -63,11 +69,17 @@ def validate_same_domain(cng) do |> URI.parse() |> (& &1.host).() - if object_domain != actor_domain do + object_domain == actor_domain + end + + def validate_deletion_rights(cng) do + actor = User.get_cached_by_ap_id(get_field(cng, :actor)) + + if User.superuser?(actor) || same_domain?(cng) do cng - |> add_error(:actor, "is not allowed to delete object") else cng + |> add_error(:actor, "is not allowed to delete object") end end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 7ccee54c9..017e39abb 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -29,7 +29,9 @@ def common_pipeline(object, meta) do defp maybe_federate(activity, meta) do with {:ok, local} <- Keyword.fetch(meta, :local) do - if local do + do_not_federate = meta[:do_not_federate] + + if !do_not_federate && local do Federator.publish(activity) {:ok, :federated} else diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 1d3646487..412db09ff 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -52,9 +52,11 @@ test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post test "it's invalid if the actor of the object and the actor of delete are from different domains", %{valid_post_delete: valid_post_delete} do + valid_user = insert(:user) + valid_other_actor = valid_post_delete - |> Map.put("actor", valid_post_delete["actor"] <> "1") + |> Map.put("actor", valid_user.ap_id) assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, [])) @@ -66,6 +68,19 @@ test "it's invalid if the actor of the object and the actor of delete are from d assert {:actor, {"is not allowed to delete object", []}} in cng.errors end + + test "it's valid if the actor of the object is a local superuser", + %{valid_post_delete: valid_post_delete} do + user = + insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo") + + valid_other_actor = + valid_post_delete + |> Map.put("actor", user.ap_id) + + {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, []) + assert meta[:do_not_federate] + end end describe "likes" do diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 1758662b0..32d91ce02 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -9,11 +9,13 @@ defmodule Pleroma.Web.CommonAPITest do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI import Pleroma.Factory + import Mock require Pleroma.Constants @@ -21,6 +23,84 @@ defmodule Pleroma.Web.CommonAPITest do setup do: clear_config([:instance, :limit]) setup do: clear_config([:instance, :max_pinned_statuses]) + describe "deletion" do + test "it allows users to delete their posts" do + user = insert(:user) + + {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + assert {:ok, delete} = CommonAPI.delete(post.id, user) + assert delete.local + assert called(Pleroma.Web.Federator.publish(delete)) + end + + refute Activity.get_by_id(post.id) + end + + test "it does not allow a user to delete their posts" do + user = insert(:user) + other_user = insert(:user) + + {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + + assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user) + assert Activity.get_by_id(post.id) + end + + test "it allows moderators to delete other user's posts" do + user = insert(:user) + moderator = insert(:user, is_moderator: true) + + {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + + assert {:ok, delete} = CommonAPI.delete(post.id, moderator) + assert delete.local + + refute Activity.get_by_id(post.id) + end + + test "it allows admins to delete other user's posts" do + user = insert(:user) + moderator = insert(:user, is_admin: true) + + {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + + assert {:ok, delete} = CommonAPI.delete(post.id, moderator) + assert delete.local + + refute Activity.get_by_id(post.id) + end + + test "superusers deleting non-local posts won't federate the delete" do + # This is the user of the ingested activity + _user = + insert(:user, + local: false, + ap_id: "http://mastodon.example.org/users/admin", + last_refreshed_at: NaiveDateTime.utc_now() + ) + + moderator = insert(:user, is_admin: true) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Jason.decode!() + + {:ok, post} = Transmogrifier.handle_incoming(data) + + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + assert {:ok, delete} = CommonAPI.delete(post.id, moderator) + assert delete.local + refute called(Pleroma.Web.Federator.publish(:_)) + end + + refute Activity.get_by_id(post.id) + end + end + test "favoriting race condition" do user = insert(:user) users_serial = insert_list(10, :user) From ecf37b46d2c06c701da390eba65239984afe683f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 1 May 2020 14:31:24 +0300 Subject: [PATCH 036/337] pagination fix for service users filters --- lib/pleroma/user/query.ex | 11 +++--- .../web/admin_api/admin_api_controller.ex | 29 +++----------- lib/pleroma/web/admin_api/search.ex | 1 + .../admin_api/admin_api_controller_test.exs | 38 ++++++++++++++++++- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index ac77aab71..3a3b04793 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -45,6 +45,7 @@ defmodule Pleroma.User.Query do is_admin: boolean(), is_moderator: boolean(), super_users: boolean(), + exclude_service_users: boolean(), followers: User.t(), friends: User.t(), recipients_from_activity: [String.t()], @@ -88,6 +89,10 @@ defp compose_query({key, value}, query) where(query, [u], ilike(field(u, ^key), ^"%#{value}%")) end + defp compose_query({:exclude_service_users, _}, query) do + where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch")) + end + defp compose_query({key, value}, query) when key in @equal_criteria and not_empty_string(value) do where(query, [u], ^[{key, value}]) @@ -98,7 +103,7 @@ defp compose_query({key, values}, query) when key in @contains_criteria and is_l end defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do - Enum.reduce(tags, query, &prepare_tag_criteria/2) + where(query, [u], fragment("? && ?", u.tags, ^tags)) end defp compose_query({:is_admin, _}, query) do @@ -192,10 +197,6 @@ defp compose_query({:limit, limit}, query) do defp compose_query(_unsupported_param, query), do: query - defp prepare_tag_criteria(tag, query) do - or_where(query, [u], fragment("? = any(?)", ^tag, u.tags)) - end - defp location_query(query, local) do where(query, [u], u.local == ^local) |> where([u], not is_nil(u.nickname)) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 816c11e01..bfcc81cb8 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -392,29 +392,12 @@ def list_users(conn, params) do email: params["email"] } - with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), - {:ok, users, count} <- filter_service_users(users, count), - do: - conn - |> json( - AccountView.render("index.json", - users: users, - count: count, - page_size: page_size - ) - ) - end - - defp filter_service_users(users, count) do - filtered_users = Enum.reject(users, &service_user?/1) - count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count - - {:ok, filtered_users, count} - end - - defp service_user?(user) do - String.match?(user.ap_id, ~r/.*\/relay$/) or - String.match?(user.ap_id, ~r/.*\/internal\/fetch$/) + with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do + json( + conn, + AccountView.render("index.json", users: users, count: count, page_size: page_size) + ) + end end @filters ~w(local external active deactivated is_admin is_moderator) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index 29cea1f44..c28efadd5 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -21,6 +21,7 @@ def user(params \\ %{}) do query = params |> Map.drop([:page, :page_size]) + |> Map.put(:exclude_service_users, true) |> User.Query.build() |> order_by([u], u.nickname) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index f80dbf8dd..e3af01089 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -18,6 +18,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do alias Pleroma.ReportNote alias Pleroma.Tests.ObanHelpers alias Pleroma.User + alias Pleroma.Web alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.CommonAPI @@ -737,6 +738,39 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do } end + test "pagination works correctly with service users", %{conn: conn} do + service1 = insert(:user, ap_id: Web.base_url() <> "/relay") + service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch") + insert_list(25, :user) + + assert %{"count" => 26, "page_size" => 10, "users" => users1} = + conn + |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"}) + |> json_response(200) + + assert Enum.count(users1) == 10 + assert service1 not in [users1] + assert service2 not in [users1] + + assert %{"count" => 26, "page_size" => 10, "users" => users2} = + conn + |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"}) + |> json_response(200) + + assert Enum.count(users2) == 10 + assert service1 not in [users2] + assert service2 not in [users2] + + assert %{"count" => 26, "page_size" => 10, "users" => users3} = + conn + |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"}) + |> json_response(200) + + assert Enum.count(users3) == 6 + assert service1 not in [users3] + assert service2 not in [users3] + end + test "renders empty array for the second page", %{conn: conn} do insert(:user) @@ -3526,7 +3560,7 @@ test "errors", %{conn: conn} do end test "success", %{conn: conn} do - base_url = Pleroma.Web.base_url() + base_url = Web.base_url() app_name = "Trusted app" response = @@ -3547,7 +3581,7 @@ test "success", %{conn: conn} do end test "with trusted", %{conn: conn} do - base_url = Pleroma.Web.base_url() + base_url = Web.base_url() app_name = "Trusted app" response = From 5f42e6629d862f0a8dcbbd1527998685b6932d52 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 13:34:47 +0200 Subject: [PATCH 037/337] DeleteValidator: Only allow deletion of certain types. --- .../object_validators/common_validations.ex | 48 ++++++++++++------- .../object_validators/delete_validator.ex | 12 ++++- lib/pleroma/web/activity_pub/side_effects.ex | 1 + .../activity_pub/object_validator_test.exs | 19 ++++++++ 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index d9a629a34..4e6ee2034 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -28,7 +28,9 @@ def validate_recipients_presence(cng, fields \\ [:to, :cc]) do end end - def validate_actor_presence(cng, field_name \\ :actor) do + def validate_actor_presence(cng, options \\ []) do + field_name = Keyword.get(options, :field_name, :actor) + cng |> validate_change(field_name, fn field_name, actor -> if User.get_cached_by_ap_id(actor) do @@ -39,25 +41,39 @@ def validate_actor_presence(cng, field_name \\ :actor) do end) end - def validate_object_presence(cng, field_name \\ :object) do + def validate_object_presence(cng, options \\ []) do + field_name = Keyword.get(options, :field_name, :object) + allowed_types = Keyword.get(options, :allowed_types, false) + 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"}] + |> validate_change(field_name, fn field_name, object_id -> + object = Object.get_cached_by_ap_id(object_id) + + cond do + !object -> + [{field_name, "can't find object"}] + + object && allowed_types && object.data["type"] not in allowed_types -> + [{field_name, "object not in allowed types"}] + + true -> + [] end end) end - def validate_object_or_user_presence(cng, field_name \\ :object) do - cng - |> validate_change(field_name, fn field_name, object -> - if Object.get_cached_by_ap_id(object) || User.get_cached_by_ap_id(object) do - [] - else - [{field_name, "can't find object"}] - end - end) + def validate_object_or_user_presence(cng, options \\ []) do + field_name = Keyword.get(options, :field_name, :object) + options = Keyword.put(options, :field_name, field_name) + + actor_cng = + cng + |> validate_actor_presence(options) + + object_cng = + cng + |> validate_object_presence(options) + + if actor_cng.valid?, do: actor_cng, else: object_cng end end diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index a2eff7b69..256ac70b6 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -42,13 +42,23 @@ def add_deleted_activity_id(cng) do end end + @deletable_types ~w{ + Answer + Article + Audio + Event + Note + Page + Question + Video + } def validate_data(cng) do cng |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Delete"]) |> validate_actor_presence() |> validate_deletion_rights() - |> validate_object_or_user_presence() + |> validate_object_or_user_presence(allowed_types: @deletable_types) |> add_deleted_activity_id() end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 139e609f4..52bd5179f 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -36,6 +36,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do # - Set up notification # - Reduce the user note count # - Reduce the reply count + # - Stream out the activity def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 412db09ff..7ab1c8ffb 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do use Pleroma.DataCase + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator @@ -26,6 +27,24 @@ test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} assert valid_post_delete["deleted_activity_id"] end + test "it is invalid if the object isn't in a list of certain types", %{ + valid_post_delete: valid_post_delete + } do + object = Object.get_by_ap_id(valid_post_delete["object"]) + + data = + object.data + |> Map.put("type", "Like") + + {:ok, _object} = + object + |> Ecto.Changeset.change(%{data: data}) + |> Object.update_and_set_cache() + + {:error, cng} = ObjectValidator.validate(valid_post_delete, []) + assert {:object, {"object not in allowed types", []}} in cng.errors + end + test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, [])) end From 51f1dbf0a2bf6b61fdef0be56fd8f20a40827100 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 14:05:25 +0200 Subject: [PATCH 038/337] User deletion mix task: Use common pipeline. --- lib/mix/tasks/pleroma/user.ex | 7 +++++-- test/tasks/user_test.exs | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 40dd9bdc0..da140ac86 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -8,6 +8,8 @@ defmodule Mix.Tasks.Pleroma.User do alias Ecto.Changeset alias Pleroma.User alias Pleroma.UserInviteToken + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Pipeline @shortdoc "Manages Pleroma users" @moduledoc File.read!("docs/administration/CLI_tasks/user.md") @@ -96,8 +98,9 @@ def run(["new", nickname, email | rest]) do def run(["rm", nickname]) do start_pleroma() - with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do - User.perform(:delete, user) + with %User{local: true} = user <- User.get_cached_by_nickname(nickname), + {:ok, delete_data, _} <- Builder.delete(user, user.ap_id), + {:ok, _delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do shell_info("User #{nickname} deleted.") else _ -> shell_error("No local user #{nickname}") diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 8df835b56..ab56f07c1 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -4,14 +4,17 @@ defmodule Mix.Tasks.Pleroma.UserTest do alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo - import Pleroma.Factory import ExUnit.CaptureIO + import Mock + import Pleroma.Factory setup_all do Mix.shell(Mix.Shell.Process) @@ -87,12 +90,17 @@ test "user is not created" do test "user is deleted" do user = insert(:user) - Mix.Tasks.Pleroma.User.run(["rm", user.nickname]) + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + Mix.Tasks.Pleroma.User.run(["rm", user.nickname]) + ObanHelpers.perform_all() - assert_received {:mix_shell, :info, [message]} - assert message =~ " deleted" + assert_received {:mix_shell, :info, [message]} + assert message =~ " deleted" + refute User.get_by_nickname(user.nickname) - refute User.get_by_nickname(user.nickname) + assert called(Pleroma.Web.Federator.publish(:_)) + end end test "no user to delete" do From ebbd9c7f369f986b7a66f66eddab91537c490c79 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 14:22:39 +0200 Subject: [PATCH 039/337] AdminAPIController: Refactor. --- lib/pleroma/web/admin_api/admin_api_controller.ex | 14 ++------------ test/web/admin_api/admin_api_controller_test.exs | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 816c11e01..c09584fd1 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -133,18 +133,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do action_fallback(:errors) - def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do - user = User.get_cached_by_nickname(nickname) - User.delete(user) - - ModerationLog.insert_log(%{ - actor: admin, - subject: [user], - action: "delete" - }) - - conn - |> json(nickname) + def user_delete(conn, %{"nickname" => nickname}) do + user_delete(conn, %{"nicknames" => [nickname]}) end def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index f80dbf8dd..c92715fab 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -156,7 +156,7 @@ test "single user", %{admin: admin, conn: conn} do assert ModerationLog.get_log_entry_message(log_entry) == "@#{admin.nickname} deleted users: @#{user.nickname}" - assert json_response(conn, 200) == user.nickname + assert json_response(conn, 200) == [user.nickname] end test "multiple users", %{admin: admin, conn: conn} do From 1ead5f49b8da941399fa2afadd40cd8beb8ccf8d Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 1 May 2020 14:30:39 +0200 Subject: [PATCH 040/337] AdminApiController: Use common pipeline for user deletion. --- .../web/admin_api/admin_api_controller.ex | 13 +++++++-- .../admin_api/admin_api_controller_test.exs | 28 +++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c09584fd1..9a12da027 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI.AccountView @@ -138,8 +140,15 @@ def user_delete(conn, %{"nickname" => nickname}) do end def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - User.delete(users) + users = + nicknames + |> Enum.map(&User.get_cached_by_nickname/1) + + users + |> Enum.each(fn user -> + {:ok, delete_data, _} = Builder.delete(admin, user.ap_id) + Pipeline.common_pipeline(delete_data, local: true) + end) ModerationLog.insert_log(%{ actor: admin, diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index c92715fab..35001ab4a 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -6,8 +6,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo - import Pleroma.Factory import ExUnit.CaptureLog + import Mock + import Pleroma.Factory alias Pleroma.Activity alias Pleroma.Config @@ -146,17 +147,26 @@ test "GET /api/pleroma/admin/users/:nickname requires " <> test "single user", %{admin: admin, conn: conn} do user = insert(:user) - conn = - conn - |> put_req_header("accept", "application/json") - |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + conn = + conn + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") - log_entry = Repo.one(ModerationLog) + ObanHelpers.perform_all() - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deleted users: @#{user.nickname}" + refute User.get_by_nickname(user.nickname) - assert json_response(conn, 200) == [user.nickname] + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted users: @#{user.nickname}" + + assert json_response(conn, 200) == [user.nickname] + + assert called(Pleroma.Web.Federator.publish(:_)) + end end test "multiple users", %{admin: admin, conn: conn} do From aea781cbd8fb43f906c6022a8d2e0bf896008203 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 1 May 2020 16:31:05 +0300 Subject: [PATCH 041/337] credo fix --- test/web/admin_api/admin_api_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index e3af01089..d798412e3 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -18,8 +18,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do alias Pleroma.ReportNote alias Pleroma.Tests.ObanHelpers alias Pleroma.User - alias Pleroma.Web alias Pleroma.UserInviteToken + alias Pleroma.Web alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.CommonAPI alias Pleroma.Web.MediaProxy From d5cdc907e3fda14c2ce78ddbb124739441330ecc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 1 May 2020 18:45:24 +0300 Subject: [PATCH 042/337] Restricted embedding of relationships where applicable (statuses / notifications / accounts rendering). Added support for :skip_notifications for accounts listing (index.json). Adjusted tests. --- benchmarks/load_testing/fetcher.ex | 21 +++++---------- config/config.exs | 2 -- lib/mix/tasks/pleroma/benchmark.ex | 3 +-- .../web/admin_api/admin_api_controller.ex | 6 ++--- .../web/admin_api/views/report_view.ex | 9 ++++--- .../web/admin_api/views/status_view.ex | 6 +++-- lib/pleroma/web/chat_channel.ex | 8 +++++- lib/pleroma/web/controller_helper.ex | 11 ++------ .../controllers/search_controller.ex | 2 +- .../web/mastodon_api/views/account_view.ex | 7 +++-- .../mastodon_api/views/notification_view.ex | 4 +++ .../web/mastodon_api/views/status_view.ex | 6 +++++ .../controllers/pleroma_api_controller.ex | 8 +++++- .../notification_controller_test.exs | 4 +-- .../controllers/status_controller_test.exs | 2 +- .../controllers/timeline_controller_test.exs | 26 +++++++++++++------ .../views/notification_view_test.exs | 25 +++++++++++++----- .../mastodon_api/views/status_view_test.exs | 14 ++++------ 18 files changed, 94 insertions(+), 70 deletions(-) diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex index 12c30f6f5..0de4924bc 100644 --- a/benchmarks/load_testing/fetcher.ex +++ b/benchmarks/load_testing/fetcher.ex @@ -387,56 +387,47 @@ defp render_timelines(user) 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, - skip_relationships: !output_relationships + as: :activity }) end, "Rendering direct timeline" => fn -> StatusView.render("index.json", %{ activities: direct_activities, for: user, - as: :activity, - skip_relationships: !output_relationships + as: :activity }) end, "Rendering public timeline" => fn -> StatusView.render("index.json", %{ activities: public_activities, for: user, - as: :activity, - skip_relationships: !output_relationships + as: :activity }) end, "Rendering tag timeline" => fn -> StatusView.render("index.json", %{ activities: tag_activities, for: user, - as: :activity, - skip_relationships: !output_relationships + as: :activity }) end, "Rendering notifications" => fn -> Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ notifications: notifications, - for: user, - skip_relationships: !output_relationships + for: user }) end, "Rendering favourites timeline" => fn -> StatusView.render("index.json", %{ activities: favourites, for: user, - as: :activity, - skip_relationships: !output_relationships + as: :activity }) end }, diff --git a/config/config.exs b/config/config.exs index 2e538c4be..d698e6028 100644 --- a/config/config.exs +++ b/config/config.exs @@ -240,8 +240,6 @@ 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/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index 6ab7fe8ef..dd2b9c8f2 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -67,8 +67,7 @@ def run(["render_timeline", nickname | _] = args) do Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ activities: activities, for: user, - as: :activity, - skip_relationships: true + as: :activity }) end }, diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 816c11e01..e0e1a2ceb 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -280,7 +280,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do conn |> put_view(Pleroma.Web.AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) + |> render("index.json", %{activities: activities, as: :activity}) end def list_user_statuses(conn, %{"nickname" => nickname} = params) do @@ -299,7 +299,7 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do conn |> put_view(StatusView) - |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) + |> render("index.json", %{activities: activities, as: :activity}) else _ -> {:error, :not_found} end @@ -834,7 +834,7 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do conn |> put_view(Pleroma.Web.AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) + |> render("index.json", %{activities: activities, as: :activity}) 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 d50969b2a..215e31100 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -7,8 +7,10 @@ defmodule Pleroma.Web.AdminAPI.ReportView do alias Pleroma.HTML alias Pleroma.User + alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MastodonAPI alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{reports: reports}) do @@ -41,8 +43,7 @@ def render("show.json", %{report: report, user: user, account: account, statuses statuses: StatusView.render("index.json", %{ activities: statuses, - as: :activity, - skip_relationships: false + as: :activity }), state: report.data["state"], notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) @@ -72,8 +73,8 @@ def render("show_note.json", %{ end defp merge_account_views(%User{} = user) do - Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) - |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) + MastodonAPI.AccountView.render("show.json", %{user: user, skip_relationships: true}) + |> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user})) end defp merge_account_views(_), do: %{} diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 3637dee24..a76fad990 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.AdminAPI.StatusView do require Pleroma.Constants alias Pleroma.User + alias Pleroma.Web.AdminAPI + alias Pleroma.Web.MastodonAPI alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", opts) do @@ -22,8 +24,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} end defp merge_account_views(%User{} = user) do - Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) - |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) + MastodonAPI.AccountView.render("show.json", %{user: user, skip_relationships: true}) + |> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user})) end defp merge_account_views(_), do: %{} diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index 38ec774f7..3df8dc0f1 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -22,7 +22,13 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do author = User.get_cached_by_nickname(user_name) - author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) + + author = + Pleroma.Web.MastodonAPI.AccountView.render("show.json", + user: author, + skip_relationships: true + ) + message = ChatChannelState.add_message(%{text: text, author: author}) broadcast!(socket, "new_msg", message) diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index eb97ae975..f0b4c087a 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -5,8 +5,6 @@ defmodule Pleroma.Web.ControllerHelper do use Pleroma.Web, :controller - 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"] @@ -106,13 +104,8 @@ 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" + @doc "Whether to skip `account.pleroma.relationship` rendering 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 + not truthy_param?(params["with_relationships"]) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index cd49da6ad..85a316762 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -86,7 +86,7 @@ defp resource_search(_, "accounts", query, options) do users: accounts, for: options[:for_user], as: :user, - skip_relationships: false + skip_relationships: true ) end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b4b61e74c..6d17c2d02 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -13,15 +13,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.Web.MediaProxy def render("index.json", %{users: users} = opts) do + opts = Map.merge(%{skip_relationships: false}, opts) + reading_user = opts[:for] - # Note: :skip_relationships option is currently intentionally not supported for accounts relationships_opt = cond do Map.has_key?(opts, :relationships) -> opts[:relationships] - is_nil(reading_user) -> + is_nil(reading_user) || opts[:skip_relationships] -> UserRelationship.view_relationships_option(nil, []) true -> @@ -158,6 +159,8 @@ def render("relationships.json", %{user: user, targets: targets} = opts) do end defp do_render("show.json", %{user: user} = opts) do + opts = Map.merge(%{skip_relationships: false}, opts) + user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 4da1ab67f..e518bdedb 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{notifications: notifications, for: reading_user} = opts) do + opts = Map.merge(%{skip_relationships: true}, opts) + activities = Enum.map(notifications, & &1.activity) parent_activities = @@ -71,6 +73,8 @@ def render( for: reading_user } = opts ) do + opts = Map.merge(%{skip_relationships: true}, opts) + actor = User.get_cached_by_ap_id(activity.data["actor"]) parent_activity_fn = fn -> diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 24167f66f..0bcc84d44 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -76,6 +76,8 @@ defp reblogged?(activity, user) do end def render("index.json", opts) do + opts = Map.merge(%{skip_relationships: true}, opts) + reading_user = opts[:for] # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list @@ -125,6 +127,8 @@ def render( "show.json", %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts ) do + opts = Map.merge(%{skip_relationships: true}, opts) + user = get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) activity_object = Object.normalize(activity) @@ -198,6 +202,8 @@ def render( end def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + opts = Map.merge(%{skip_relationships: true}, opts) + object = Object.normalize(activity) user = get_user(activity.data["actor"]) 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 2c1874051..f3ac17a66 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -66,7 +66,13 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} %{ name: emoji, count: length(users), - accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}), + accounts: + AccountView.render("index.json", %{ + users: users, + for: user, + as: :user, + skip_relationships: true + }), me: !!(user && user.ap_id in user_ap_ids) } end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index db380f76a..e2d98ef3e 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -12,9 +12,7 @@ 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) - + test "does NOT render account/pleroma/relationship by default" do %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 85068edd0..00e026087 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -1058,7 +1058,7 @@ test "replaces missing description with an empty string", %{conn: conn, user: us end test "bookmarks" do - bookmarks_uri = "/api/v1/bookmarks?with_relationships=true" + bookmarks_uri = "/api/v1/bookmarks" %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) author = insert(:user) diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index 06efdc901..b8bb83af7 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -20,12 +20,10 @@ 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", %{ + test "does NOT render account/pleroma/relationship 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}"}) @@ -41,7 +39,7 @@ test "does NOT render account/pleroma/relationship if this is disabled by defaul end) end - test "the home timeline", %{user: user, conn: conn} do + test "embeds account relationships with `with_relationships=true`", %{user: user, conn: conn} do uri = "/api/v1/timelines/home?with_relationships=true" following = insert(:user, nickname: "followed") @@ -69,13 +67,19 @@ test "the home timeline", %{user: user, conn: conn} do } } }, - "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}} + "account" => %{ + "pleroma" => %{ + "relationship" => %{"following" => true} + } + } }, %{ "content" => "post", "account" => %{ "acct" => "followed", - "pleroma" => %{"relationship" => %{"following" => true}} + "pleroma" => %{ + "relationship" => %{"following" => true} + } } } ] = json_response(ret_conn, :ok) @@ -95,13 +99,19 @@ test "the home timeline", %{user: user, conn: conn} do } } }, - "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}} + "account" => %{ + "pleroma" => %{ + "relationship" => %{"following" => true} + } + } }, %{ "content" => "post", "account" => %{ "acct" => "followed", - "pleroma" => %{"relationship" => %{"following" => true}} + "pleroma" => %{ + "relationship" => %{"following" => true} + } } } ] = json_response(ret_conn, :ok) diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index c3ec9dfec..e1f9c3ac4 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -42,7 +42,12 @@ test "Mention notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "mention", - account: AccountView.render("show.json", %{user: user, for: mentioned_user}), + account: + AccountView.render("show.json", %{ + user: user, + for: mentioned_user, + skip_relationships: true + }), status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -62,7 +67,8 @@ test "Favourite notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "favourite", - account: AccountView.render("show.json", %{user: another_user, for: user}), + account: + AccountView.render("show.json", %{user: another_user, for: user, skip_relationships: true}), status: StatusView.render("show.json", %{activity: create_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -82,7 +88,8 @@ test "Reblog notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "reblog", - account: AccountView.render("show.json", %{user: another_user, for: user}), + account: + AccountView.render("show.json", %{user: another_user, for: user, skip_relationships: true}), status: StatusView.render("show.json", %{activity: reblog_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -100,7 +107,8 @@ test "Follow notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "follow", - account: AccountView.render("show.json", %{user: follower, for: followed}), + account: + AccountView.render("show.json", %{user: follower, for: followed, skip_relationships: true}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -143,8 +151,10 @@ test "Move notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "move", - account: AccountView.render("show.json", %{user: old_user, for: follower}), - target: AccountView.render("show.json", %{user: new_user, for: follower}), + account: + AccountView.render("show.json", %{user: old_user, for: follower, skip_relationships: true}), + target: + AccountView.render("show.json", %{user: new_user, for: follower, skip_relationships: true}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -169,7 +179,8 @@ test "EmojiReact notification" do pleroma: %{is_seen: false}, type: "pleroma:emoji_reaction", emoji: "☕", - account: AccountView.render("show.json", %{user: other_user, for: user}), + account: + AccountView.render("show.json", %{user: other_user, for: user, skip_relationships: true}), status: StatusView.render("show.json", %{activity: activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 6791c2fb0..91d4ded2c 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -555,7 +555,7 @@ test "a rich media card with all relevant data renders correctly" do end end - test "embeds a relationship in the account" do + test "does not embed a relationship in the account" do user = insert(:user) other_user = insert(:user) @@ -566,11 +566,10 @@ test "embeds a relationship in the account" do result = StatusView.render("show.json", %{activity: activity, for: other_user}) - assert result[:account][:pleroma][:relationship] == - AccountView.render("relationship.json", %{user: other_user, target: user}) + assert result[:account][:pleroma][:relationship] == %{} end - test "embeds a relationship in the account in reposts" do + test "does not embed a relationship in the account in reposts" do user = insert(:user) other_user = insert(:user) @@ -583,11 +582,8 @@ test "embeds a relationship in the account in reposts" do result = StatusView.render("show.json", %{activity: activity, for: user}) - assert result[:account][:pleroma][:relationship] == - AccountView.render("relationship.json", %{user: user, target: other_user}) - - assert result[:reblog][:account][:pleroma][:relationship] == - AccountView.render("relationship.json", %{user: user, target: user}) + assert result[:account][:pleroma][:relationship] == %{} + assert result[:reblog][:account][:pleroma][:relationship] == %{} end test "visibility/list" do From f20a1a27ef93c494e671b67603b320249073e011 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 3 May 2020 12:19:01 +0200 Subject: [PATCH 043/337] DeleteValidator: Improve code readability --- .../activity_pub/object_validators/delete_validator.ex | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 256ac70b6..68ab08605 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -67,19 +67,17 @@ def do_not_federate?(cng) do end defp same_domain?(cng) do - actor_domain = + actor_uri = cng |> get_field(:actor) |> URI.parse() - |> (& &1.host).() - object_domain = + object_uri = cng |> get_field(:object) |> URI.parse() - |> (& &1.host).() - object_domain == actor_domain + object_uri.host == actor_uri.host end def validate_deletion_rights(cng) do From 4dfc617cdf1c2579f4f941dcd0fa5c728178df06 Mon Sep 17 00:00:00 2001 From: lain Date: Sun, 3 May 2020 12:51:28 +0200 Subject: [PATCH 044/337] Transmogrifier: Don't fetch actor that's guaranteed to be there. --- .../web/activity_pub/transmogrifier.ex | 3 +- .../transmogrifier/delete_handling_test.exs | 30 ++++--------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 855aab8d4..1e031a015 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -733,8 +733,7 @@ def handle_incoming( %{"type" => "Delete"} = data, _options ) do - with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), - {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do + with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} end end diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs index 64c908a05..c141e25bc 100644 --- a/test/web/activity_pub/transmogrifier/delete_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do alias Pleroma.Web.ActivityPub.Transmogrifier import Pleroma.Factory - import ExUnit.CaptureLog setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -27,22 +26,15 @@ test "it works for incoming deletes" do data = File.read!("test/fixtures/mastodon-delete.json") |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) |> Map.put("actor", deleting_user.ap_id) + |> put_in(["object", "id"], activity.data["object"]) {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = Transmogrifier.handle_incoming(data) assert id == data["id"] - # We delete the Create activity because base our timelines on it. + # We delete the Create activity because we base our timelines on it. # This should be changed after we unify objects and activities refute Activity.get_by_id(activity.id) assert actor == deleting_user.ap_id @@ -54,25 +46,15 @@ test "it works for incoming deletes" do test "it fails for incoming deletes with spoofed origin" do activity = insert(:note_activity) + %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") data = File.read!("test/fixtures/mastodon-delete.json") |> Poison.decode!() + |> Map.put("actor", ap_id) + |> put_in(["object", "id"], activity.data["object"]) - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - - assert capture_log(fn -> - {:error, _} = Transmogrifier.handle_incoming(data) - end) =~ - "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" - - assert Activity.get_by_id(activity.id) + assert match?({:error, _}, Transmogrifier.handle_incoming(data)) end @tag capture_log: true From 6c337489f4db28f78be940bef01ef3a80e279ffc Mon Sep 17 00:00:00 2001 From: lain Date: Sun, 3 May 2020 13:01:19 +0200 Subject: [PATCH 045/337] Various testing fixes in relation to user deletion. --- test/web/activity_pub/side_effects_test.exs | 2 +- test/web/activity_pub/transmogrifier/delete_handling_test.exs | 2 +- test/web/admin_api/admin_api_controller_test.exs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index ce34eed4c..a9598d7b3 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -68,7 +68,7 @@ test "it handles user deletions", %{delete_user: delete, user: user} do {:ok, _delete, _} = SideEffects.handle(delete) ObanHelpers.perform_all() - refute User.get_cached_by_ap_id(user.ap_id) + assert User.get_cached_by_ap_id(user.ap_id).deactivated end end diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs index c141e25bc..f235a8e63 100644 --- a/test/web/activity_pub/transmogrifier/delete_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -68,7 +68,7 @@ test "it works for incoming user deletes" do {:ok, _} = Transmogrifier.handle_incoming(data) ObanHelpers.perform_all() - refute User.get_cached_by_ap_id(ap_id) + assert User.get_cached_by_ap_id(ap_id).deactivated end test "it fails for incoming user deletes with spoofed origin" do diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index bf054a12e..0daf29ffb 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -156,7 +156,7 @@ test "single user", %{admin: admin, conn: conn} do ObanHelpers.perform_all() - refute User.get_by_nickname(user.nickname) + assert User.get_by_nickname(user.nickname).deactivated log_entry = Repo.one(ModerationLog) From 1974d0cc423efefcbdadd68442d0fbed8f3ee4ab Mon Sep 17 00:00:00 2001 From: lain Date: Sun, 3 May 2020 13:02:57 +0200 Subject: [PATCH 046/337] DeleteValidator: The deleted activity id is an object id --- .../web/activity_pub/object_validators/delete_validator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex index 68ab08605..e06de3dff 100644 --- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do field(:actor, Types.ObjectID) field(:to, Types.Recipients, default: []) field(:cc, Types.Recipients, default: []) - field(:deleted_activity_id) + field(:deleted_activity_id, Types.ObjectID) field(:object, Types.ObjectID) end From a7966f2080a0e9b3c2b35efa7ea647c1bdef2a2d Mon Sep 17 00:00:00 2001 From: lain Date: Sun, 3 May 2020 13:48:01 +0200 Subject: [PATCH 047/337] Webfinger: Request account info with the acct scheme --- lib/pleroma/web/web_finger/web_finger.ex | 6 ++++-- test/support/http_request_mock.ex | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 7ffd0e51b..442b25165 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -194,13 +194,15 @@ def finger(account) do URI.parse(account).host end + encoded_account = URI.encode("acct:#{account}") + address = case find_lrdd_template(domain) do {:ok, template} -> - String.replace(template, "{uri}", URI.encode(account)) + String.replace(template, "{uri}", encoded_account) _ -> - "https://#{domain}/.well-known/webfinger?resource=acct:#{account}" + "https://#{domain}/.well-known/webfinger?resource=#{encoded_account}" end with response <- diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 9624cb0f7..3a95e92da 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -211,7 +211,7 @@ def get( end def get( - "https://squeet.me/xrd/?uri=lain@squeet.me", + "https://squeet.me/xrd/?uri=acct:lain@squeet.me", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -870,7 +870,7 @@ def get( end def get( - "https://social.heldscal.la/.well-known/webfinger?resource=shp@social.heldscal.la", + "https://social.heldscal.la/.well-known/webfinger?resource=acct:shp@social.heldscal.la", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -883,7 +883,7 @@ def get( end def get( - "https://social.heldscal.la/.well-known/webfinger?resource=invalid_content@social.heldscal.la", + "https://social.heldscal.la/.well-known/webfinger?resource=acct:invalid_content@social.heldscal.la", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -900,7 +900,7 @@ def get("http://framatube.org/.well-known/host-meta", _, _, _) do end def get( - "http://framatube.org/main/xrd?uri=framasoft@framatube.org", + "http://framatube.org/main/xrd?uri=acct:framasoft@framatube.org", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -959,7 +959,7 @@ def get("http://gerzilla.de/.well-known/host-meta", _, _, _) do end def get( - "https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", + "https://gerzilla.de/xrd/?uri=acct:kaniini@gerzilla.de", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -1155,7 +1155,7 @@ def get("http://404.site" <> _, _, _, _) do end def get( - "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=lain@zetsubou.xn--q9jyb4c", + "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:lain@zetsubou.xn--q9jyb4c", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] @@ -1168,7 +1168,7 @@ def get( end def get( - "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=https://zetsubou.xn--q9jyb4c/users/lain", + "https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:https://zetsubou.xn--q9jyb4c/users/lain", _, _, [{"accept", "application/xrd+xml,application/jrd+json"}] From 7dd47bee82c9f4a5e3b4ce6d74c5a22cac596b52 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 May 2020 12:22:31 +0200 Subject: [PATCH 048/337] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522285efe..92dd6f0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking**: SimplePolicy `:reject` and `:accept` allow deletions again - Fix follower/blocks import when nicknames starts with @ - Filtering of push notifications on activities from blocked domains +- Resolving Peertube accounts with Webfinger ## [unreleased-patch] ### Security From d08c63500b5deca268ebc24833be4cb3279bdaaa Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 4 May 2020 20:16:18 +0400 Subject: [PATCH 049/337] Ignore unexpected query params --- lib/pleroma/web/api_spec/cast_and_validate.ex | 120 ++++++++++++++++++ .../controllers/account_controller.ex | 2 +- .../controllers/app_controller.ex | 2 +- .../controllers/custom_emoji_controller.ex | 2 +- .../controllers/domain_block_controller.ex | 2 +- .../controllers/notification_controller.ex | 2 +- .../controllers/report_controller.ex | 2 +- 7 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/web/api_spec/cast_and_validate.ex diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex new file mode 100644 index 000000000..f36cf7a55 --- /dev/null +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -0,0 +1,120 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.CastAndValidate do + @moduledoc """ + This plug is based on [`OpenApiSpex.Plug.CastAndValidate`] + (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex). + The main difference is ignoring unexpected query params + instead of throwing an error. Also, the default rendering + error module is `Pleroma.Web.ApiSpec.RenderError`. + """ + + @behaviour Plug + + alias Plug.Conn + + @impl Plug + def init(opts) do + opts + |> Map.new() + |> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError) + end + + @impl Plug + def call(%{private: %{open_api_spex: private_data}} = conn, %{ + operation_id: operation_id, + render_error: render_error + }) do + spec = private_data.spec + operation = private_data.operation_lookup[operation_id] + + content_type = + case Conn.get_req_header(conn, "content-type") do + [header_value | _] -> + header_value + |> String.split(";") + |> List.first() + + _ -> + nil + end + + private_data = Map.put(private_data, :operation_id, operation_id) + conn = Conn.put_private(conn, :open_api_spex, private_data) + + case cast_and_validate(spec, operation, conn, content_type) do + {:ok, conn} -> + conn + + {:error, reason} -> + opts = render_error.init(reason) + + conn + |> render_error.call(opts) + |> Plug.Conn.halt() + end + end + + def call( + %{ + private: %{ + phoenix_controller: controller, + phoenix_action: action, + open_api_spex: private_data + } + } = conn, + opts + ) do + operation = + case private_data.operation_lookup[{controller, action}] do + nil -> + operation_id = controller.open_api_operation(action).operationId + operation = private_data.operation_lookup[operation_id] + + operation_lookup = + private_data.operation_lookup + |> Map.put({controller, action}, operation) + + OpenApiSpex.Plug.Cache.adapter().put( + private_data.spec_module, + {private_data.spec, operation_lookup} + ) + + operation + + operation -> + operation + end + + if operation.operationId do + call(conn, Map.put(opts, :operation_id, operation.operationId)) + else + raise "operationId was not found in action API spec" + end + end + + def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts) + + defp cast_and_validate(spec, operation, conn, content_type) do + case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do + {:ok, conn} -> + {:ok, conn} + + # Remove unexpected query params and cast/validate again + {:error, errors} -> + query_params = + Enum.reduce(errors, conn.query_params, fn + %{reason: :unexpected_field, name: name, path: [name]}, params -> + Map.delete(params, name) + + _, params -> + params + end) + + conn = %Conn{conn | query_params: query_params} + OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) + 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 61b0e2f63..8458cbdd5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI - plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 408e11474..a516b6c20 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.AppController do plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) - plug(OpenApiSpex.Plug.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate) @local_mastodon_name "Mastodon-Local" diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index 000ad743f..c5f47c5df 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do use Pleroma.Web, :controller - plug(OpenApiSpex.Plug.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug( :skip_plug, diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index c4fa383f2..825b231ab 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User - plug(OpenApiSpex.Plug.CastAndValidate) + plug(Pleroma.Web.ApiSpec.CastAndValidate) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation plug( diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index a14c86893..596b85617 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] - plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug( OAuthScopesPlug, diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index f65c5c62b..405167108 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation From bfbff7d82673d6128a18e73dcc91f70ee669c2ac Mon Sep 17 00:00:00 2001 From: minibikini Date: Mon, 4 May 2020 16:38:23 +0000 Subject: [PATCH 050/337] Apply suggestion to lib/pleroma/web/api_spec/cast_and_validate.ex --- lib/pleroma/web/api_spec/cast_and_validate.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index f36cf7a55..cd02403c1 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -1,5 +1,6 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2019-2020 Moxley Stratton, Mike Buhot , MPL-2.0 +# Copyright © 2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ApiSpec.CastAndValidate do From 4b9ab67aa8bdf7fdf7390080932fee2e5879a5e4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 4 May 2020 21:46:25 +0400 Subject: [PATCH 051/337] Ignore unexpected ENUM values in query string --- lib/pleroma/web/api_spec/cast_and_validate.ex | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index cd02403c1..b94517c52 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -110,6 +110,10 @@ defp cast_and_validate(spec, operation, conn, content_type) do %{reason: :unexpected_field, name: name, path: [name]}, params -> Map.delete(params, name) + %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> + path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() + update_in(params, path, &List.delete(&1, value)) + _, params -> params end) @@ -118,4 +122,11 @@ defp cast_and_validate(spec, operation, conn, content_type) do OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) end end + + defp list_items_to_string(list) do + Enum.map(list, fn + i when is_atom(i) -> to_string(i) + i -> i + end) + end end From f070b5569ca0eafdca79f1f3e3b6b5025f3f8fc9 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 4 May 2020 22:33:05 +0400 Subject: [PATCH 052/337] Add a config option to enable strict validation --- config/config.exs | 2 ++ lib/pleroma/web/api_spec/cast_and_validate.ex | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config/config.exs b/config/config.exs index a6c6d6f99..ca9bbab64 100644 --- a/config/config.exs +++ b/config/config.exs @@ -653,6 +653,8 @@ profiles: %{local: false, remote: false}, activities: %{local: false, remote: false} +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index b94517c52..bd9026237 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -7,9 +7,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do @moduledoc """ This plug is based on [`OpenApiSpex.Plug.CastAndValidate`] (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex). - The main difference is ignoring unexpected query params - instead of throwing an error. Also, the default rendering - error module is `Pleroma.Web.ApiSpec.RenderError`. + The main difference is ignoring unexpected query params instead of throwing + an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`) + to disable this behavior. Also, the default rendering error module + is `Pleroma.Web.ApiSpec.RenderError`. """ @behaviour Plug @@ -45,7 +46,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{ private_data = Map.put(private_data, :operation_id, operation_id) conn = Conn.put_private(conn, :open_api_spex, private_data) - case cast_and_validate(spec, operation, conn, content_type) do + case cast_and_validate(spec, operation, conn, content_type, strict?()) do {:ok, conn} -> conn @@ -98,7 +99,11 @@ def call( def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts) - defp cast_and_validate(spec, operation, conn, content_type) do + defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do + OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) + end + + defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do {:ok, conn} -> {:ok, conn} @@ -129,4 +134,6 @@ defp list_items_to_string(list) do i -> i end) end + + defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false) end From e55fd530bc9a6ab42e475efe689e239963906928 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 4 May 2020 22:33:34 +0400 Subject: [PATCH 053/337] Render better errors for ENUM validation --- lib/pleroma/web/api_spec/render_error.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index b5877ca9c..d476b8ef3 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -17,6 +17,9 @@ def init(opts), do: opts def call(conn, errors) do errors = Enum.map(errors, fn + %{name: nil, reason: :invalid_enum} = err -> + %OpenApiSpex.Cast.Error{err | name: err.value} + %{name: nil} = err -> %OpenApiSpex.Cast.Error{err | name: List.last(err.path)} From 1cb89aac1eef7711aa7950fe03e02e24bc665317 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 4 May 2020 22:35:28 +0400 Subject: [PATCH 054/337] Enable strict validation mode in dev and test environments --- config/dev.exs | 2 ++ config/test.exs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/dev.exs b/config/dev.exs index 7e1e3b4be..4faaeff5b 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -52,6 +52,8 @@ hostname: "localhost", pool_size: 10 +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true + if File.exists?("./config/dev.secret.exs") do import_config "dev.secret.exs" else diff --git a/config/test.exs b/config/test.exs index 040e67e4a..cbf775109 100644 --- a/config/test.exs +++ b/config/test.exs @@ -96,6 +96,8 @@ config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false +config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true + if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" else From bf0e41f0daa5809db53ed4a9130ade63952e8da0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 4 May 2020 23:32:53 +0200 Subject: [PATCH 055/337] Transmogrifier.set_sensitive/1: Keep sensitive set to true --- CHANGELOG.md | 1 + .../web/activity_pub/transmogrifier.ex | 4 ++++ .../activity_pub_controller_test.exs | 22 +++++++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522285efe..cdb8a2080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Logger configuration through AdminFE - HTTP Basic Authentication permissions issue - ObjectAgePolicy didn't filter out old messages +- Transmogrifier: Keep object sensitive settings for outgoing representation (AP C2S) ### Added - NodeInfo: ObjectAgePolicy settings to the `federation` list. diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 581e7040b..3a4d364e7 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1195,6 +1195,10 @@ def set_conversation(object) do Map.put(object, "conversation", object["context"]) end + def set_sensitive(%{"sensitive" => true} = object) do + object + end + def set_sensitive(object) do tags = object["tag"] || [] Map.put(object, "sensitive", "nsfw" in tags) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index a8f1f0e26..5c8d20ac4 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -820,21 +820,29 @@ test "it inserts an incoming sensitive activity into the database", %{ activity: activity } do user = insert(:user) + conn = assign(conn, :user, user) object = Map.put(activity["object"], "sensitive", true) activity = Map.put(activity, "object", object) - result = + response = conn - |> assign(:user, user) |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/outbox", activity) |> json_response(201) - assert Activity.get_by_ap_id(result["id"]) - assert result["object"] - assert %Object{data: object} = Object.normalize(result["object"]) - assert object["sensitive"] == activity["object"]["sensitive"] - assert object["content"] == activity["object"]["content"] + assert Activity.get_by_ap_id(response["id"]) + assert response["object"] + assert %Object{data: response_object} = Object.normalize(response["object"]) + assert response_object["sensitive"] == true + assert response_object["content"] == activity["object"]["content"] + + representation = + conn + |> put_req_header("accept", "application/activity+json") + |> get(response["id"]) + |> json_response(200) + + assert representation["object"]["sensitive"] == true end test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do From 8bed6ea922dbc1cfb8166fea6ce344d3618b3d52 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 09:25:09 +0200 Subject: [PATCH 056/337] User, Webfinger: Remove OStatus vestiges Mainly the `magic_key` field --- lib/pleroma/user.ex | 2 - lib/pleroma/web/web_finger/web_finger.ex | 39 +------------------ .../20200505072231_remove_magic_key_field.exs | 9 +++++ test/web/web_finger/web_finger_test.exs | 4 +- 4 files changed, 12 insertions(+), 42 deletions(-) create mode 100644 priv/repo/migrations/20200505072231_remove_magic_key_field.exs diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 99358ddaf..2c343eb22 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -113,7 +113,6 @@ defmodule Pleroma.User do field(:is_admin, :boolean, default: false) field(:show_role, :boolean, default: true) field(:settings, :map, default: nil) - field(:magic_key, :string, default: nil) field(:uri, Types.Uri, default: nil) field(:hide_followers_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false) @@ -387,7 +386,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :banner, :locked, :last_refreshed_at, - :magic_key, :uri, :follower_address, :following_address, diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 7ffd0e51b..b26453828 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -86,53 +86,19 @@ def represent_user(user, "XML") do |> XmlBuilder.to_doc() end - defp get_magic_key("data:application/magic-public-key," <> magic_key) do - {:ok, magic_key} - end - - defp get_magic_key(nil) do - Logger.debug("Undefined magic key.") - {:ok, nil} - end - - defp get_magic_key(_) do - {:error, "Missing magic key data."} - end - defp webfinger_from_xml(doc) do - with magic_key <- XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc), - {:ok, magic_key} <- get_magic_key(magic_key), - topic <- - XML.string_from_xpath( - ~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, - doc - ), - subject <- XML.string_from_xpath("//Subject", doc), - subscribe_address <- - XML.string_from_xpath( - ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, - doc - ), + with subject <- XML.string_from_xpath("//Subject", doc), ap_id <- XML.string_from_xpath( ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, doc ) do data = %{ - "magic_key" => magic_key, - "topic" => topic, "subject" => subject, - "subscribe_address" => subscribe_address, "ap_id" => ap_id } {:ok, data} - else - {:error, e} -> - {:error, e} - - e -> - {:error, e} end end @@ -146,9 +112,6 @@ defp webfinger_from_json(doc) do {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} -> Map.put(data, "ap_id", link["href"]) - {_, "http://ostatus.org/schema/1.0/subscribe"} -> - Map.put(data, "subscribe_address", link["template"]) - _ -> Logger.debug("Unhandled type: #{inspect(link["type"])}") data diff --git a/priv/repo/migrations/20200505072231_remove_magic_key_field.exs b/priv/repo/migrations/20200505072231_remove_magic_key_field.exs new file mode 100644 index 000000000..2635e671b --- /dev/null +++ b/priv/repo/migrations/20200505072231_remove_magic_key_field.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.RemoveMagicKeyField do + use Ecto.Migration + + def change do + alter table(:users) do + remove(:magic_key, :string) + end + end +end diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 4b4282727..ce17f83d6 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -67,10 +67,10 @@ test "it work for AP-only user" do assert data["magic_key"] == nil assert data["salmon"] == nil - assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" + assert data["topic"] == nil assert data["subject"] == "acct:kPherox@mstdn.jp" assert data["ap_id"] == "https://mstdn.jp/users/kPherox" - assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}" + assert data["subscribe_address"] == nil end test "it works for friendica" do From f897da21158796eb3962e50add312d62165160fc Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 09:36:38 +0200 Subject: [PATCH 057/337] WebFinger: Add back in subscribe_address. It's used for remote following. --- lib/pleroma/web/web_finger/web_finger.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index b26453828..d0775fa28 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -88,6 +88,11 @@ def represent_user(user, "XML") do defp webfinger_from_xml(doc) do with subject <- XML.string_from_xpath("//Subject", doc), + subscribe_address <- + XML.string_from_xpath( + ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, + doc + ), ap_id <- XML.string_from_xpath( ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, @@ -95,6 +100,7 @@ defp webfinger_from_xml(doc) do ) do data = %{ "subject" => subject, + "subscribe_address" => subscribe_address, "ap_id" => ap_id } From 6a2905ccf08f89bd988b1bcd0788566930fbf17e Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 09:55:33 +0200 Subject: [PATCH 058/337] WebFinger Test: Add back test. --- test/web/web_finger/web_finger_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index ce17f83d6..f4884e0a2 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -70,7 +70,7 @@ test "it work for AP-only user" do assert data["topic"] == nil assert data["subject"] == "acct:kPherox@mstdn.jp" assert data["ap_id"] == "https://mstdn.jp/users/kPherox" - assert data["subscribe_address"] == nil + assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}" end test "it works for friendica" do From 6400998820084c7b81a53bbeb705b0eb2c0a0e1b Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 10:12:37 +0200 Subject: [PATCH 059/337] AP C2S: Restrict creation to `Note`s for now. --- .../web/activity_pub/activity_pub_controller.ex | 5 ++++- .../activity_pub/activity_pub_controller_test.exs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index f607931ab..504eed4f4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -396,7 +396,10 @@ def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ |> json(err) end - defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do + defp handle_user_activity( + %User{} = user, + %{"type" => "Create", "object" => %{"type" => "Note"}} = params + ) do object = params["object"] |> Map.merge(Map.take(params, ["to", "cc"])) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index a8f1f0e26..9a085ffc5 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -815,6 +815,21 @@ test "it inserts an incoming create activity into the database", %{ assert object["content"] == activity["object"]["content"] end + test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do + user = insert(:user) + + activity = + activity + |> put_in(["object", "type"], "Benis") + + _result = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(400) + end + test "it inserts an incoming sensitive activity into the database", %{ conn: conn, activity: activity From f21f53829339115e9a6cc9066d09026345047b43 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 10:38:59 +0200 Subject: [PATCH 060/337] LikeValidator: Add defaults for recipients back in. --- .../web/activity_pub/object_validators/like_validator.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index d835b052e..034f25492 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -20,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do field(:object, Types.ObjectID) field(:actor, Types.ObjectID) field(:context, :string) - field(:to, Types.Recipients) - field(:cc, Types.Recipients) + field(:to, Types.Recipients, default: []) + field(:cc, Types.Recipients, default: []) end def cast_and_validate(data) do From 142bf0957c64f76b9b511200544b1ccbcef5ba16 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 11:20:53 +0200 Subject: [PATCH 061/337] Transmogrifier: Extract EmojiReact tests. --- .../emoji_react_handling_test.exs | 55 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 37 ------------- 2 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs new file mode 100644 index 000000000..9f4f6b296 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == other_user.ap_id + assert data["type"] == "EmojiReact" + assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" + assert data["object"] == activity.data["object"] + assert data["content"] == "👌" + end + + test "it reject invalid emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction-too-long.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + assert :error = Transmogrifier.handle_incoming(data) + + data = + File.read!("test/fixtures/emoji-reaction-no-emoji.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + assert :error = Transmogrifier.handle_incoming(data) + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 23efa4be6..c1c3ff9d2 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -325,43 +325,6 @@ test "it cleans up incoming notices which are not really DMs" do assert object_data["cc"] == to end - test "it works for incoming emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "EmojiReact" - assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" - assert data["object"] == activity.data["object"] - assert data["content"] == "👌" - end - - test "it reject invalid emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction-too-long.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert :error = Transmogrifier.handle_incoming(data) - - data = - File.read!("test/fixtures/emoji-reaction-no-emoji.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert :error = Transmogrifier.handle_incoming(data) - end - test "it works for incoming emoji reaction undos" do user = insert(:user) From ad771546d886171ea8c3e7694fad393eaa5a2017 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 12:11:46 +0200 Subject: [PATCH 062/337] Transmogrifier: Move emoji reactions to common pipeline. --- lib/pleroma/web/activity_pub/builder.ex | 12 +++ .../web/activity_pub/object_validator.ex | 11 +++ .../emoji_react_validator.ex | 81 +++++++++++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 12 +++ .../web/activity_pub/transmogrifier.ex | 23 +----- .../activity_pub/object_validator_test.exs | 41 ++++++++++ test/web/activity_pub/side_effects_test.exs | 27 +++++++ .../emoji_react_handling_test.exs | 10 ++- 8 files changed, 193 insertions(+), 24 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 429a510b8..2a763645c 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -10,6 +10,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()} + def emoji_react(actor, object, emoji) do + with {:ok, data, meta} <- like(actor, object) do + data = + data + |> Map.put("content", emoji) + |> Map.put("type", "EmojiReact") + + {:ok, data, meta} + end + end + @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"]) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index dc4bce059..8246558ed 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) @@ -24,6 +25,16 @@ def validate(%{"type" => "Like"} = object, meta) do end end + def validate(%{"type" => "EmojiReact"} = object, meta) do + with {:ok, object} <- + object + |> EmojiReactValidator.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) diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex new file mode 100644 index 000000000..e87519c59 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -0,0 +1,81 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do + use Ecto.Schema + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + + 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(:content, :string) + field(:to, {:array, :string}, default: []) + field(:cc, {:array, :string}, default: []) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + |> fix_after_cast() + end + + def fix_after_cast(cng) do + cng + |> fix_context() + end + + def fix_context(cng) do + object = get_field(cng, :object) + + with nil <- get_field(cng, :context), + %Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do + cng + |> put_change(:context, context) + else + _ -> + cng + end + end + + def validate_emoji(cng) do + content = get_field(cng, :content) + + if Pleroma.Emoji.is_unicode_emoji?(content) do + cng + else + cng + |> add_error(:content, "must be a single character emoji") + end + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["EmojiReact"]) + |> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content]) + |> validate_actor_presence() + |> validate_object_presence() + |> validate_emoji() + end +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 6a8f1af96..b15343c07 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -23,6 +23,18 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do {:ok, object, meta} end + # Tasks this handles: + # - Add reaction to object + # - Set up notification + def handle(%{data: %{"type" => "EmojiReact"}} = object, meta) do + reacted_object = Object.get_by_ap_id(object.data["object"]) + Utils.add_emoji_reaction_to_object(object, reacted_object) + + Notification.create_notifications(object) + + {:ok, object, meta} + end + # Nothing to do def handle(object, meta) do {:ok, object, meta} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 581e7040b..81e763f88 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -656,7 +656,7 @@ def handle_incoming( |> handle_incoming(options) end - def handle_incoming(%{"type" => "Like"} = data, _options) do + def handle_incoming(%{"type" => type} = data, _options) when type in ["Like", "EmojiReact"] do with :ok <- ObjectValidator.fetch_actor_and_object(data), {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do @@ -666,27 +666,6 @@ def handle_incoming(%{"type" => "Like"} = data, _options) do end end - def handle_incoming( - %{ - "type" => "EmojiReact", - "object" => object_id, - "actor" => _actor, - "id" => id, - "content" => emoji - } = 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.react_with_emoji(actor, object, emoji, activity_id: id, local: false) do - {:ok, activity} - else - _e -> :error - end - end - def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, _options diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 93989e28a..a7ad8e646 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.Utils @@ -8,6 +9,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do import Pleroma.Factory + describe "EmojiReacts" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) + + object = Pleroma.Object.get_by_ap_id(post_activity.data["object"]) + + {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌") + + %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react} + end + + test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do + assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, []) + end + + test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do + without_content = + valid_emoji_react + |> Map.delete("content") + + {:error, cng} = ObjectValidator.validate(without_content, []) + + refute cng.valid? + assert {:content, {"can't be blank", [validation: :required]}} in cng.errors + end + + test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do + without_emoji_content = + valid_emoji_react + |> Map.put("content", "x") + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:content, {"must be a single character emoji", []}} in cng.errors + end + end + describe "likes" do setup do user = insert(:user) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 0b6b55156..9271d5ba1 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -15,6 +15,33 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do import Pleroma.Factory + describe "EmojiReact objects" do + setup do + poster = insert(:user) + user = insert(:user) + + {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) + + {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌") + {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true) + + %{emoji_react: emoji_react, user: user, poster: poster} + end + + test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do + {:ok, emoji_react, _} = SideEffects.handle(emoji_react) + object = Object.get_by_ap_id(emoji_react.data["object"]) + + assert object.data["reaction_count"] == 1 + assert ["👌", [user.ap_id]] in object.data["reactions"] + end + + test "creates a notification", %{emoji_react: emoji_react, poster: poster} do + {:ok, emoji_react, _} = SideEffects.handle(emoji_react) + assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id) + end + end + describe "like objects" do setup do poster = insert(:user) diff --git a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs index 9f4f6b296..6988e3e0a 100644 --- a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI @@ -29,6 +30,11 @@ test "it works for incoming emoji reactions" do assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" assert data["object"] == activity.data["object"] assert data["content"] == "👌" + + object = Object.get_by_ap_id(data["object"]) + + assert object.data["reaction_count"] == 1 + assert match?([["👌", _]], object.data["reactions"]) end test "it reject invalid emoji reactions" do @@ -42,7 +48,7 @@ test "it reject invalid emoji reactions" do |> Map.put("object", activity.data["object"]) |> Map.put("actor", other_user.ap_id) - assert :error = Transmogrifier.handle_incoming(data) + assert {:error, _} = Transmogrifier.handle_incoming(data) data = File.read!("test/fixtures/emoji-reaction-no-emoji.json") @@ -50,6 +56,6 @@ test "it reject invalid emoji reactions" do |> Map.put("object", activity.data["object"]) |> Map.put("actor", other_user.ap_id) - assert :error = Transmogrifier.handle_incoming(data) + assert {:error, _} = Transmogrifier.handle_incoming(data) end end From db55dc944581b1d4b50d1608b2e991050ea29bb3 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 12:28:28 +0200 Subject: [PATCH 063/337] ActivityPub: Remove `react_with_emoji`. --- lib/pleroma/web/activity_pub/activity_pub.ex | 24 ------- .../web/activity_pub/object_validator.ex | 2 +- lib/pleroma/web/common_api/common_api.ex | 6 +- .../controllers/pleroma_api_controller.ex | 2 +- test/notification_test.exs | 2 +- test/web/activity_pub/activity_pub_test.exs | 71 +------------------ test/web/activity_pub/transmogrifier_test.exs | 4 +- test/web/common_api/common_api_test.exs | 4 +- .../views/notification_view_test.exs | 2 +- .../mastodon_api/views/status_view_test.exs | 6 +- .../pleroma_api_controller_test.exs | 10 +-- 11 files changed, 23 insertions(+), 110 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1c21d78af..4c6ac9241 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -349,30 +349,6 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do end end - @spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) :: - {:ok, Activity.t(), Object.t()} | {:error, any()} - def react_with_emoji(user, object, emoji, options \\ []) do - with {:ok, result} <- - Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do - result - end - end - - defp do_react_with_emoji(user, object, emoji, options) do - with local <- Keyword.get(options, :local, true), - activity_id <- Keyword.get(options, :activity_id, nil), - true <- Pleroma.Emoji.is_unicode_emoji?(emoji), - reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), - {:ok, activity} <- insert(reaction_data, local), - {:ok, object} <- add_emoji_reaction_to_object(activity, object), - :ok <- maybe_federate(activity) do - {:ok, activity, object} - else - false -> {:error, false} - {:error, error} -> Repo.rollback(error) - end - end - @spec unreact_with_emoji(User.t(), String.t(), keyword()) :: {:ok, Activity.t(), Object.t()} | {:error, any()} def unreact_with_emoji(user, reaction_id, options \\ []) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 8246558ed..d730cb062 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User - alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index f9db97d24..192c84eda 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -177,8 +177,10 @@ def unfavorite(id, user) do def react_with_emoji(id, user, emoji) do with %Activity{} = activity <- Activity.get_by_id(id), - object <- Object.normalize(activity) do - ActivityPub.react_with_emoji(user, object, emoji) + object <- Object.normalize(activity), + {:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji), + {:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do + {:ok, activity} else _ -> {:error, dgettext("errors", "Could not add reaction emoji")} 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 1bdb3aa4d..6688d7caf 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -86,7 +86,7 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} end def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do - with {:ok, _activity, _object} <- CommonAPI.react_with_emoji(activity_id, user, emoji), + with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji), activity <- Activity.get_by_id(activity_id) do conn |> put_view(StatusView) diff --git a/test/notification_test.exs b/test/notification_test.exs index 601a6c0ca..bd562c85c 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -24,7 +24,7 @@ test "creates a notification for an emoji reaction" do other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"}) - {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") {:ok, [notification]} = Notification.create_notifications(activity) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 84ead93bb..1ac4f9896 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -874,71 +874,6 @@ test "returns reblogs for users for whom reblogs have not been muted" do end end - describe "react to an object" do - test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do - Config.put([:instance, :federating], true) - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert called(Federator.publish(reaction_activity)) - end - - test "adds an emoji reaction activity to the db" do - user = insert(:user) - reactor = insert(:user) - third_user = insert(:user) - fourth_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert reaction_activity - - assert reaction_activity.data["actor"] == reactor.ap_id - assert reaction_activity.data["type"] == "EmojiReact" - assert reaction_activity.data["content"] == "🔥" - assert reaction_activity.data["object"] == object.data["id"] - assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] - assert reaction_activity.data["context"] == object.data["context"] - assert object.data["reaction_count"] == 1 - assert object.data["reactions"] == [["🔥", [reactor.ap_id]]] - - {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕") - - assert object.data["reaction_count"] == 2 - assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]] - - {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥") - - assert object.data["reaction_count"] == 3 - - assert object.data["reactions"] == [ - ["🔥", [fourth_user.ap_id, reactor.ap_id]], - ["☕", [third_user.ap_id]] - ] - end - - test "reverts emoji reaction on error" do - [user, reactor] = insert_list(2, :user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"}) - object = Object.normalize(activity) - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.react_with_emoji(reactor, object, "😀") - end - - object = Object.get_by_ap_id(object.data["id"]) - refute object.data["reaction_count"] - refute object.data["reactions"] - end - end - describe "unreacting to an object" do test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do Config.put([:instance, :federating], true) @@ -947,7 +882,7 @@ test "reverts emoji reaction on error" do {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) assert object = Object.normalize(activity) - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥") assert called(Federator.publish(reaction_activity)) @@ -963,7 +898,7 @@ test "adds an undo activity to the db" do {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) assert object = Object.normalize(activity) - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "🔥") {:ok, unreaction_activity, _object} = ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) @@ -981,7 +916,7 @@ test "reverts emoji unreact on error" do {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"}) object = Object.normalize(activity) - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "😀") + {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, reactor, "😀") with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do assert {:error, :reverted} = diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index c1c3ff9d2..7deac2909 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -329,7 +329,7 @@ test "it works for incoming emoji reaction undos" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌") + {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌") data = File.read!("test/fixtures/mastodon-undo-like.json") @@ -562,7 +562,7 @@ test "it strips internal likes" do test "it strips internal reactions" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢") %{object: object} = Activity.get_by_id_with_object(activity.id) assert Map.has_key?(object.data, "reactions") diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index bc0c1a791..74171fcd9 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -278,7 +278,7 @@ test "reacting to a status with an emoji" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍") assert reaction.data["actor"] == user.ap_id assert reaction.data["content"] == "👍" @@ -293,7 +293,7 @@ test "unreacting to a status with an emoji" do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍") {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index c3ec9dfec..0806269a2 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -156,7 +156,7 @@ test "EmojiReact notification" do other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) - {:ok, _activity, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") activity = Repo.get(Activity, activity.id) diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 6791c2fb0..b64370c3f 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -32,9 +32,9 @@ test "has an emoji reaction list" do third_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"}) - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") activity = Repo.get(Activity, activity.id) status = StatusView.render("show.json", activity: activity) 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 61a1689b9..593aa92b2 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -41,7 +41,7 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) - {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") result = conn @@ -71,8 +71,8 @@ test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do assert result == [] - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅") User.perform(:delete, doomed_user) @@ -109,8 +109,8 @@ test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do assert result == [] - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") result = conn From cc922e7d8ccbf22a0f7e0898a6ff4639123f0c7f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 14:44:29 +0400 Subject: [PATCH 064/337] Document configuration for Pleroma.Web.ApiSpec.CastAndValidate --- config/description.exs | 14 ++++++++++++++ docs/configuration/cheatsheet.md | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/config/description.exs b/config/description.exs index 9d8e3b93c..72bb4d436 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3194,5 +3194,19 @@ ] } ] + }, + %{ + group: :pleroma, + key: Pleroma.Web.ApiSpec.CastAndValidate, + type: :group, + children: [ + %{ + key: :strict, + type: :boolean, + description: + "Enables strict input validation (useful in development, not recommended in production)", + suggestions: [false] + } + ] } ] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 681ab6b93..705c4c15e 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -924,4 +924,8 @@ Restrict access for unauthenticated users to timelines (public and federate), us * `remote` * `activities` - statuses * `local` - * `remote` \ No newline at end of file + * `remote` + +## Pleroma.Web.ApiSpec.CastAndValidate + +* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`. From d20152700470c9b84a9404193ff08dd6d90b97a3 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 11:17:44 +0000 Subject: [PATCH 065/337] Apply suggestion to lib/pleroma/web/web_finger/web_finger.ex --- lib/pleroma/web/web_finger/web_finger.ex | 34 +++++++++++------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index d0775fa28..84ece1be2 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -87,25 +87,23 @@ def represent_user(user, "XML") do end defp webfinger_from_xml(doc) do - with subject <- XML.string_from_xpath("//Subject", doc), - subscribe_address <- - XML.string_from_xpath( - ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, - doc - ), - ap_id <- - XML.string_from_xpath( - ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, - doc - ) do - data = %{ - "subject" => subject, - "subscribe_address" => subscribe_address, - "ap_id" => ap_id - } + subject = XML.string_from_xpath("//Subject", doc) - {:ok, data} - end + subscribe_address = + ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template} + |> XML.string_from_xpath(doc) + + ap_id = + ~s{//Link[@rel="self" and @type="application/activity+json"]/@href} + |> XML.string_from_xpath(doc) + + data = %{ + "subject" => subject, + "subscribe_address" => subscribe_address, + "ap_id" => ap_id + } + + {:ok, data} end defp webfinger_from_json(doc) do From 8b2457bdbf8791923701b0b015f6ddf2e7c89bf7 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 13:25:34 +0200 Subject: [PATCH 066/337] Transmogrifier tests: Extract Undo handling --- .../transmogrifier/undo_handling_test.exs | 185 ++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 161 --------------- 2 files changed, 185 insertions(+), 161 deletions(-) create mode 100644 test/web/activity_pub/transmogrifier/undo_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs new file mode 100644 index 000000000..a9ebfdb18 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming emoji reaction undos" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌") + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", reaction_activity.data["id"]) + |> Map.put("actor", user.ap_id) + + {:ok, activity} = Transmogrifier.handle_incoming(data) + + assert activity.actor == user.ap_id + assert activity.data["id"] == data["id"] + assert activity.data["type"] == "Undo" + end + + test "it returns an error for incoming unlikes wihout a like activity" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert Transmogrifier.handle_incoming(data) == :error + end + + test "it works for incoming unlikes with an existing like activity" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _liker = insert(:user, ap_id: like_data["actor"], local: false) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + end + + test "it works for incoming unlikes with an existing like activity and a compact object" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _liker = insert(:user, ap_id: like_data["actor"], local: false) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data["id"]) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + end + + test "it works for incoming unannounces with an existing notice" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + + announce_data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _announcer = insert(:user, ap_id: announce_data["actor"], local: false) + + {:ok, %Activity{data: announce_data, local: false}} = + Transmogrifier.handle_incoming(announce_data) + + data = + File.read!("test/fixtures/mastodon-undo-announce.json") + |> Poison.decode!() + |> Map.put("object", announce_data) + |> Map.put("actor", announce_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Undo" + assert object_data = data["object"] + assert object_data["type"] == "Announce" + assert object_data["object"] == activity.data["object"] + + assert object_data["id"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + end + + test "it works for incomming unfollows with an existing follow" do + user = insert(:user) + + follow_data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + _follower = insert(:user, ap_id: follow_data["actor"], local: false) + + {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data) + + data = + File.read!("test/fixtures/mastodon-unfollow-activity.json") + |> Poison.decode!() + |> Map.put("object", follow_data) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Undo" + assert data["object"]["type"] == "Follow" + assert data["object"]["object"] == user.ap_id + assert data["actor"] == "http://mastodon.example.org/users/admin" + + refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) + end + + test "it works for incoming unblocks with an existing block" do + user = insert(:user) + + block_data = + File.read!("test/fixtures/mastodon-block-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + _blocker = insert(:user, ap_id: block_data["actor"], local: false) + + {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data) + + data = + File.read!("test/fixtures/mastodon-unblock-activity.json") + |> Poison.decode!() + |> Map.put("object", block_data) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + assert data["type"] == "Undo" + assert data["object"]["type"] == "Block" + assert data["object"]["object"] == user.ap_id + assert data["actor"] == "http://mastodon.example.org/users/admin" + + blocker = User.get_cached_by_ap_id(data["actor"]) + + refute User.blocks?(blocker, user) + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 23efa4be6..a315ff42d 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -362,87 +362,6 @@ test "it reject invalid emoji reactions" do assert :error = Transmogrifier.handle_incoming(data) end - test "it works for incoming emoji reaction undos" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌") - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", reaction_activity.data["id"]) - |> Map.put("actor", user.ap_id) - - {:ok, activity} = Transmogrifier.handle_incoming(data) - - assert activity.actor == user.ap_id - assert activity.data["id"] == data["id"] - assert activity.data["type"] == "Undo" - end - - test "it returns an error for incoming unlikes wihout a like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert Transmogrifier.handle_incoming(data) == :error - end - - test "it works for incoming unlikes with an existing like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" - end - - test "it works for incoming unlikes with an existing like activity and a compact object" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data["id"]) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" - end - test "it works for incoming announces" do data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() @@ -844,60 +763,6 @@ test "it fails for incoming user deletes with spoofed origin" do assert User.get_cached_by_ap_id(ap_id) end - test "it works for incoming unannounces with an existing notice" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) - - announce_data = - File.read!("test/fixtures/mastodon-announce.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: announce_data, local: false}} = - Transmogrifier.handle_incoming(announce_data) - - data = - File.read!("test/fixtures/mastodon-undo-announce.json") - |> Poison.decode!() - |> Map.put("object", announce_data) - |> Map.put("actor", announce_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["type"] == "Undo" - assert object_data = data["object"] - assert object_data["type"] == "Announce" - assert object_data["object"] == activity.data["object"] - - assert object_data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - end - - test "it works for incomming unfollows with an existing follow" do - user = insert(:user) - - follow_data = - File.read!("test/fixtures/mastodon-follow-activity.json") - |> Poison.decode!() - |> Map.put("object", user.ap_id) - - {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data) - - data = - File.read!("test/fixtures/mastodon-unfollow-activity.json") - |> Poison.decode!() - |> Map.put("object", follow_data) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["type"] == "Undo" - assert data["object"]["type"] == "Follow" - assert data["object"]["object"] == user.ap_id - assert data["actor"] == "http://mastodon.example.org/users/admin" - - refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) - end - test "it works for incoming follows to locked account" do pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") user = insert(:user, locked: true) @@ -967,32 +832,6 @@ test "incoming blocks successfully tear down any follow relationship" do refute User.following?(blocked, blocker) end - test "it works for incoming unblocks with an existing block" do - user = insert(:user) - - block_data = - File.read!("test/fixtures/mastodon-block-activity.json") - |> Poison.decode!() - |> Map.put("object", user.ap_id) - - {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data) - - data = - File.read!("test/fixtures/mastodon-unblock-activity.json") - |> Poison.decode!() - |> Map.put("object", block_data) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - assert data["type"] == "Undo" - assert data["object"]["type"] == "Block" - assert data["object"]["object"] == user.ap_id - assert data["actor"] == "http://mastodon.example.org/users/admin" - - blocker = User.get_cached_by_ap_id(data["actor"]) - - refute User.blocks?(blocker, user) - end - test "it works for incoming accepts which were pre-accepted" do follower = insert(:user) followed = insert(:user) From f1da8882f971f932b65f655b6457759387dafe51 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 14:17:47 +0200 Subject: [PATCH 067/337] UndoValidator: Add UndoValidator. --- lib/pleroma/web/activity_pub/builder.ex | 13 ++++ .../web/activity_pub/object_validator.ex | 9 +++ .../object_validators/common_validations.ex | 3 +- .../object_validators/undo_validator.ex | 62 +++++++++++++++++++ .../activity_pub/object_validator_test.exs | 41 ++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/undo_validator.ex diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 429a510b8..380d8f565 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -10,6 +10,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()} + def undo(actor, object) do + {:ok, + %{ + "id" => Utils.generate_activity_id(), + "actor" => actor.ap_id, + "type" => "Undo", + "object" => object.data["id"], + "to" => object.data["to"] || [], + "cc" => object.data["cc"] || [] + }, []} + end + @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"]) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index dc4bce059..b6937d2e1 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,10 +12,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) + def validate(%{"type" => "Undo"} = object, meta) do + with {:ok, object} <- + object |> UndoValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object |> Map.from_struct()) + {:ok, object, meta} + end + end + def validate(%{"type" => "Like"} = object, meta) do with {:ok, object} <- object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index b479c3918..067ee4f9a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do import Ecto.Changeset + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.User @@ -22,7 +23,7 @@ def validate_actor_presence(cng, field_name \\ :actor) do 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 + if Object.get_cached_by_ap_id(object) || Activity.get_by_ap_id(object) do [] else [{field_name, "can't find object"}] diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex new file mode 100644 index 000000000..d0ba418e8 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do + use Ecto.Schema + + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + + 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(:to, {:array, :string}, default: []) + field(:cc, {:array, :string}, default: []) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Undo"]) + |> validate_required([:id, :type, :object, :actor, :to, :cc]) + |> validate_actor_presence() + |> validate_object_presence() + |> validate_undo_rights() + end + + def validate_undo_rights(cng) do + actor = get_field(cng, :actor) + object = get_field(cng, :object) + + with %Activity{data: %{"actor" => object_actor}} <- Activity.get_by_ap_id(object), + true <- object_actor != actor do + cng + |> add_error(:actor, "not the same as object actor") + else + _ -> cng + end + end +end diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 93989e28a..8626e127e 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,6 +1,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.Utils @@ -8,6 +9,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do import Pleroma.Factory + describe "Undos" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) + {:ok, like} = CommonAPI.favorite(user, post_activity.id) + {:ok, valid_like_undo, []} = Builder.undo(user, like) + + %{user: user, like: like, valid_like_undo: valid_like_undo} + end + + test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do + assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, []) + end + + test "it does not validate if the actor of the undo is not the actor of the object", %{ + valid_like_undo: valid_like_undo + } do + other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") + + bad_actor = + valid_like_undo + |> Map.put("actor", other_user.ap_id) + + {:error, cng} = ObjectValidator.validate(bad_actor, []) + + assert {:actor, {"not the same as object actor", []}} in cng.errors + end + + test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do + missing_object = + valid_like_undo + |> Map.put("object", "https://gensokyo.2hu/objects/1") + + {:error, cng} = ObjectValidator.validate(missing_object, []) + + assert {:object, {"can't find object", []}} in cng.errors + assert length(cng.errors) == 1 + end + end + describe "likes" do setup do user = insert(:user) From d861b0790a62767b31b8a85862fc249a4f8ca542 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 16:43:00 +0400 Subject: [PATCH 068/337] Add OpenAPI spec for SubscriptionController --- lib/pleroma/web/api_spec.ex | 7 +- .../operations/subscription_operation.ex | 188 ++++++++++++++++++ .../web/api_spec/schemas/push_subscription.ex | 66 ++++++ .../controllers/subscription_controller.ex | 12 +- lib/pleroma/web/push/subscription.ex | 10 +- lib/pleroma/web/router.ex | 2 +- .../subscription_controller_test.exs | 28 +-- 7 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/subscription_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/push_subscription.ex diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index b3c1e3ea2..79fd5f871 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -39,7 +39,12 @@ def spec do password: %OpenApiSpex.OAuthFlow{ authorizationUrl: "/oauth/authorize", tokenUrl: "/oauth/token", - scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} + scopes: %{ + "read" => "read", + "write" => "write", + "follow" => "follow", + "push" => "push" + } } } } diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex new file mode 100644 index 000000000..663b8fa11 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex @@ -0,0 +1,188 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.PushSubscription + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["Push Subscriptions"], + summary: "Subscribe to push notifications", + description: + "Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.", + operationId: "SubscriptionController.create", + security: [%{"oAuth" => ["push"]}], + requestBody: Helpers.request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 400 => Operation.response("Error", "application/json", ApiError), + 403 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Push Subscriptions"], + summary: "Get current subscription", + description: "View the PushSubscription currently associated with this access token.", + operationId: "SubscriptionController.show", + security: [%{"oAuth" => ["push"]}], + responses: %{ + 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 403 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Push Subscriptions"], + summary: "Change types of notifications", + description: + "Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.", + operationId: "SubscriptionController.update", + security: [%{"oAuth" => ["push"]}], + requestBody: Helpers.request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 403 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Push Subscriptions"], + summary: "Remove current subscription", + description: "Removes the current Web Push API subscription.", + operationId: "SubscriptionController.delete", + security: [%{"oAuth" => ["push"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}), + 403 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "SubscriptionCreateRequest", + description: "POST body for creating a push subscription", + type: :object, + properties: %{ + subscription: %Schema{ + type: :object, + properties: %{ + endpoint: %Schema{ + type: :string, + description: "Endpoint URL that is called when a notification event occurs." + }, + keys: %Schema{ + type: :object, + properties: %{ + p256dh: %Schema{ + type: :string, + description: + "User agent public key. Base64 encoded string of public key of ECDH key using `prime256v1` curve." + }, + auth: %Schema{ + type: :string, + description: "Auth secret. Base64 encoded string of 16 bytes of random data." + } + }, + required: [:p256dh, :auth] + } + }, + required: [:endpoint, :keys] + }, + data: %Schema{ + type: :object, + properties: %{ + alerts: %Schema{ + type: :object, + properties: %{ + follow: %Schema{type: :boolean, description: "Receive follow notifications?"}, + favourite: %Schema{ + type: :boolean, + description: "Receive favourite notifications?" + }, + reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"}, + mention: %Schema{type: :boolean, description: "Receive mention notifications?"}, + poll: %Schema{type: :boolean, description: "Receive poll notifications?"} + } + } + } + } + }, + required: [:subscription], + example: %{ + "subscription" => %{ + "endpoint" => "https://example.com/example/1234", + "keys" => %{ + "auth" => "8eDyX_uCN0XRhSbY5hs7Hg==", + "p256dh" => + "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=" + } + }, + "data" => %{ + "alerts" => %{ + "follow" => true, + "mention" => true, + "poll" => false + } + } + } + } + end + + defp update_request do + %Schema{ + title: "SubscriptionUpdateRequest", + type: :object, + properties: %{ + data: %Schema{ + type: :object, + properties: %{ + alerts: %Schema{ + type: :object, + properties: %{ + follow: %Schema{type: :boolean, description: "Receive follow notifications?"}, + favourite: %Schema{ + type: :boolean, + description: "Receive favourite notifications?" + }, + reblog: %Schema{type: :boolean, description: "Receive reblog notifications?"}, + mention: %Schema{type: :boolean, description: "Receive mention notifications?"}, + poll: %Schema{type: :boolean, description: "Receive poll notifications?"} + } + } + } + } + }, + example: %{ + "data" => %{ + "alerts" => %{ + "follow" => true, + "favourite" => true, + "reblog" => true, + "mention" => true, + "poll" => true + } + } + } + } + end +end diff --git a/lib/pleroma/web/api_spec/schemas/push_subscription.ex b/lib/pleroma/web/api_spec/schemas/push_subscription.ex new file mode 100644 index 000000000..cc91b95b8 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/push_subscription.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.PushSubscription do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "PushSubscription", + description: "Response schema for a push subscription", + type: :object, + properties: %{ + id: %Schema{ + anyOf: [%Schema{type: :string}, %Schema{type: :integer}], + description: "The id of the push subscription in the database." + }, + endpoint: %Schema{type: :string, description: "Where push alerts will be sent to."}, + server_key: %Schema{type: :string, description: "The streaming server's VAPID key."}, + alerts: %Schema{ + type: :object, + description: "Which alerts should be delivered to the endpoint.", + properties: %{ + follow: %Schema{ + type: :boolean, + description: "Receive a push notification when someone has followed you?" + }, + favourite: %Schema{ + type: :boolean, + description: + "Receive a push notification when a status you created has been favourited by someone else?" + }, + reblog: %Schema{ + type: :boolean, + description: + "Receive a push notification when a status you created has been boosted by someone else?" + }, + mention: %Schema{ + type: :boolean, + description: + "Receive a push notification when someone else has mentioned you in a status?" + }, + poll: %Schema{ + type: :boolean, + description: + "Receive a push notification when a poll you voted in or created has ended? " + } + } + } + }, + example: %{ + "id" => "328_183", + "endpoint" => "https://yourdomain.example/listener", + "alerts" => %{ + "follow" => true, + "favourite" => true, + "reblog" => true, + "mention" => true, + "poll" => true + }, + "server_key" => + "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=" + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index d184ea1d0..34eac97c5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -11,14 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do action_fallback(:errors) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(:restrict_push_enabled) plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) - plug(:restrict_push_enabled) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation # Creates PushSubscription # POST /api/v1/push/subscription # - def create(%{assigns: %{user: user, token: token}} = conn, params) do + def create(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do with {:ok, _} <- Subscription.delete_if_exists(user, token), {:ok, subscription} <- Subscription.create(user, token, params) do render(conn, "show.json", subscription: subscription) @@ -28,7 +30,7 @@ def create(%{assigns: %{user: user, token: token}} = conn, params) do # Gets PushSubscription # GET /api/v1/push/subscription # - def get(%{assigns: %{user: user, token: token}} = conn, _params) do + def show(%{assigns: %{user: user, token: token}} = conn, _params) do with {:ok, subscription} <- Subscription.get(user, token) do render(conn, "show.json", subscription: subscription) end @@ -37,7 +39,7 @@ def get(%{assigns: %{user: user, token: token}} = conn, _params) do # Updates PushSubscription # PUT /api/v1/push/subscription # - def update(%{assigns: %{user: user, token: token}} = conn, params) do + def update(%{assigns: %{user: user, token: token}, body_params: params} = conn, _) do with {:ok, subscription} <- Subscription.update(user, token, params) do render(conn, "show.json", subscription: subscription) end @@ -66,7 +68,7 @@ defp restrict_push_enabled(conn, _) do def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) - |> json(dgettext("errors", "Not found")) + |> json(%{error: dgettext("errors", "Record not found")}) end def errors(conn, _) do diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index b99b0c5fb..3e401a490 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -25,9 +25,9 @@ defmodule Pleroma.Web.Push.Subscription do timestamps() end - @supported_alert_types ~w[follow favourite mention reblog] + @supported_alert_types ~w[follow favourite mention reblog]a - defp alerts(%{"data" => %{"alerts" => alerts}}) do + defp alerts(%{data: %{alerts: alerts}}) do alerts = Map.take(alerts, @supported_alert_types) %{"alerts" => alerts} end @@ -44,9 +44,9 @@ def create( %User{} = user, %Token{} = token, %{ - "subscription" => %{ - "endpoint" => endpoint, - "keys" => %{"auth" => key_auth, "p256dh" => key_p256dh} + subscription: %{ + endpoint: endpoint, + keys: %{auth: key_auth, p256dh: key_p256dh} } } = params ) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5b00243e9..eda8320ea 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -426,7 +426,7 @@ defmodule Pleroma.Web.Router do post("/statuses/:id/unmute", StatusController, :unmute_conversation) post("/push/subscription", SubscriptionController, :create) - get("/push/subscription", SubscriptionController, :get) + get("/push/subscription", SubscriptionController, :show) put("/push/subscription", SubscriptionController, :update) delete("/push/subscription", SubscriptionController, :delete) diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 5682498c0..4aa260663 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory + alias Pleroma.Web.Push alias Pleroma.Web.Push.Subscription @@ -27,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do build_conn() |> assign(:user, user) |> assign(:token, token) + |> put_req_header("content-type", "application/json") %{conn: conn, user: user, token: token} end @@ -47,8 +49,8 @@ defmacro assert_error_when_disable_push(do: yield) do test "returns error when push disabled ", %{conn: conn} do assert_error_when_disable_push do conn - |> post("/api/v1/push/subscription", %{}) - |> json_response(403) + |> post("/api/v1/push/subscription", %{subscription: @sub}) + |> json_response_and_validate_schema(403) end end @@ -59,7 +61,7 @@ test "successful creation", %{conn: conn} do "data" => %{"alerts" => %{"mention" => true, "test" => true}}, "subscription" => @sub }) - |> json_response(200) + |> json_response_and_validate_schema(200) [subscription] = Pleroma.Repo.all(Subscription) @@ -77,7 +79,7 @@ test "returns error when push disabled ", %{conn: conn} do assert_error_when_disable_push do conn |> get("/api/v1/push/subscription", %{}) - |> json_response(403) + |> json_response_and_validate_schema(403) end end @@ -85,9 +87,9 @@ test "returns error when user hasn't subscription", %{conn: conn} do res = conn |> get("/api/v1/push/subscription", %{}) - |> json_response(404) + |> json_response_and_validate_schema(404) - assert "Not found" == res + assert %{"error" => "Record not found"} == res end test "returns a user subsciption", %{conn: conn, user: user, token: token} do @@ -101,7 +103,7 @@ test "returns a user subsciption", %{conn: conn, user: user, token: token} do res = conn |> get("/api/v1/push/subscription", %{}) - |> json_response(200) + |> json_response_and_validate_schema(200) expect = %{ "alerts" => %{"mention" => true}, @@ -130,7 +132,7 @@ test "returns error when push disabled ", %{conn: conn} do assert_error_when_disable_push do conn |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}}) - |> json_response(403) + |> json_response_and_validate_schema(403) end end @@ -140,7 +142,7 @@ test "returns updated subsciption", %{conn: conn, subscription: subscription} do |> put("/api/v1/push/subscription", %{ data: %{"alerts" => %{"mention" => false, "follow" => true}} }) - |> json_response(200) + |> json_response_and_validate_schema(200) expect = %{ "alerts" => %{"follow" => true, "mention" => false}, @@ -158,7 +160,7 @@ test "returns error when push disabled ", %{conn: conn} do assert_error_when_disable_push do conn |> delete("/api/v1/push/subscription", %{}) - |> json_response(403) + |> json_response_and_validate_schema(403) end end @@ -166,9 +168,9 @@ test "returns error when user hasn't subscription", %{conn: conn} do res = conn |> delete("/api/v1/push/subscription", %{}) - |> json_response(404) + |> json_response_and_validate_schema(404) - assert "Not found" == res + assert %{"error" => "Record not found"} == res end test "returns empty result and delete user subsciption", %{ @@ -186,7 +188,7 @@ test "returns empty result and delete user subsciption", %{ res = conn |> delete("/api/v1/push/subscription", %{}) - |> json_response(200) + |> json_response_and_validate_schema(200) assert %{} == res refute Pleroma.Repo.get(Subscription, subscription.id) From 8096565653f262844214d715228c31d4ef761f57 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 14 Apr 2020 22:50:29 +0400 Subject: [PATCH 069/337] Add OpenAPI spec for MarkerController --- .../api_spec/operations/marker_operation.ex | 52 +++++++++++++++++++ lib/pleroma/web/api_spec/schemas/marker.ex | 31 +++++++++++ .../web/api_spec/schemas/markers_response.ex | 35 +++++++++++++ .../schemas/markers_upsert_request.ex | 35 +++++++++++++ .../controllers/marker_controller.ex | 12 ++++- .../controllers/marker_controller_test.exs | 12 ++++- 6 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/marker_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/marker.ex create mode 100644 lib/pleroma/web/api_spec/schemas/markers_response.ex create mode 100644 lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex diff --git a/lib/pleroma/web/api_spec/operations/marker_operation.ex b/lib/pleroma/web/api_spec/operations/marker_operation.ex new file mode 100644 index 000000000..60adc7c7d --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/marker_operation.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.MarkerOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.MarkersResponse + alias Pleroma.Web.ApiSpec.Schemas.MarkersUpsertRequest + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["markers"], + summary: "Get saved timeline position", + security: [%{"oAuth" => ["read:statuses"]}], + operationId: "MarkerController.index", + parameters: [ + Operation.parameter( + :timeline, + :query, + %Schema{ + type: :array, + items: %Schema{type: :string, enum: ["home", "notifications"]} + }, + "Array of markers to fetch. If not provided, an empty object will be returned." + ) + ], + responses: %{ + 200 => Operation.response("Marker", "application/json", MarkersResponse) + } + } + end + + def upsert_operation do + %Operation{ + tags: ["markers"], + summary: "Save position in timeline", + operationId: "MarkerController.upsert", + requestBody: Helpers.request_body("Parameters", MarkersUpsertRequest, required: true), + security: [%{"oAuth" => ["follow", "write:blocks"]}], + responses: %{ + 200 => Operation.response("Marker", "application/json", MarkersResponse) + } + } + end +end diff --git a/lib/pleroma/web/api_spec/schemas/marker.ex b/lib/pleroma/web/api_spec/schemas/marker.ex new file mode 100644 index 000000000..64fca5973 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/marker.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Marker do + require OpenApiSpex + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + title: "Marker", + description: "Schema for a marker", + type: :object, + properties: %{ + last_read_id: %Schema{type: :string}, + version: %Schema{type: :integer}, + updated_at: %Schema{type: :string}, + pleroma: %Schema{ + type: :object, + properties: %{ + unread_count: %Schema{type: :integer} + } + } + }, + example: %{ + "last_read_id" => "35098814", + "version" => 361, + "updated_at" => "2019-11-26T22:37:25.239Z", + "pleroma" => %{"unread_count" => 5} + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/markers_response.ex b/lib/pleroma/web/api_spec/schemas/markers_response.ex new file mode 100644 index 000000000..cb1121931 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/markers_response.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.MarkersResponse do + require OpenApiSpex + alias OpenApiSpex.Schema + + alias Pleroma.Web.ApiSpec.Schemas.Marker + + OpenApiSpex.schema(%{ + title: "MarkersResponse", + description: "Response schema for markers", + type: :object, + properties: %{ + notifications: %Schema{allOf: [Marker], nullable: true}, + home: %Schema{allOf: [Marker], nullable: true} + }, + items: %Schema{type: :string}, + example: %{ + "notifications" => %{ + "last_read_id" => "35098814", + "version" => 361, + "updated_at" => "2019-11-26T22:37:25.239Z", + "pleroma" => %{"unread_count" => 0} + }, + "home" => %{ + "last_read_id" => "103206604258487607", + "version" => 468, + "updated_at" => "2019-11-26T22:37:25.235Z", + "pleroma" => %{"unread_count" => 10} + } + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex b/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex new file mode 100644 index 000000000..97dcc24b4 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.MarkersUpsertRequest do + require OpenApiSpex + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + title: "MarkersUpsertRequest", + description: "Request schema for marker upsert", + type: :object, + properties: %{ + notifications: %Schema{ + type: :object, + properties: %{ + last_read_id: %Schema{type: :string} + } + }, + home: %Schema{ + type: :object, + properties: %{ + last_read_id: %Schema{type: :string} + } + } + }, + example: %{ + "home" => %{ + "last_read_id" => "103194548672408537", + "version" => 462, + "updated_at" => "2019-11-24T19:39:39.337Z" + } + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 9f9d4574e..b94171b36 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -15,15 +15,23 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(OpenApiSpex.Plug.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MarkerOperation # GET /api/v1/markers def index(%{assigns: %{user: user}} = conn, params) do - markers = Pleroma.Marker.get_markers(user, params["timeline"]) + markers = Pleroma.Marker.get_markers(user, params[:timeline]) render(conn, "markers.json", %{markers: markers}) end # POST /api/v1/markers - def upsert(%{assigns: %{user: user}} = conn, params) do + def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do + params = + params + |> Map.from_struct() + |> Map.new(fn {key, value} -> {to_string(key), value} end) + with {:ok, result} <- Pleroma.Marker.upsert(user, params), markers <- Map.values(result) do render(conn, "markers.json", %{markers: markers}) diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 919f295bd..1c85ed032 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -4,8 +4,10 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Web.ApiSpec import Pleroma.Factory + import OpenApiSpex.TestAssertions describe "GET /api/v1/markers" do test "gets markers with correct scopes", %{conn: conn} do @@ -22,7 +24,7 @@ test "gets markers with correct scopes", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, token) - |> get("/api/v1/markers", %{timeline: ["notifications"]}) + |> get("/api/v1/markers?timeline[]=notifications") |> json_response(200) assert response == %{ @@ -32,6 +34,8 @@ test "gets markers with correct scopes", %{conn: conn} do "version" => 0 } } + + assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "gets markers with missed scopes", %{conn: conn} do @@ -60,6 +64,7 @@ test "creates a marker with correct scopes", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, token) + |> put_req_header("content-type", "application/json") |> post("/api/v1/markers", %{ home: %{last_read_id: "777"}, notifications: %{"last_read_id" => "69420"} @@ -73,6 +78,8 @@ test "creates a marker with correct scopes", %{conn: conn} do "version" => 0 } } = response + + assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "updates exist marker", %{conn: conn} do @@ -89,6 +96,7 @@ test "updates exist marker", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, token) + |> put_req_header("content-type", "application/json") |> post("/api/v1/markers", %{ home: %{last_read_id: "777"}, notifications: %{"last_read_id" => "69888"} @@ -102,6 +110,8 @@ test "updates exist marker", %{conn: conn} do "version" => 0 } } + + assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "creates a marker with missed scopes", %{conn: conn} do From babcae7130d3bc75f85adeef1845997cd091eb84 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 16:45:34 +0400 Subject: [PATCH 070/337] Move single used schemas to Marker operation schema --- .../api_spec/operations/marker_operation.ex | 102 ++++++++++++++++-- lib/pleroma/web/api_spec/schemas/marker.ex | 31 ------ .../web/api_spec/schemas/markers_response.ex | 35 ------ .../schemas/markers_upsert_request.ex | 35 ------ .../controllers/marker_controller.ex | 8 +- .../web/mastodon_api/views/marker_view.ex | 13 +-- .../controllers/marker_controller_test.exs | 19 ++-- 7 files changed, 111 insertions(+), 132 deletions(-) delete mode 100644 lib/pleroma/web/api_spec/schemas/marker.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/markers_response.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex diff --git a/lib/pleroma/web/api_spec/operations/marker_operation.ex b/lib/pleroma/web/api_spec/operations/marker_operation.ex index 60adc7c7d..06620492a 100644 --- a/lib/pleroma/web/api_spec/operations/marker_operation.ex +++ b/lib/pleroma/web/api_spec/operations/marker_operation.ex @@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.MarkerOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers - alias Pleroma.Web.ApiSpec.Schemas.MarkersResponse - alias Pleroma.Web.ApiSpec.Schemas.MarkersUpsertRequest def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -16,7 +14,7 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["markers"], + tags: ["Markers"], summary: "Get saved timeline position", security: [%{"oAuth" => ["read:statuses"]}], operationId: "MarkerController.index", @@ -32,21 +30,111 @@ def index_operation do ) ], responses: %{ - 200 => Operation.response("Marker", "application/json", MarkersResponse) + 200 => Operation.response("Marker", "application/json", response()), + 403 => Operation.response("Error", "application/json", api_error()) } } end def upsert_operation do %Operation{ - tags: ["markers"], + tags: ["Markers"], summary: "Save position in timeline", operationId: "MarkerController.upsert", - requestBody: Helpers.request_body("Parameters", MarkersUpsertRequest, required: true), + requestBody: Helpers.request_body("Parameters", upsert_request(), required: true), security: [%{"oAuth" => ["follow", "write:blocks"]}], responses: %{ - 200 => Operation.response("Marker", "application/json", MarkersResponse) + 200 => Operation.response("Marker", "application/json", response()), + 403 => Operation.response("Error", "application/json", api_error()) } } end + + defp marker do + %Schema{ + title: "Marker", + description: "Schema for a marker", + type: :object, + properties: %{ + last_read_id: %Schema{type: :string}, + version: %Schema{type: :integer}, + updated_at: %Schema{type: :string}, + pleroma: %Schema{ + type: :object, + properties: %{ + unread_count: %Schema{type: :integer} + } + } + }, + example: %{ + "last_read_id" => "35098814", + "version" => 361, + "updated_at" => "2019-11-26T22:37:25.239Z", + "pleroma" => %{"unread_count" => 5} + } + } + end + + defp response do + %Schema{ + title: "MarkersResponse", + description: "Response schema for markers", + type: :object, + properties: %{ + notifications: %Schema{allOf: [marker()], nullable: true}, + home: %Schema{allOf: [marker()], nullable: true} + }, + items: %Schema{type: :string}, + example: %{ + "notifications" => %{ + "last_read_id" => "35098814", + "version" => 361, + "updated_at" => "2019-11-26T22:37:25.239Z", + "pleroma" => %{"unread_count" => 0} + }, + "home" => %{ + "last_read_id" => "103206604258487607", + "version" => 468, + "updated_at" => "2019-11-26T22:37:25.235Z", + "pleroma" => %{"unread_count" => 10} + } + } + } + end + + defp upsert_request do + %Schema{ + title: "MarkersUpsertRequest", + description: "Request schema for marker upsert", + type: :object, + properties: %{ + notifications: %Schema{ + type: :object, + properties: %{ + last_read_id: %Schema{type: :string} + } + }, + home: %Schema{ + type: :object, + properties: %{ + last_read_id: %Schema{type: :string} + } + } + }, + example: %{ + "home" => %{ + "last_read_id" => "103194548672408537", + "version" => 462, + "updated_at" => "2019-11-24T19:39:39.337Z" + } + } + } + end + + defp api_error do + %Schema{ + type: :object, + properties: %{error: %Schema{type: :string}} + } + end end diff --git a/lib/pleroma/web/api_spec/schemas/marker.ex b/lib/pleroma/web/api_spec/schemas/marker.ex deleted file mode 100644 index 64fca5973..000000000 --- a/lib/pleroma/web/api_spec/schemas/marker.ex +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.Marker do - require OpenApiSpex - alias OpenApiSpex.Schema - - OpenApiSpex.schema(%{ - title: "Marker", - description: "Schema for a marker", - type: :object, - properties: %{ - last_read_id: %Schema{type: :string}, - version: %Schema{type: :integer}, - updated_at: %Schema{type: :string}, - pleroma: %Schema{ - type: :object, - properties: %{ - unread_count: %Schema{type: :integer} - } - } - }, - example: %{ - "last_read_id" => "35098814", - "version" => 361, - "updated_at" => "2019-11-26T22:37:25.239Z", - "pleroma" => %{"unread_count" => 5} - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/markers_response.ex b/lib/pleroma/web/api_spec/schemas/markers_response.ex deleted file mode 100644 index cb1121931..000000000 --- a/lib/pleroma/web/api_spec/schemas/markers_response.ex +++ /dev/null @@ -1,35 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.MarkersResponse do - require OpenApiSpex - alias OpenApiSpex.Schema - - alias Pleroma.Web.ApiSpec.Schemas.Marker - - OpenApiSpex.schema(%{ - title: "MarkersResponse", - description: "Response schema for markers", - type: :object, - properties: %{ - notifications: %Schema{allOf: [Marker], nullable: true}, - home: %Schema{allOf: [Marker], nullable: true} - }, - items: %Schema{type: :string}, - example: %{ - "notifications" => %{ - "last_read_id" => "35098814", - "version" => 361, - "updated_at" => "2019-11-26T22:37:25.239Z", - "pleroma" => %{"unread_count" => 0} - }, - "home" => %{ - "last_read_id" => "103206604258487607", - "version" => 468, - "updated_at" => "2019-11-26T22:37:25.235Z", - "pleroma" => %{"unread_count" => 10} - } - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex b/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex deleted file mode 100644 index 97dcc24b4..000000000 --- a/lib/pleroma/web/api_spec/schemas/markers_upsert_request.ex +++ /dev/null @@ -1,35 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.MarkersUpsertRequest do - require OpenApiSpex - alias OpenApiSpex.Schema - - OpenApiSpex.schema(%{ - title: "MarkersUpsertRequest", - description: "Request schema for marker upsert", - type: :object, - properties: %{ - notifications: %Schema{ - type: :object, - properties: %{ - last_read_id: %Schema{type: :string} - } - }, - home: %Schema{ - type: :object, - properties: %{ - last_read_id: %Schema{type: :string} - } - } - }, - example: %{ - "home" => %{ - "last_read_id" => "103194548672408537", - "version" => 462, - "updated_at" => "2019-11-24T19:39:39.337Z" - } - } - }) -end diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index b94171b36..85310edfa 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do use Pleroma.Web, :controller alias Pleroma.Plugs.OAuthScopesPlug + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug( OAuthScopesPlug, %{scopes: ["read:statuses"]} @@ -15,7 +17,6 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(OpenApiSpex.Plug.CastAndValidate) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MarkerOperation @@ -27,10 +28,7 @@ def index(%{assigns: %{user: user}} = conn, params) do # POST /api/v1/markers def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do - params = - params - |> Map.from_struct() - |> Map.new(fn {key, value} -> {to_string(key), value} end) + params = Map.new(params, fn {key, value} -> {to_string(key), value} end) with {:ok, result} <- Pleroma.Marker.upsert(user, params), markers <- Map.values(result) do diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 985368fe5..9705b7a91 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -6,12 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.MarkerView do use Pleroma.Web, :view def render("markers.json", %{markers: markers}) do - Enum.reduce(markers, %{}, fn m, acc -> - Map.put_new(acc, m.timeline, %{ - last_read_id: m.last_read_id, - version: m.lock_version, - updated_at: NaiveDateTime.to_iso8601(m.updated_at) - }) + Map.new(markers, fn m -> + {m.timeline, + %{ + last_read_id: m.last_read_id, + version: m.lock_version, + updated_at: NaiveDateTime.to_iso8601(m.updated_at) + }} end) end end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 1c85ed032..bce719bea 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -4,10 +4,8 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Web.ApiSpec import Pleroma.Factory - import OpenApiSpex.TestAssertions describe "GET /api/v1/markers" do test "gets markers with correct scopes", %{conn: conn} do @@ -25,7 +23,7 @@ test "gets markers with correct scopes", %{conn: conn} do |> assign(:user, user) |> assign(:token, token) |> get("/api/v1/markers?timeline[]=notifications") - |> json_response(200) + |> json_response_and_validate_schema(200) assert response == %{ "notifications" => %{ @@ -34,8 +32,6 @@ test "gets markers with correct scopes", %{conn: conn} do "version" => 0 } } - - assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "gets markers with missed scopes", %{conn: conn} do @@ -49,7 +45,7 @@ test "gets markers with missed scopes", %{conn: conn} do |> assign(:user, user) |> assign(:token, token) |> get("/api/v1/markers", %{timeline: ["notifications"]}) - |> json_response(403) + |> json_response_and_validate_schema(403) assert response == %{"error" => "Insufficient permissions: read:statuses."} end @@ -69,7 +65,7 @@ test "creates a marker with correct scopes", %{conn: conn} do home: %{last_read_id: "777"}, notifications: %{"last_read_id" => "69420"} }) - |> json_response(200) + |> json_response_and_validate_schema(200) assert %{ "notifications" => %{ @@ -78,8 +74,6 @@ test "creates a marker with correct scopes", %{conn: conn} do "version" => 0 } } = response - - assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "updates exist marker", %{conn: conn} do @@ -101,7 +95,7 @@ test "updates exist marker", %{conn: conn} do home: %{last_read_id: "777"}, notifications: %{"last_read_id" => "69888"} }) - |> json_response(200) + |> json_response_and_validate_schema(200) assert response == %{ "notifications" => %{ @@ -110,8 +104,6 @@ test "updates exist marker", %{conn: conn} do "version" => 0 } } - - assert_schema(response, "MarkersResponse", ApiSpec.spec()) end test "creates a marker with missed scopes", %{conn: conn} do @@ -122,11 +114,12 @@ test "creates a marker with missed scopes", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, token) + |> put_req_header("content-type", "application/json") |> post("/api/v1/markers", %{ home: %{last_read_id: "777"}, notifications: %{"last_read_id" => "69420"} }) - |> json_response(403) + |> json_response_and_validate_schema(403) assert response == %{"error" => "Insufficient permissions: write:statuses."} end From 5ec6aad5670cf0888942a13e83b9ffd16e97dd18 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 17:05:34 +0400 Subject: [PATCH 071/337] Add OpenAPI spec for ListController --- .../api_spec/operations/account_operation.ex | 19 +- .../web/api_spec/operations/list_operation.ex | 189 ++++++++++++++++++ lib/pleroma/web/api_spec/schemas/list.ex | 23 +++ .../controllers/list_controller.ex | 26 +-- test/support/conn_case.ex | 2 +- .../controllers/list_controller_test.exs | 60 ++++-- 6 files changed, 266 insertions(+), 53 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/list_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/list.ex diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index fe9548b1b..470fc0215 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do alias Pleroma.Web.ApiSpec.Schemas.ActorType alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.List alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope @@ -646,28 +647,12 @@ defp mute_request do } end - defp list do - %Schema{ - title: "List", - description: "Response schema for a list", - type: :object, - properties: %{ - id: %Schema{type: :string}, - title: %Schema{type: :string} - }, - example: %{ - "id" => "123", - "title" => "my list" - } - } - end - defp array_of_lists do %Schema{ title: "ArrayOfLists", description: "Response schema for lists", type: :array, - items: list(), + items: List, example: [ %{"id" => "123", "title" => "my list"}, %{"id" => "1337", "title" => "anotehr list"} diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex new file mode 100644 index 000000000..bb903a379 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -0,0 +1,189 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ListOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.List + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Lists"], + summary: "Show user's lists", + description: "Fetch all lists that the user owns", + security: [%{"oAuth" => ["read:lists"]}], + operationId: "ListController.index", + responses: %{ + 200 => Operation.response("Array of List", "application/json", array_of_lists()) + } + } + end + + def create_operation do + %Operation{ + tags: ["Lists"], + summary: "Show a single list", + description: "Fetch the list with the given ID. Used for verifying the title of a list.", + operationId: "ListController.create", + requestBody: create_update_request(), + security: [%{"oAuth" => ["write:lists"]}], + responses: %{ + 200 => Operation.response("List", "application/json", List), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Lists"], + summary: "Show a single list", + description: "Fetch the list with the given ID. Used for verifying the title of a list.", + operationId: "ListController.show", + parameters: [id_param()], + security: [%{"oAuth" => ["read:lists"]}], + responses: %{ + 200 => Operation.response("List", "application/json", List), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Lists"], + summary: "Update a list", + description: "Change the title of a list", + operationId: "ListController.update", + parameters: [id_param()], + requestBody: create_update_request(), + security: [%{"oAuth" => ["write:lists"]}], + responses: %{ + 200 => Operation.response("List", "application/json", List), + 422 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Lists"], + summary: "Delete a list", + operationId: "ListController.delete", + parameters: [id_param()], + security: [%{"oAuth" => ["write:lists"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) + } + } + end + + def list_accounts_operation do + %Operation{ + tags: ["Lists"], + summary: "View accounts in list", + operationId: "ListController.list_accounts", + parameters: [id_param()], + security: [%{"oAuth" => ["read:lists"]}], + responses: %{ + 200 => + Operation.response("Array of Account", "application/json", %Schema{ + type: :array, + items: Account + }) + } + } + end + + def add_to_list_operation do + %Operation{ + tags: ["Lists"], + summary: "Add accounts to list", + description: + "Add accounts to the given list. Note that the user must be following these accounts.", + operationId: "ListController.add_to_list", + parameters: [id_param()], + requestBody: add_remove_accounts_request(), + security: [%{"oAuth" => ["write:lists"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) + } + } + end + + def remove_from_list_operation do + %Operation{ + tags: ["Lists"], + summary: "Remove accounts from list", + operationId: "ListController.remove_from_list", + parameters: [id_param()], + requestBody: add_remove_accounts_request(), + security: [%{"oAuth" => ["write:lists"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) + } + } + end + + defp array_of_lists do + %Schema{ + title: "ArrayOfLists", + description: "Response schema for lists", + type: :array, + items: List, + example: [ + %{"id" => "123", "title" => "my list"}, + %{"id" => "1337", "title" => "another list"} + ] + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "List ID", + example: "123", + required: true + ) + end + + defp create_update_request do + request_body( + "Parameters", + %Schema{ + description: "POST body for creating or updating a List", + type: :object, + properties: %{ + title: %Schema{type: :string, description: "List title"} + }, + required: [:title] + }, + required: true + ) + end + + defp add_remove_accounts_request do + request_body( + "Parameters", + %Schema{ + description: "POST body for adding/removing accounts to/from a List", + type: :object, + properties: %{ + account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID} + }, + required: [:account_ids] + }, + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex new file mode 100644 index 000000000..78aa0736f --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.List do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "List", + description: "Represents a list of some users that the authenticated user follows", + type: :object, + properties: %{ + id: %Schema{type: :string, description: "The internal database ID of the list"}, + title: %Schema{type: :string, description: "The user-defined title of the list"} + }, + example: %{ + "id" => "12249", + "title" => "Friends" + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index bfe856025..acdc76fd2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -9,20 +9,17 @@ defmodule Pleroma.Web.MastodonAPI.ListController do alias Pleroma.User alias Pleroma.Web.MastodonAPI.AccountView - plug(:list_by_id_and_user when action not in [:index, :create]) - @oauth_read_actions [:index, :show, :list_accounts] + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(:list_by_id_and_user when action not in [:index, :create]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions) - - plug( - OAuthScopesPlug, - %{scopes: ["write:lists"]} - when action not in @oauth_read_actions - ) + plug(OAuthScopesPlug, %{scopes: ["write:lists"]} when action not in @oauth_read_actions) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation + # GET /api/v1/lists def index(%{assigns: %{user: user}} = conn, opts) do lists = Pleroma.List.for_user(user, opts) @@ -30,7 +27,7 @@ def index(%{assigns: %{user: user}} = conn, opts) do end # POST /api/v1/lists - def create(%{assigns: %{user: user}} = conn, %{"title" => title}) do + def create(%{assigns: %{user: user}, body_params: %{title: title}} = conn, _) do with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do render(conn, "show.json", list: list) end @@ -42,7 +39,7 @@ def show(%{assigns: %{list: list}} = conn, _) do end # PUT /api/v1/lists/:id - def update(%{assigns: %{list: list}} = conn, %{"title" => title}) do + def update(%{assigns: %{list: list}, body_params: %{title: title}} = conn, _) do with {:ok, list} <- Pleroma.List.rename(list, title) do render(conn, "show.json", list: list) end @@ -65,7 +62,7 @@ def list_accounts(%{assigns: %{user: user, list: list}} = conn, _) do end # POST /api/v1/lists/:id/accounts - def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do + def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, _) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.follow(list, followed) @@ -76,7 +73,10 @@ def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids end # DELETE /api/v1/lists/:id/accounts - def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do + def remove_from_list( + %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, + _ + ) do Enum.each(account_ids, fn account_id -> with %User{} = followed <- User.get_cached_by_id(account_id) do Pleroma.List.unfollow(list, followed) @@ -86,7 +86,7 @@ def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => accoun json(conn, %{}) end - defp list_by_id_and_user(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do + defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) nil -> conn |> render_error(:not_found, "List not found") |> halt() diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index fa30a0c41..91c03b1a8 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -74,7 +74,7 @@ defp json_response_and_validate_schema( status = Plug.Conn.Status.code(status) unless lookup[op_id].responses[status] do - err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}" + err = "Response schema not found for #{status} #{conn.method} #{conn.request_path}" flunk(err) end diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index c9c4cbb49..57a9ef4a4 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -12,37 +12,44 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "creating a list" do %{conn: conn} = oauth_access(["write:lists"]) - conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) - - assert %{"title" => title} = json_response(conn, 200) - assert title == "cuties" + assert %{"title" => "cuties"} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/lists", %{"title" => "cuties"}) + |> json_response_and_validate_schema(:ok) end test "renders error for invalid params" do %{conn: conn} = oauth_access(["write:lists"]) - conn = post(conn, "/api/v1/lists", %{"title" => nil}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/lists", %{"title" => nil}) - assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) + assert %{"error" => "title - null value where string expected."} = + json_response_and_validate_schema(conn, 400) end test "listing a user's lists" do %{conn: conn} = oauth_access(["read:lists", "write:lists"]) conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/lists", %{"title" => "cuties"}) - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/lists", %{"title" => "cofe"}) - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) conn = get(conn, "/api/v1/lists") assert [ %{"id" => _, "title" => "cofe"}, %{"id" => _, "title" => "cuties"} - ] = json_response(conn, :ok) + ] = json_response_and_validate_schema(conn, :ok) end test "adding users to a list" do @@ -50,9 +57,12 @@ test "adding users to a list" do other_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) - conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + assert %{} == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + |> json_response_and_validate_schema(:ok) - assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [other_user.follower_address] end @@ -65,9 +75,12 @@ test "removing users from a list" do {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) - conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + assert %{} == + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + |> json_response_and_validate_schema(:ok) - assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [third_user.follower_address] end @@ -83,7 +96,7 @@ test "listing users in a list" do |> assign(:user, user) |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) - assert [%{"id" => id}] = json_response(conn, 200) + assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200) assert id == to_string(other_user.id) end @@ -96,7 +109,7 @@ test "retrieving a list" do |> assign(:user, user) |> get("/api/v1/lists/#{list.id}") - assert %{"id" => id} = json_response(conn, 200) + assert %{"id" => id} = json_response_and_validate_schema(conn, 200) assert id == to_string(list.id) end @@ -105,17 +118,18 @@ test "renders 404 if list is not found" do conn = get(conn, "/api/v1/lists/666") - assert %{"error" => "List not found"} = json_response(conn, :not_found) + assert %{"error" => "List not found"} = json_response_and_validate_schema(conn, :not_found) end test "renaming a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) - conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) - - assert %{"title" => name} = json_response(conn, 200) - assert name == "newname" + assert %{"title" => "newname"} = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) + |> json_response_and_validate_schema(:ok) end test "validates title when renaming a list" do @@ -125,9 +139,11 @@ test "validates title when renaming a list" do conn = conn |> assign(:user, user) + |> put_req_header("content-type", "application/json") |> put("/api/v1/lists/#{list.id}", %{"title" => " "}) - assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) + assert %{"error" => "can't be blank"} == + json_response_and_validate_schema(conn, :unprocessable_entity) end test "deleting a list" do @@ -136,7 +152,7 @@ test "deleting a list" do conn = delete(conn, "/api/v1/lists/#{list.id}") - assert %{} = json_response(conn, 200) + assert %{} = json_response_and_validate_schema(conn, 200) assert is_nil(Repo.get(Pleroma.List, list.id)) end end From f2bf4390f4231d25486b803d426199975996f175 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 1 May 2020 19:53:00 +0400 Subject: [PATCH 072/337] Fix descriptions for List API spec --- lib/pleroma/web/api_spec/operations/list_operation.ex | 5 ++--- lib/pleroma/web/api_spec/schemas/list.ex | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index bb903a379..c88ed5dd0 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -33,7 +33,7 @@ def index_operation do def create_operation do %Operation{ tags: ["Lists"], - summary: "Show a single list", + summary: "Create a list", description: "Fetch the list with the given ID. Used for verifying the title of a list.", operationId: "ListController.create", requestBody: create_update_request(), @@ -111,8 +111,7 @@ def add_to_list_operation do %Operation{ tags: ["Lists"], summary: "Add accounts to list", - description: - "Add accounts to the given list. Note that the user must be following these accounts.", + description: "Add accounts to the given list.", operationId: "ListController.add_to_list", parameters: [id_param()], requestBody: add_remove_accounts_request(), diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex index 78aa0736f..b7d1685c9 100644 --- a/lib/pleroma/web/api_spec/schemas/list.ex +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do OpenApiSpex.schema(%{ title: "List", - description: "Represents a list of some users that the authenticated user follows", + description: "Represents a list of users", type: :object, properties: %{ id: %Schema{type: :string, description: "The internal database ID of the list"}, From a3071f023166cb5364ce56e3666d5a77baa16434 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 15:08:41 +0200 Subject: [PATCH 073/337] Undoing: Move undoing likes to the pipeline everywhere. --- lib/pleroma/user.ex | 12 ++-- lib/pleroma/web/activity_pub/activity_pub.ex | 23 ------- lib/pleroma/web/activity_pub/side_effects.ex | 19 ++++++ .../web/activity_pub/transmogrifier.ex | 11 +--- lib/pleroma/web/common_api/common_api.ex | 9 ++- .../controllers/status_controller.ex | 6 +- test/notification_test.exs | 2 +- test/web/activity_pub/activity_pub_test.exs | 60 ------------------- test/web/activity_pub/side_effects_test.exs | 29 +++++++++ .../transmogrifier/undo_handling_test.exs | 9 ++- 10 files changed, 75 insertions(+), 105 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 99358ddaf..0136ba119 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -29,7 +29,9 @@ defmodule Pleroma.User do alias Pleroma.UserRelationship alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils @@ -1553,11 +1555,13 @@ defp delete_activity(%{data: %{"type" => "Create"}} = activity) do end defp delete_activity(%{data: %{"type" => "Like"}} = activity) do - object = Object.normalize(activity) + actor = + activity.actor + |> get_cached_by_ap_id() - activity.actor - |> get_cached_by_ap_id() - |> ActivityPub.unlike(object) + {:ok, undo, _} = Builder.undo(actor, activity) + + Pipeline.common_pipeline(undo, local: true) end defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1c21d78af..daad4d751 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -398,29 +398,6 @@ defp do_unreact_with_emoji(user, reaction_id, options) do end end - @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) :: - {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()} - def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do - with {:ok, result} <- - Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do - result - end - end - - defp do_unlike(actor, object, activity_id, local) do - with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), - unlike_data <- make_unlike_data(actor, like_activity, activity_id), - {:ok, unlike_activity} <- insert(unlike_data, local), - {:ok, _activity} <- Repo.delete(like_activity), - {:ok, object} <- remove_like_from_object(like_activity, object), - :ok <- maybe_federate(unlike_activity) do - {:ok, unlike_activity, like_activity, object} - else - nil -> {:ok, object} - {:error, error} -> Repo.rollback(error) - end - end - @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) :: {:ok, Activity.t(), Object.t()} | {:error, any()} def announce( diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 6a8f1af96..8ed91e257 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -5,8 +5,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do liked object, a `Follow` activity will add the user to the follower collection, and so on. """ + alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.Web.ActivityPub.Utils def handle(object, meta \\ []) @@ -23,8 +25,25 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do {:ok, object, meta} end + def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do + with undone_object <- Activity.get_by_ap_id(undone_object), + :ok <- handle_undoing(undone_object) do + {:ok, object, meta} + end + end + # Nothing to do def handle(object, meta) do {:ok, object, meta} end + + def handle_undoing(%{data: %{"type" => "Like"}} = object) do + with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]), + {:ok, _} <- Utils.remove_like_from_object(object, liked_object), + {:ok, _} <- Repo.delete(object) do + :ok + end + end + + def handle_undoing(object), do: {:error, ["don't know how to handle", object]} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 581e7040b..a60b27bea 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -865,19 +865,12 @@ def handle_incoming( def handle_incoming( %{ "type" => "Undo", - "object" => %{"type" => "Like", "object" => object_id}, - "actor" => _actor, - "id" => id + "object" => %{"type" => "Like"} } = 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, _, _} <- ActivityPub.unlike(actor, object, id, false) do + with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} - else - _e -> :error end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index f9db97d24..a670ea5bc 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -166,9 +166,12 @@ def favorite_helper(user, id) do def unfavorite(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- - {:find_activity, Activity.get_by_id(id)} do - object = Object.normalize(activity) - ActivityPub.unlike(user, object) + {:find_activity, Activity.get_by_id(id)}, + %Object{} = note <- Object.normalize(activity, false), + %Activity{} = like <- Utils.get_existing_like(user.ap_id, note), + {:ok, undo, _} <- Builder.undo(user, like), + {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: false) do + {:ok, activity} else {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not unfavorite")} diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 9eea2e9eb..2a5eac9d9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -222,9 +222,9 @@ def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do end @doc "POST /api/v1/statuses/:id/unfavourite" - def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + with {:ok, _unfav} <- CommonAPI.unfavorite(activity_id, user), + %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end diff --git a/test/notification_test.exs b/test/notification_test.exs index 601a6c0ca..7d5b82993 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -724,7 +724,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is un assert length(Notification.for_user(user)) == 1 - {:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user) + {:ok, _} = CommonAPI.unfavorite(activity.id, other_user) assert Enum.empty?(Notification.for_user(user)) end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 84ead93bb..797af66a0 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -995,66 +995,6 @@ test "reverts emoji unreact on error" do end end - describe "unliking" do - test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do - Config.put([:instance, :federating], true) - - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - {:ok, object} = ActivityPub.unlike(user, object) - refute called(Federator.publish()) - - {:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id) - object = Object.get_by_id(object.id) - assert object.data["like_count"] == 1 - - {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - assert called(Federator.publish(unlike_activity)) - end - - test "reverts unliking on error" do - note_activity = insert(:note_activity) - user = insert(:user) - - {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id) - object = Object.normalize(note_activity) - assert object.data["like_count"] == 1 - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.unlike(user, object) - end - - assert Object.get_by_ap_id(object.data["id"]) == object - assert object.data["like_count"] == 1 - assert Activity.get_by_id(like_activity.id) - end - - test "unliking a previously liked object" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - # Unliking something that hasn't been liked does nothing - {:ok, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id) - - object = Object.get_by_id(object.id) - assert object.data["like_count"] == 1 - - {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - assert Activity.get_by_id(like_activity.id) == nil - assert note_activity.actor in unlike_activity.recipients - end - end - describe "announcing an object" do test "adds an announce activity to the db" do note_activity = insert(:note_activity) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 0b6b55156..61ef72742 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do use Pleroma.DataCase + alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo @@ -15,6 +16,34 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do import Pleroma.Factory + describe "Undo objects" do + setup do + poster = insert(:user) + user = insert(:user) + {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) + {:ok, like} = CommonAPI.favorite(user, post.id) + + {:ok, undo_data, _meta} = Builder.undo(user, like) + {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + %{like_undo: like_undo, post: post, like: like} + end + + test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do + {:ok, _like_undo, _} = SideEffects.handle(like_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["like_count"] == 0 + assert object.data["likes"] == [] + end + + test "deletes the original like", %{like_undo: like_undo, like: like} do + {:ok, _like_undo, _} = SideEffects.handle(like_undo) + refute Activity.get_by_id(like.id) + end + end + describe "like objects" do setup do poster = insert(:user) diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs index a9ebfdb18..bf2a6bc5b 100644 --- a/test/web/activity_pub/transmogrifier/undo_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI @@ -67,7 +68,11 @@ test "it works for incoming unlikes with an existing like activity" do assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["type"] == "Undo" assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" + + note = Object.get_by_ap_id(like_data["object"]) + assert note.data["like_count"] == 0 + assert note.data["likes"] == [] end test "it works for incoming unlikes with an existing like activity and a compact object" do @@ -94,7 +99,7 @@ test "it works for incoming unlikes with an existing like activity and a compact assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["type"] == "Undo" assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" end test "it works for incoming unannounces with an existing notice" do From e7d8ab8303cb69682a75c30a356572a75deb9837 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 5 May 2020 16:08:44 +0300 Subject: [PATCH 074/337] admin_api fetch status by id --- CHANGELOG.md | 1 + docs/API/admin_api.md | 11 +++++++++++ .../web/admin_api/admin_api_controller.ex | 12 +++++++++++- lib/pleroma/web/router.ex | 1 + .../admin_api/admin_api_controller_test.exs | 19 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522285efe..114bfac4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. - Mastodon API: Add support for filtering replies in public and home timelines - Admin API: endpoints for create/update/delete OAuth Apps. +- Admin API: endpoint for status view. ### Fixed diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 6202c5a1a..23af08961 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -755,6 +755,17 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - 400 Bad Request `"Invalid parameters"` when `status` is missing - On success: `204`, empty response +## `GET /api/pleroma/admin/statuses/:id` + +### Show status by id + +- Params: + - `id`: required, status id +- Response: + - On failure: + - 404 Not Found `"Not Found"` + - On success: JSON, Mastodon Status entity + ## `PUT /api/pleroma/admin/statuses/:id` ### Change the scope of an individual reported status diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 816c11e01..ac661e515 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} - when action in [:list_statuses, :list_user_statuses, :list_instance_statuses] + when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show] ) plug( @@ -837,6 +837,16 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) end + def status_show(conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id) do + conn + |> put_view(StatusView) + |> render("show.json", %{activity: activity}) + else + _ -> errors(conn, {:error, :not_found}) + end + end + def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5b00243e9..ef2239d59 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -188,6 +188,7 @@ defmodule Pleroma.Web.Router do post("/reports/:id/notes", AdminAPIController, :report_notes_create) delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) + get("/statuses/:id", AdminAPIController, :status_show) put("/statuses/:id", AdminAPIController, :status_update) delete("/statuses/:id", AdminAPIController, :status_delete) get("/statuses", AdminAPIController, :list_statuses) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 1862a9589..c3f3ad051 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1620,6 +1620,25 @@ test "returns 403 when requested by anonymous" do end end + describe "GET /api/pleroma/admin/statuses/:id" do + test "not found", %{conn: conn} do + assert conn + |> get("/api/pleroma/admin/statuses/not_found") + |> json_response(:not_found) + end + + test "shows activity", %{conn: conn} do + activity = insert(:note_activity) + + response = + conn + |> get("/api/pleroma/admin/statuses/#{activity.id}") + |> json_response(200) + + assert response["id"] == activity.id + end + end + describe "PUT /api/pleroma/admin/statuses/:id" do setup do activity = insert(:note_activity) From 88a14da8172cde6316926b5fbaa2f55b6da6f080 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 17:24:16 +0400 Subject: [PATCH 075/337] Add OpenAPI spec for InstanceController --- lib/pleroma/stats.ex | 2 +- .../api_spec/operations/instance_operation.ex | 169 ++++++++++++++++++ .../controllers/instance_controller.ex | 4 + .../controllers/instance_controller_test.exs | 6 +- 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/instance_operation.ex diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 8d2809bbb..6b3a8a41f 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -91,7 +91,7 @@ def calculate_stat_data do peers: peers, stats: %{ domain_count: domain_count, - status_count: status_count, + status_count: status_count || 0, user_count: user_count } } diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex new file mode 100644 index 000000000..36a1a9043 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -0,0 +1,169 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.InstanceOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def show_operation do + %Operation{ + tags: ["Instance"], + summary: "Fetch instance", + description: "Information about the server", + operationId: "InstanceController.show", + responses: %{ + 200 => Operation.response("Instance", "application/json", instance()) + } + } + end + + def peers_operation do + %Operation{ + tags: ["Instance"], + summary: "List of connected domains", + operationId: "InstanceController.peers", + responses: %{ + 200 => Operation.response("Array of domains", "application/json", array_of_domains()) + } + } + end + + defp instance do + %Schema{ + type: :object, + properties: %{ + uri: %Schema{type: :string, description: "The domain name of the instance"}, + title: %Schema{type: :string, description: "The title of the website"}, + description: %Schema{ + type: :string, + description: "Admin-defined description of the Mastodon site" + }, + version: %Schema{ + type: :string, + description: "The version of Mastodon installed on the instance" + }, + email: %Schema{ + type: :string, + description: "An email that may be contacted for any inquiries", + format: :email + }, + urls: %Schema{ + type: :object, + description: "URLs of interest for clients apps", + properties: %{ + streaming_api: %Schema{ + type: :string, + description: "Websockets address for push streaming" + } + } + }, + stats: %Schema{ + type: :object, + description: "Statistics about how much information the instance contains", + properties: %{ + user_count: %Schema{ + type: :integer, + description: "Users registered on this instance" + }, + status_count: %Schema{ + type: :integer, + description: "Statuses authored by users on instance" + }, + domain_count: %Schema{ + type: :integer, + description: "Domains federated with this instance" + } + } + }, + thumbnail: %Schema{ + type: :string, + description: "Banner image for the website", + nullable: true + }, + languages: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Primary langauges of the website and its staff" + }, + registrations: %Schema{type: :boolean, description: "Whether registrations are enabled"}, + # Extra (not present in Mastodon): + max_toot_chars: %Schema{ + type: :integer, + description: ": Posts character limit (CW/Subject included in the counter)" + }, + poll_limits: %Schema{ + type: :object, + description: "A map with poll limits for local polls", + properties: %{ + max_options: %Schema{ + type: :integer, + description: "Maximum number of options." + }, + max_option_chars: %Schema{ + type: :integer, + description: "Maximum number of characters per option." + }, + min_expiration: %Schema{ + type: :integer, + description: "Minimum expiration time (in seconds)." + }, + max_expiration: %Schema{ + type: :integer, + description: "Maximum expiration time (in seconds)." + } + } + }, + upload_limit: %Schema{ + type: :integer, + description: "File size limit of uploads (except for avatar, background, banner)" + }, + avatar_upload_limit: %Schema{type: :integer, description: "The title of the website"}, + background_upload_limit: %Schema{type: :integer, description: "The title of the website"}, + banner_upload_limit: %Schema{type: :integer, description: "The title of the website"} + }, + example: %{ + "avatar_upload_limit" => 2_000_000, + "background_upload_limit" => 4_000_000, + "banner_upload_limit" => 4_000_000, + "description" => "A Pleroma instance, an alternative fediverse server", + "email" => "lain@lain.com", + "languages" => ["en"], + "max_toot_chars" => 5000, + "poll_limits" => %{ + "max_expiration" => 31_536_000, + "max_option_chars" => 200, + "max_options" => 20, + "min_expiration" => 0 + }, + "registrations" => false, + "stats" => %{ + "domain_count" => 2996, + "status_count" => 15_802, + "user_count" => 5 + }, + "thumbnail" => "https://lain.com/instance/thumbnail.jpeg", + "title" => "lain.com", + "upload_limit" => 16_000_000, + "uri" => "https://lain.com", + "urls" => %{ + "streaming_api" => "wss://lain.com" + }, + "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)" + } + } + end + + defp array_of_domains do + %Schema{ + type: :array, + items: %Schema{type: :string}, + example: ["pleroma.site", "lain.com", "bikeshed.party"] + } + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 237f85677..d8859731d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -5,12 +5,16 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do use Pleroma.Web, :controller + plug(OpenApiSpex.Plug.CastAndValidate) + plug( :skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] when action in [:show, :peers] ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation + @doc "GET /api/v1/instance" def show(conn, _params) do render(conn, "show.json") diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index 2c7fd9fd0..90840d5ab 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -10,7 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do test "get instance information", %{conn: conn} do conn = get(conn, "/api/v1/instance") - assert result = json_response(conn, 200) + assert result = json_response_and_validate_schema(conn, 200) email = Pleroma.Config.get([:instance, :email]) # Note: not checking for "max_toot_chars" since it's optional @@ -56,7 +56,7 @@ test "get instance stats", %{conn: conn} do conn = get(conn, "/api/v1/instance") - assert result = json_response(conn, 200) + assert result = json_response_and_validate_schema(conn, 200) stats = result["stats"] @@ -74,7 +74,7 @@ test "get peers", %{conn: conn} do conn = get(conn, "/api/v1/instance/peers") - assert result = json_response(conn, 200) + assert result = json_response_and_validate_schema(conn, 200) assert ["peer1.com", "peer2.com"] == Enum.sort(result) end From b5189d2c50929aa67293e2e39ca020bad43f5f8b Mon Sep 17 00:00:00 2001 From: minibikini Date: Thu, 30 Apr 2020 17:45:48 +0000 Subject: [PATCH 076/337] Apply suggestion to lib/pleroma/web/api_spec/operations/instance_operation.ex --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 36a1a9043..9407fa74d 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -26,7 +26,7 @@ def show_operation do def peers_operation do %Operation{ tags: ["Instance"], - summary: "List of connected domains", + summary: "List of known hosts", operationId: "InstanceController.peers", responses: %{ 200 => Operation.response("Array of domains", "application/json", array_of_domains()) From 3817f179d777058259324d2e300780da06cce460 Mon Sep 17 00:00:00 2001 From: minibikini Date: Fri, 1 May 2020 12:46:53 +0000 Subject: [PATCH 077/337] Apply suggestion to lib/pleroma/web/api_spec/operations/instance_operation.ex --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 9407fa74d..5644cb54d 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -46,7 +46,7 @@ defp instance do }, version: %Schema{ type: :string, - description: "The version of Mastodon installed on the instance" + description: "The version of Pleroma installed on the instance" }, email: %Schema{ type: :string, From 42a4a863f159b863ec4617fc47697e11f92ff956 Mon Sep 17 00:00:00 2001 From: minibikini Date: Fri, 1 May 2020 12:46:56 +0000 Subject: [PATCH 078/337] Apply suggestion to lib/pleroma/web/api_spec/operations/instance_operation.ex --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 5644cb54d..880bd3f1b 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -42,7 +42,7 @@ defp instance do title: %Schema{type: :string, description: "The title of the website"}, description: %Schema{ type: :string, - description: "Admin-defined description of the Mastodon site" + description: "Admin-defined description of the Pleroma site" }, version: %Schema{ type: :string, From ec1e4b4f1acb81fc36b396e7f58f67928dc6a0df Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 17:40:00 +0400 Subject: [PATCH 079/337] Add OpenAPI spec for FollowRequestController --- .../operations/follow_request_operation.ex | 65 +++++++++++++++++++ .../controllers/follow_request_controller.ex | 5 +- .../follow_request_controller_test.exs | 6 +- 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/follow_request_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex new file mode 100644 index 000000000..ac4aee6da --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -0,0 +1,65 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Follow Requests"], + summary: "Pending Follows", + security: [%{"oAuth" => ["read:follows", "follow"]}], + operationId: "FollowRequestController.index", + responses: %{ + 200 => + Operation.response("Array of Account", "application/json", %Schema{ + type: :array, + items: Account, + example: [Account.schema().example] + }) + } + } + end + + def authorize_operation do + %Operation{ + tags: ["Follow Requests"], + summary: "Accept Follow", + operationId: "FollowRequestController.authorize", + parameters: [id_param()], + security: [%{"oAuth" => ["follow", "write:follows"]}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + + def reject_operation do + %Operation{ + tags: ["Follow Requests"], + summary: "Reject Follow", + operationId: "FollowRequestController.reject", + parameters: [id_param()], + security: [%{"oAuth" => ["follow", "write:follows"]}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship) + } + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "Conversation ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 25f2269b9..748b6b475 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do alias Pleroma.Web.CommonAPI plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:assign_follower when action != :index) action_fallback(:errors) @@ -21,6 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do %{scopes: ["follow", "write:follows"]} when action != :index ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FollowRequestOperation + @doc "GET /api/v1/follow_requests" def index(%{assigns: %{user: followed}} = conn, _params) do follow_requests = User.get_follow_requests(followed) @@ -42,7 +45,7 @@ def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do end end - defp assign_follower(%{params: %{"id" => id}} = conn, _) do + defp assign_follower(%{params: %{id: id}} = conn, _) do case User.get_cached_by_id(id) do %User{} = follower -> assign(conn, :follower, follower) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs index d8dbe4800..44e12d15a 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -27,7 +27,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do conn = get(conn, "/api/v1/follow_requests") - assert [relationship] = json_response(conn, 200) + assert [relationship] = json_response_and_validate_schema(conn, 200) assert to_string(other_user.id) == relationship["id"] end @@ -44,7 +44,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") - assert relationship = json_response(conn, 200) + assert relationship = json_response_and_validate_schema(conn, 200) assert to_string(other_user.id) == relationship["id"] user = User.get_cached_by_id(user.id) @@ -62,7 +62,7 @@ test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") - assert relationship = json_response(conn, 200) + assert relationship = json_response_and_validate_schema(conn, 200) assert to_string(other_user.id) == relationship["id"] user = User.get_cached_by_id(user.id) From 7e7a3e15449792581412be002f287c504e3449a6 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 14 Apr 2020 18:36:32 +0400 Subject: [PATCH 080/337] Add OpenAPI spec for FilterController --- lib/pleroma/filter.ex | 9 +- .../api_spec/operations/filter_operation.ex | 89 +++++++++++++++++++ lib/pleroma/web/api_spec/schemas/filter.ex | 51 +++++++++++ .../api_spec/schemas/filter_create_request.ex | 30 +++++++ .../api_spec/schemas/filter_update_request.ex | 41 +++++++++ .../web/api_spec/schemas/filters_response.ex | 40 +++++++++ .../controllers/filter_controller.ex | 54 +++++------ .../web/mastodon_api/views/filter_view.ex | 6 +- test/filter_test.exs | 10 +-- .../controllers/filter_controller_test.exs | 55 ++++++++++-- 10 files changed, 340 insertions(+), 45 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/filter_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/filter.ex create mode 100644 lib/pleroma/web/api_spec/schemas/filter_create_request.ex create mode 100644 lib/pleroma/web/api_spec/schemas/filter_update_request.ex create mode 100644 lib/pleroma/web/api_spec/schemas/filters_response.ex diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index 7cb49360f..4d61b3650 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -89,11 +89,10 @@ def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do |> Repo.delete() end - def update(%Pleroma.Filter{} = filter) do - destination = Map.from_struct(filter) - - Pleroma.Filter.get(filter.filter_id, %{id: filter.user_id}) - |> cast(destination, [:phrase, :context, :hide, :expires_at, :whole_word]) + def update(%Pleroma.Filter{} = filter, params) do + filter + |> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word]) + |> validate_required([:phrase, :context]) |> Repo.update() end end diff --git a/lib/pleroma/web/api_spec/operations/filter_operation.ex b/lib/pleroma/web/api_spec/operations/filter_operation.ex new file mode 100644 index 000000000..0d673f566 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/filter_operation.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.FilterOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.Filter + alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest + alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse + alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["apps"], + summary: "View all filters", + operationId: "FilterController.index", + security: [%{"oAuth" => ["read:filters"]}], + responses: %{ + 200 => Operation.response("Filters", "application/json", FiltersResponse) + } + } + end + + def create_operation do + %Operation{ + tags: ["apps"], + summary: "Create a filter", + operationId: "FilterController.create", + requestBody: Helpers.request_body("Parameters", FilterCreateRequest, required: true), + security: [%{"oAuth" => ["write:filters"]}], + responses: %{200 => Operation.response("Filter", "application/json", Filter)} + } + end + + def show_operation do + %Operation{ + tags: ["apps"], + summary: "View all filters", + parameters: [id_param()], + operationId: "FilterController.show", + security: [%{"oAuth" => ["read:filters"]}], + responses: %{ + 200 => Operation.response("Filter", "application/json", Filter) + } + } + end + + def update_operation do + %Operation{ + tags: ["apps"], + summary: "Update a filter", + parameters: [id_param()], + operationId: "FilterController.update", + requestBody: Helpers.request_body("Parameters", FilterUpdateRequest, required: true), + security: [%{"oAuth" => ["write:filters"]}], + responses: %{ + 200 => Operation.response("Filter", "application/json", Filter) + } + } + end + + def delete_operation do + %Operation{ + tags: ["apps"], + summary: "Remove a filter", + parameters: [id_param()], + operationId: "FilterController.delete", + security: [%{"oAuth" => ["write:filters"]}], + responses: %{ + 200 => + Operation.response("Filter", "application/json", %Schema{ + type: :object, + description: "Empty object" + }) + } + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "Filter ID", example: "123", required: true) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/filter.ex b/lib/pleroma/web/api_spec/schemas/filter.ex new file mode 100644 index 000000000..fc5480b71 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/filter.ex @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Filter do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Filter", + type: :object, + properties: %{ + id: %Schema{type: :string}, + phrase: %Schema{type: :string, description: "The text to be filtered"}, + context: %Schema{ + type: :array, + items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, + description: "The contexts in which the filter should be applied." + }, + expires_at: %Schema{ + type: :string, + format: :"date-time", + description: + "When the filter should no longer be applied. String (ISO 8601 Datetime), or null if the filter does not expire.", + nullable: true + }, + irreversible: %Schema{ + type: :boolean, + description: + "Should matching entities in home and notifications be dropped by the server?" + }, + whole_word: %Schema{ + type: :boolean, + description: "Should the filter consider word boundaries?" + } + }, + example: %{ + "id" => "5580", + "phrase" => "@twitter.com", + "context" => [ + "home", + "notifications", + "public", + "thread" + ], + "whole_word" => false, + "expires_at" => nil, + "irreversible" => true + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/filter_create_request.ex b/lib/pleroma/web/api_spec/schemas/filter_create_request.ex new file mode 100644 index 000000000..f2a475b12 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/filter_create_request.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "FilterCreateRequest", + allOf: [ + %OpenApiSpex.Reference{"$ref": "#/components/schemas/FilterUpdateRequest"}, + %Schema{ + type: :object, + properties: %{ + irreversible: %Schema{ + type: :bolean, + description: + "Should the server irreversibly drop matching entities from home and notifications?", + default: false + } + } + } + ], + example: %{ + "phrase" => "knights", + "context" => ["home"] + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/filter_update_request.ex b/lib/pleroma/web/api_spec/schemas/filter_update_request.ex new file mode 100644 index 000000000..e703db0ce --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/filter_update_request.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "FilterUpdateRequest", + type: :object, + properties: %{ + phrase: %Schema{type: :string, description: "The text to be filtered"}, + context: %Schema{ + type: :array, + items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, + description: + "Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified." + }, + irreversible: %Schema{ + type: :bolean, + description: + "Should the server irreversibly drop matching entities from home and notifications?" + }, + whole_word: %Schema{type: :bolean, description: "Consider word boundaries?", default: true} + # TODO: probably should implement filter expiration + # expires_in: %Schema{ + # type: :string, + # format: :"date-time", + # description: + # "ISO 8601 Datetime for when the filter expires. Otherwise, + # null for a filter that doesn't expire." + # } + }, + required: [:phrase, :context], + example: %{ + "phrase" => "knights", + "context" => ["home"] + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/filters_response.ex b/lib/pleroma/web/api_spec/schemas/filters_response.ex new file mode 100644 index 000000000..8c56c5982 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/filters_response.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.FiltersResponse do + require OpenApiSpex + alias Pleroma.Web.ApiSpec.Schemas.Filter + + OpenApiSpex.schema(%{ + title: "FiltersResponse", + description: "Array of Filters", + type: :array, + items: Filter, + example: [ + %{ + "id" => "5580", + "phrase" => "@twitter.com", + "context" => [ + "home", + "notifications", + "public", + "thread" + ], + "whole_word" => false, + "expires_at" => nil, + "irreversible" => true + }, + %{ + "id" => "6191", + "phrase" => ":eurovision2019:", + "context" => [ + "home" + ], + "whole_word" => true, + "expires_at" => "2019-05-21T13:47:31.333Z", + "irreversible" => false + } + ] + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index 7fd0562c9..dd13a8a09 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -10,67 +10,69 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do @oauth_read_actions [:show, :index] + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions) plug( OAuthScopesPlug, %{scopes: ["write:filters"]} when action not in @oauth_read_actions ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation @doc "GET /api/v1/filters" def index(%{assigns: %{user: user}} = conn, _) do filters = Filter.get_filters(user) - render(conn, "filters.json", filters: filters) + render(conn, "index.json", filters: filters) end @doc "POST /api/v1/filters" - def create( - %{assigns: %{user: user}} = conn, - %{"phrase" => phrase, "context" => context} = params - ) do + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do query = %Filter{ user_id: user.id, - phrase: phrase, - context: context, - hide: Map.get(params, "irreversible", false), - whole_word: Map.get(params, "boolean", true) + phrase: params.phrase, + context: params.context, + hide: params.irreversible, + whole_word: params.whole_word # expires_at } {:ok, response} = Filter.create(query) - render(conn, "filter.json", filter: response) + render(conn, "show.json", filter: response) end @doc "GET /api/v1/filters/:id" - def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do + def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do filter = Filter.get(filter_id, user) - render(conn, "filter.json", filter: filter) + render(conn, "show.json", filter: filter) end @doc "PUT /api/v1/filters/:id" def update( - %{assigns: %{user: user}} = conn, - %{"phrase" => phrase, "context" => context, "id" => filter_id} = params + %{assigns: %{user: user}, body_params: params} = conn, + %{id: filter_id} ) do - query = %Filter{ - user_id: user.id, - filter_id: filter_id, - phrase: phrase, - context: context, - hide: Map.get(params, "irreversible", nil), - whole_word: Map.get(params, "boolean", true) - # expires_at - } + params = + params + |> Map.from_struct() + |> Map.delete(:irreversible) + |> Map.put(:hide, params.irreversible) + |> Enum.reject(fn {_key, value} -> is_nil(value) end) + |> Map.new() - {:ok, response} = Filter.update(query) - render(conn, "filter.json", filter: response) + # TODO: add expires_in -> expires_at + + with %Filter{} = filter <- Filter.get(filter_id, user), + {:ok, %Filter{} = filter} <- Filter.update(filter, params) do + render(conn, "show.json", filter: filter) + end end @doc "DELETE /api/v1/filters/:id" - def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do + def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do query = %Filter{ user_id: user.id, filter_id: filter_id diff --git a/lib/pleroma/web/mastodon_api/views/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex index 97fd1e83f..8d5c381ec 100644 --- a/lib/pleroma/web/mastodon_api/views/filter_view.ex +++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex @@ -7,11 +7,11 @@ defmodule Pleroma.Web.MastodonAPI.FilterView do alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.FilterView - def render("filters.json", %{filters: filters} = opts) do - render_many(filters, FilterView, "filter.json", opts) + def render("index.json", %{filters: filters} = opts) do + render_many(filters, FilterView, "show.json", opts) end - def render("filter.json", %{filter: filter}) do + def render("show.json", %{filter: filter}) do expires_at = if filter.expires_at do Utils.to_masto_date(filter.expires_at) diff --git a/test/filter_test.exs b/test/filter_test.exs index b2a8330ee..63a30c736 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -141,17 +141,15 @@ test "updating a filter" do context: ["home"] } - query_two = %Pleroma.Filter{ - user_id: user.id, - filter_id: 1, + changes = %{ phrase: "who", context: ["home", "timeline"] } {:ok, filter_one} = Pleroma.Filter.create(query_one) - {:ok, filter_two} = Pleroma.Filter.update(query_two) + {:ok, filter_two} = Pleroma.Filter.update(filter_one, changes) assert filter_one != filter_two - assert filter_two.phrase == query_two.phrase - assert filter_two.context == query_two.context + assert filter_two.phrase == changes.phrase + assert filter_two.context == changes.context end end diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 97ab005e0..41a290eb2 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -5,8 +5,15 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Web.ApiSpec + alias Pleroma.Web.ApiSpec.Schemas.Filter + alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest + alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse + alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest alias Pleroma.Web.MastodonAPI.FilterView + import OpenApiSpex.TestAssertions + test "creating a filter" do %{conn: conn} = oauth_access(["write:filters"]) @@ -15,7 +22,10 @@ test "creating a filter" do context: ["home"] } - conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) assert response = json_response(conn, 200) assert response["phrase"] == filter.phrase @@ -23,6 +33,7 @@ test "creating a filter" do assert response["irreversible"] == false assert response["id"] != nil assert response["id"] != "" + assert_schema(response, "Filter", ApiSpec.spec()) end test "fetching a list of filters" do @@ -53,9 +64,11 @@ test "fetching a list of filters" do assert response == render_json( FilterView, - "filters.json", + "index.json", filters: [filter_two, filter_one] ) + + assert_schema(response, "FiltersResponse", ApiSpec.spec()) end test "get a filter" do @@ -72,7 +85,8 @@ test "get a filter" do conn = get(conn, "/api/v1/filters/#{filter.filter_id}") - assert _response = json_response(conn, 200) + assert response = json_response(conn, 200) + assert_schema(response, "Filter", ApiSpec.spec()) end test "update a filter" do @@ -82,7 +96,8 @@ test "update a filter" do user_id: user.id, filter_id: 2, phrase: "knight", - context: ["home"] + context: ["home"], + hide: true } {:ok, _filter} = Pleroma.Filter.create(query) @@ -93,7 +108,9 @@ test "update a filter" do } conn = - put(conn, "/api/v1/filters/#{query.filter_id}", %{ + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{query.filter_id}", %{ phrase: new.phrase, context: new.context }) @@ -101,6 +118,8 @@ test "update a filter" do assert response = json_response(conn, 200) assert response["phrase"] == new.phrase assert response["context"] == new.context + assert response["irreversible"] == true + assert_schema(response, "Filter", ApiSpec.spec()) end test "delete a filter" do @@ -120,4 +139,30 @@ test "delete a filter" do assert response = json_response(conn, 200) assert response == %{} end + + describe "OpenAPI" do + test "Filter example matches schema" do + api_spec = ApiSpec.spec() + schema = Filter.schema() + assert_schema(schema.example, "Filter", api_spec) + end + + test "FiltersResponse example matches schema" do + api_spec = ApiSpec.spec() + schema = FiltersResponse.schema() + assert_schema(schema.example, "FiltersResponse", api_spec) + end + + test "FilterCreateRequest example matches schema" do + api_spec = ApiSpec.spec() + schema = FilterCreateRequest.schema() + assert_schema(schema.example, "FilterCreateRequest", api_spec) + end + + test "FilterUpdateRequest example matches schema" do + api_spec = ApiSpec.spec() + schema = FilterUpdateRequest.schema() + assert_schema(schema.example, "FilterUpdateRequest", api_spec) + end + end end From 46aae346f8530d4b9933b8e718e9578a96447f0a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 27 Apr 2020 23:54:11 +0400 Subject: [PATCH 081/337] Move single used schemas to Filter operation schema --- .../api_spec/operations/filter_operation.ex | 158 ++++++++++++++++-- lib/pleroma/web/api_spec/schemas/filter.ex | 51 ------ .../api_spec/schemas/filter_create_request.ex | 30 ---- .../api_spec/schemas/filter_update_request.ex | 41 ----- .../web/api_spec/schemas/filters_response.ex | 40 ----- .../controllers/filter_controller.ex | 7 +- .../web/mastodon_api/views/filter_view.ex | 4 +- .../controllers/filter_controller_test.exs | 49 +----- 8 files changed, 158 insertions(+), 222 deletions(-) delete mode 100644 lib/pleroma/web/api_spec/schemas/filter.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/filter_create_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/filter_update_request.ex delete mode 100644 lib/pleroma/web/api_spec/schemas/filters_response.ex diff --git a/lib/pleroma/web/api_spec/operations/filter_operation.ex b/lib/pleroma/web/api_spec/operations/filter_operation.ex index 0d673f566..53e57b46b 100644 --- a/lib/pleroma/web/api_spec/operations/filter_operation.ex +++ b/lib/pleroma/web/api_spec/operations/filter_operation.ex @@ -6,10 +6,6 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers - alias Pleroma.Web.ApiSpec.Schemas.Filter - alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest - alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse - alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -23,7 +19,7 @@ def index_operation do operationId: "FilterController.index", security: [%{"oAuth" => ["read:filters"]}], responses: %{ - 200 => Operation.response("Filters", "application/json", FiltersResponse) + 200 => Operation.response("Filters", "application/json", array_of_filters()) } } end @@ -33,9 +29,9 @@ def create_operation do tags: ["apps"], summary: "Create a filter", operationId: "FilterController.create", - requestBody: Helpers.request_body("Parameters", FilterCreateRequest, required: true), + requestBody: Helpers.request_body("Parameters", create_request(), required: true), security: [%{"oAuth" => ["write:filters"]}], - responses: %{200 => Operation.response("Filter", "application/json", Filter)} + responses: %{200 => Operation.response("Filter", "application/json", filter())} } end @@ -47,7 +43,7 @@ def show_operation do operationId: "FilterController.show", security: [%{"oAuth" => ["read:filters"]}], responses: %{ - 200 => Operation.response("Filter", "application/json", Filter) + 200 => Operation.response("Filter", "application/json", filter()) } } end @@ -58,10 +54,10 @@ def update_operation do summary: "Update a filter", parameters: [id_param()], operationId: "FilterController.update", - requestBody: Helpers.request_body("Parameters", FilterUpdateRequest, required: true), + requestBody: Helpers.request_body("Parameters", update_request(), required: true), security: [%{"oAuth" => ["write:filters"]}], responses: %{ - 200 => Operation.response("Filter", "application/json", Filter) + 200 => Operation.response("Filter", "application/json", filter()) } } end @@ -86,4 +82,146 @@ def delete_operation do defp id_param do Operation.parameter(:id, :path, :string, "Filter ID", example: "123", required: true) end + + defp filter do + %Schema{ + title: "Filter", + type: :object, + properties: %{ + id: %Schema{type: :string}, + phrase: %Schema{type: :string, description: "The text to be filtered"}, + context: %Schema{ + type: :array, + items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, + description: "The contexts in which the filter should be applied." + }, + expires_at: %Schema{ + type: :string, + format: :"date-time", + description: + "When the filter should no longer be applied. String (ISO 8601 Datetime), or null if the filter does not expire.", + nullable: true + }, + irreversible: %Schema{ + type: :boolean, + description: + "Should matching entities in home and notifications be dropped by the server?" + }, + whole_word: %Schema{ + type: :boolean, + description: "Should the filter consider word boundaries?" + } + }, + example: %{ + "id" => "5580", + "phrase" => "@twitter.com", + "context" => [ + "home", + "notifications", + "public", + "thread" + ], + "whole_word" => false, + "expires_at" => nil, + "irreversible" => true + } + } + end + + defp array_of_filters do + %Schema{ + title: "ArrayOfFilters", + description: "Array of Filters", + type: :array, + items: filter(), + example: [ + %{ + "id" => "5580", + "phrase" => "@twitter.com", + "context" => [ + "home", + "notifications", + "public", + "thread" + ], + "whole_word" => false, + "expires_at" => nil, + "irreversible" => true + }, + %{ + "id" => "6191", + "phrase" => ":eurovision2019:", + "context" => [ + "home" + ], + "whole_word" => true, + "expires_at" => "2019-05-21T13:47:31.333Z", + "irreversible" => false + } + ] + } + end + + defp create_request do + %Schema{ + title: "FilterCreateRequest", + allOf: [ + update_request(), + %Schema{ + type: :object, + properties: %{ + irreversible: %Schema{ + type: :bolean, + description: + "Should the server irreversibly drop matching entities from home and notifications?", + default: false + } + } + } + ], + example: %{ + "phrase" => "knights", + "context" => ["home"] + } + } + end + + defp update_request do + %Schema{ + title: "FilterUpdateRequest", + type: :object, + properties: %{ + phrase: %Schema{type: :string, description: "The text to be filtered"}, + context: %Schema{ + type: :array, + items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, + description: + "Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified." + }, + irreversible: %Schema{ + type: :bolean, + description: + "Should the server irreversibly drop matching entities from home and notifications?" + }, + whole_word: %Schema{ + type: :bolean, + description: "Consider word boundaries?", + default: true + } + # TODO: probably should implement filter expiration + # expires_in: %Schema{ + # type: :string, + # format: :"date-time", + # description: + # "ISO 8601 Datetime for when the filter expires. Otherwise, + # null for a filter that doesn't expire." + # } + }, + required: [:phrase, :context], + example: %{ + "phrase" => "knights", + "context" => ["home"] + } + } + end end diff --git a/lib/pleroma/web/api_spec/schemas/filter.ex b/lib/pleroma/web/api_spec/schemas/filter.ex deleted file mode 100644 index fc5480b71..000000000 --- a/lib/pleroma/web/api_spec/schemas/filter.ex +++ /dev/null @@ -1,51 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.Filter do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "Filter", - type: :object, - properties: %{ - id: %Schema{type: :string}, - phrase: %Schema{type: :string, description: "The text to be filtered"}, - context: %Schema{ - type: :array, - items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, - description: "The contexts in which the filter should be applied." - }, - expires_at: %Schema{ - type: :string, - format: :"date-time", - description: - "When the filter should no longer be applied. String (ISO 8601 Datetime), or null if the filter does not expire.", - nullable: true - }, - irreversible: %Schema{ - type: :boolean, - description: - "Should matching entities in home and notifications be dropped by the server?" - }, - whole_word: %Schema{ - type: :boolean, - description: "Should the filter consider word boundaries?" - } - }, - example: %{ - "id" => "5580", - "phrase" => "@twitter.com", - "context" => [ - "home", - "notifications", - "public", - "thread" - ], - "whole_word" => false, - "expires_at" => nil, - "irreversible" => true - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/filter_create_request.ex b/lib/pleroma/web/api_spec/schemas/filter_create_request.ex deleted file mode 100644 index f2a475b12..000000000 --- a/lib/pleroma/web/api_spec/schemas/filter_create_request.ex +++ /dev/null @@ -1,30 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "FilterCreateRequest", - allOf: [ - %OpenApiSpex.Reference{"$ref": "#/components/schemas/FilterUpdateRequest"}, - %Schema{ - type: :object, - properties: %{ - irreversible: %Schema{ - type: :bolean, - description: - "Should the server irreversibly drop matching entities from home and notifications?", - default: false - } - } - } - ], - example: %{ - "phrase" => "knights", - "context" => ["home"] - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/filter_update_request.ex b/lib/pleroma/web/api_spec/schemas/filter_update_request.ex deleted file mode 100644 index e703db0ce..000000000 --- a/lib/pleroma/web/api_spec/schemas/filter_update_request.ex +++ /dev/null @@ -1,41 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest do - alias OpenApiSpex.Schema - require OpenApiSpex - - OpenApiSpex.schema(%{ - title: "FilterUpdateRequest", - type: :object, - properties: %{ - phrase: %Schema{type: :string, description: "The text to be filtered"}, - context: %Schema{ - type: :array, - items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, - description: - "Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified." - }, - irreversible: %Schema{ - type: :bolean, - description: - "Should the server irreversibly drop matching entities from home and notifications?" - }, - whole_word: %Schema{type: :bolean, description: "Consider word boundaries?", default: true} - # TODO: probably should implement filter expiration - # expires_in: %Schema{ - # type: :string, - # format: :"date-time", - # description: - # "ISO 8601 Datetime for when the filter expires. Otherwise, - # null for a filter that doesn't expire." - # } - }, - required: [:phrase, :context], - example: %{ - "phrase" => "knights", - "context" => ["home"] - } - }) -end diff --git a/lib/pleroma/web/api_spec/schemas/filters_response.ex b/lib/pleroma/web/api_spec/schemas/filters_response.ex deleted file mode 100644 index 8c56c5982..000000000 --- a/lib/pleroma/web/api_spec/schemas/filters_response.ex +++ /dev/null @@ -1,40 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Schemas.FiltersResponse do - require OpenApiSpex - alias Pleroma.Web.ApiSpec.Schemas.Filter - - OpenApiSpex.schema(%{ - title: "FiltersResponse", - description: "Array of Filters", - type: :array, - items: Filter, - example: [ - %{ - "id" => "5580", - "phrase" => "@twitter.com", - "context" => [ - "home", - "notifications", - "public", - "thread" - ], - "whole_word" => false, - "expires_at" => nil, - "irreversible" => true - }, - %{ - "id" => "6191", - "phrase" => ":eurovision2019:", - "context" => [ - "home" - ], - "whole_word" => true, - "expires_at" => "2019-05-21T13:47:31.333Z", - "irreversible" => false - } - ] - }) -end diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index dd13a8a09..21dc374cd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -35,7 +35,7 @@ def create(%{assigns: %{user: user}, body_params: params} = conn, _) do context: params.context, hide: params.irreversible, whole_word: params.whole_word - # expires_at + # TODO: support `expires_in` parameter (as in Mastodon API) } {:ok, response} = Filter.create(query) @@ -57,13 +57,12 @@ def update( ) do params = params - |> Map.from_struct() |> Map.delete(:irreversible) - |> Map.put(:hide, params.irreversible) + |> Map.put(:hide, params[:irreversible]) |> Enum.reject(fn {_key, value} -> is_nil(value) end) |> Map.new() - # TODO: add expires_in -> expires_at + # TODO: support `expires_in` parameter (as in Mastodon API) with %Filter{} = filter <- Filter.get(filter_id, user), {:ok, %Filter{} = filter} <- Filter.update(filter, params) do diff --git a/lib/pleroma/web/mastodon_api/views/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex index 8d5c381ec..aeff646f5 100644 --- a/lib/pleroma/web/mastodon_api/views/filter_view.ex +++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterView do alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.FilterView - def render("index.json", %{filters: filters} = opts) do - render_many(filters, FilterView, "show.json", opts) + def render("index.json", %{filters: filters}) do + render_many(filters, FilterView, "show.json") end def render("show.json", %{filter: filter}) do diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 41a290eb2..f29547d13 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -5,15 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Web.ApiSpec - alias Pleroma.Web.ApiSpec.Schemas.Filter - alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest - alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse - alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest alias Pleroma.Web.MastodonAPI.FilterView - import OpenApiSpex.TestAssertions - test "creating a filter" do %{conn: conn} = oauth_access(["write:filters"]) @@ -27,13 +20,12 @@ test "creating a filter" do |> put_req_header("content-type", "application/json") |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) - assert response = json_response(conn, 200) + assert response = json_response_and_validate_schema(conn, 200) assert response["phrase"] == filter.phrase assert response["context"] == filter.context assert response["irreversible"] == false assert response["id"] != nil assert response["id"] != "" - assert_schema(response, "Filter", ApiSpec.spec()) end test "fetching a list of filters" do @@ -59,7 +51,7 @@ test "fetching a list of filters" do response = conn |> get("/api/v1/filters") - |> json_response(200) + |> json_response_and_validate_schema(200) assert response == render_json( @@ -67,8 +59,6 @@ test "fetching a list of filters" do "index.json", filters: [filter_two, filter_one] ) - - assert_schema(response, "FiltersResponse", ApiSpec.spec()) end test "get a filter" do @@ -85,8 +75,7 @@ test "get a filter" do conn = get(conn, "/api/v1/filters/#{filter.filter_id}") - assert response = json_response(conn, 200) - assert_schema(response, "Filter", ApiSpec.spec()) + assert response = json_response_and_validate_schema(conn, 200) end test "update a filter" do @@ -115,11 +104,10 @@ test "update a filter" do context: new.context }) - assert response = json_response(conn, 200) + assert response = json_response_and_validate_schema(conn, 200) assert response["phrase"] == new.phrase assert response["context"] == new.context assert response["irreversible"] == true - assert_schema(response, "Filter", ApiSpec.spec()) end test "delete a filter" do @@ -136,33 +124,6 @@ test "delete a filter" do conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") - assert response = json_response(conn, 200) - assert response == %{} - end - - describe "OpenAPI" do - test "Filter example matches schema" do - api_spec = ApiSpec.spec() - schema = Filter.schema() - assert_schema(schema.example, "Filter", api_spec) - end - - test "FiltersResponse example matches schema" do - api_spec = ApiSpec.spec() - schema = FiltersResponse.schema() - assert_schema(schema.example, "FiltersResponse", api_spec) - end - - test "FilterCreateRequest example matches schema" do - api_spec = ApiSpec.spec() - schema = FilterCreateRequest.schema() - assert_schema(schema.example, "FilterCreateRequest", api_spec) - end - - test "FilterUpdateRequest example matches schema" do - api_spec = ApiSpec.spec() - schema = FilterUpdateRequest.schema() - assert_schema(schema.example, "FilterUpdateRequest", api_spec) - end + assert json_response_and_validate_schema(conn, 200) == %{} end end From 32ca9f2c59369c15905f665bee3c759ae963ff91 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 28 Apr 2020 16:25:13 +0400 Subject: [PATCH 082/337] Render mastodon-like errors in FilterController --- lib/pleroma/web/mastodon_api/controllers/filter_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index 21dc374cd..abbf0ce02 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do OAuthScopesPlug, %{scopes: ["write:filters"]} when action not in @oauth_read_actions ) - + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation @doc "GET /api/v1/filters" From 3a45952a3a324e5fb823e9bdf3ffe19fb3923cb3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 17:44:46 +0400 Subject: [PATCH 083/337] Add OpenAPI spec for ConversationController --- lib/pleroma/conversation/participation.ex | 4 +- .../operations/conversation_operation.ex | 61 +++++++++++++++++++ .../web/api_spec/schemas/conversation.ex | 41 +++++++++++++ lib/pleroma/web/api_spec/schemas/status.ex | 7 ++- .../controllers/conversation_controller.ex | 5 +- .../conversation_controller_test.exs | 22 +++---- 6 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/conversation_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/conversation.ex diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 215265fc9..51bb1bda9 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -128,7 +128,7 @@ def for_user(user, params \\ %{}) do |> Pleroma.Pagination.fetch_paginated(params) end - def restrict_recipients(query, user, %{"recipients" => user_ids}) do + def restrict_recipients(query, user, %{recipients: user_ids}) do user_binary_ids = [user.id | user_ids] |> Enum.uniq() @@ -172,7 +172,7 @@ def for_user_with_last_activity_id(user, params \\ %{}) do | last_activity_id: activity_id } end) - |> Enum.filter(& &1.last_activity_id) + |> Enum.reject(&is_nil(&1.last_activity_id)) end def get(_, _ \\ []) diff --git a/lib/pleroma/web/api_spec/operations/conversation_operation.ex b/lib/pleroma/web/api_spec/operations/conversation_operation.ex new file mode 100644 index 000000000..475468893 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/conversation_operation.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ConversationOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Conversation + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Conversations"], + summary: "Show conversation", + security: [%{"oAuth" => ["read:statuses"]}], + operationId: "ConversationController.index", + parameters: [ + Operation.parameter( + :recipients, + :query, + %Schema{type: :array, items: FlakeID}, + "Only return conversations with the given recipients (a list of user ids)" + ) + | pagination_params() + ], + responses: %{ + 200 => + Operation.response("Array of Conversation", "application/json", %Schema{ + type: :array, + items: Conversation, + example: [Conversation.schema().example] + }) + } + } + end + + def mark_as_read_operation do + %Operation{ + tags: ["Conversations"], + summary: "Mark as read", + operationId: "ConversationController.mark_as_read", + parameters: [ + Operation.parameter(:id, :path, :string, "Conversation ID", + example: "123", + required: true + ) + ], + security: [%{"oAuth" => ["write:conversations"]}], + responses: %{ + 200 => Operation.response("Conversation", "application/json", Conversation) + } + } + end +end diff --git a/lib/pleroma/web/api_spec/schemas/conversation.ex b/lib/pleroma/web/api_spec/schemas/conversation.ex new file mode 100644 index 000000000..d8ff5ba26 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/conversation.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Conversation do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.Status + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Conversation", + description: "Represents a conversation with \"direct message\" visibility.", + type: :object, + required: [:id, :accounts, :unread], + properties: %{ + id: %Schema{type: :string}, + accounts: %Schema{ + type: :array, + items: Account, + description: "Participants in the conversation" + }, + unread: %Schema{ + type: :boolean, + description: "Is the conversation currently marked as unread?" + }, + # last_status: Status + last_status: %Schema{ + allOf: [Status], + description: "The last status in the conversation, to be used for optional display" + } + }, + example: %{ + "id" => "418450", + "unread" => true, + "accounts" => [Account.schema().example], + "last_status" => Status.schema().example + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index aef0588d4..42e9dae19 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -86,7 +86,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do properties: %{ content: %Schema{type: :object, additionalProperties: %Schema{type: :string}}, conversation_id: %Schema{type: :integer}, - direct_conversation_id: %Schema{type: :string, nullable: true}, + direct_conversation_id: %Schema{ + type: :integer, + nullable: true, + description: + "The ID of the Mastodon direct message conversation the status is associated with (if any)" + }, emoji_reactions: %Schema{ type: :array, items: %Schema{ diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index c44641526..f35ec3596 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -13,9 +13,12 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index) plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ConversationOperation + @doc "GET /api/v1/conversations" def index(%{assigns: %{user: user}} = conn, params) do participations = Participation.for_user_with_last_activity_id(user, params) @@ -26,7 +29,7 @@ def index(%{assigns: %{user: user}} = conn, params) do end @doc "POST /api/v1/conversations/:id/read" - def mark_as_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + def mark_as_read(%{assigns: %{user: user}} = conn, %{id: participation_id}) do with %Participation{} = participation <- Repo.get_by(Participation, id: participation_id, user_id: user.id), {:ok, participation} <- Participation.mark_as_read(participation) do diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs index 801b0259b..04695572e 100644 --- a/test/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -36,7 +36,7 @@ test "returns a list of conversations", %{user: user_one, conn: conn} do res_conn = get(conn, "/api/v1/conversations") - assert response = json_response(res_conn, 200) + assert response = json_response_and_validate_schema(res_conn, 200) assert [ %{ @@ -91,18 +91,18 @@ test "filters conversations by recipients", %{user: user_one, conn: conn} do "visibility" => "direct" }) - [conversation1, conversation2] = - conn - |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) - |> json_response(200) + assert [conversation1, conversation2] = + conn + |> get("/api/v1/conversations?recipients[]=#{user_two.id}") + |> json_response_and_validate_schema(200) assert conversation1["last_status"]["id"] == direct5.id assert conversation2["last_status"]["id"] == direct1.id [conversation1] = conn - |> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) - |> json_response(200) + |> get("/api/v1/conversations?recipients[]=#{user_two.id}&recipients[]=#{user_three.id}") + |> json_response_and_validate_schema(200) assert conversation1["last_status"]["id"] == direct3.id end @@ -126,7 +126,7 @@ test "updates the last_status on reply", %{user: user_one, conn: conn} do [%{"last_status" => res_last_status}] = conn |> get("/api/v1/conversations") - |> json_response(200) + |> json_response_and_validate_schema(200) assert res_last_status["id"] == direct_reply.id end @@ -154,12 +154,12 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do [%{"id" => direct_conversation_id, "unread" => true}] = user_two_conn |> get("/api/v1/conversations") - |> json_response(200) + |> json_response_and_validate_schema(200) %{"unread" => false} = user_two_conn |> post("/api/v1/conversations/#{direct_conversation_id}/read") - |> json_response(200) + |> json_response_and_validate_schema(200) assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 @@ -175,7 +175,7 @@ test "the user marks a conversation as read", %{user: user_one, conn: conn} do [%{"unread" => true}] = conn |> get("/api/v1/conversations") - |> json_response(200) + |> json_response_and_validate_schema(200) assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 From b34debe61540cf845ccf4ac93066e45a1d9c8f85 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 16:17:09 +0200 Subject: [PATCH 084/337] Undoing: Move undoing reactions to the pipeline everywhere. --- lib/pleroma/web/activity_pub/activity_pub.ex | 25 --------- lib/pleroma/web/activity_pub/side_effects.ex | 8 +++ .../web/activity_pub/transmogrifier.ex | 27 +-------- lib/pleroma/web/common_api/common_api.ex | 8 ++- .../controllers/pleroma_api_controller.ex | 3 +- test/web/activity_pub/activity_pub_test.exs | 56 ------------------- test/web/activity_pub/side_effects_test.exs | 30 +++++++++- test/web/common_api/common_api_test.exs | 3 +- .../pleroma_api_controller_test.exs | 10 +++- 9 files changed, 57 insertions(+), 113 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index daad4d751..c94af3b5f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -373,31 +373,6 @@ defp do_react_with_emoji(user, object, emoji, options) do end end - @spec unreact_with_emoji(User.t(), String.t(), keyword()) :: - {:ok, Activity.t(), Object.t()} | {:error, any()} - def unreact_with_emoji(user, reaction_id, options \\ []) do - with {:ok, result} <- - Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do - result - end - end - - defp do_unreact_with_emoji(user, reaction_id, options) do - with local <- Keyword.get(options, :local, true), - activity_id <- Keyword.get(options, :activity_id, nil), - user_ap_id <- user.ap_id, - %Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id), - object <- Object.normalize(reaction_activity), - unreact_data <- make_undo_data(user, reaction_activity, activity_id), - {:ok, activity} <- insert(unreact_data, local), - {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), - :ok <- maybe_federate(activity) do - {:ok, activity, object} - else - {:error, error} -> Repo.rollback(error) - end - end - @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) :: {:ok, Activity.t(), Object.t()} | {:error, any()} def announce( diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 8ed91e257..d58df9394 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -45,5 +45,13 @@ def handle_undoing(%{data: %{"type" => "Like"}} = object) do end end + def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do + with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]), + {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object), + {:ok, _} <- Repo.delete(object) do + :ok + end + end + def handle_undoing(object), do: {:error, ["don't know how to handle", object]} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a60b27bea..94849b5f5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -806,28 +806,6 @@ def handle_incoming( end end - def handle_incoming( - %{ - "type" => "Undo", - "object" => %{"type" => "EmojiReact", "id" => reaction_activity_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, activity, _} <- - ActivityPub.unreact_with_emoji(actor, reaction_activity_id, - activity_id: id, - local: false - ) do - {:ok, activity} - else - _e -> :error - end - end - def handle_incoming( %{ "type" => "Undo", @@ -865,10 +843,11 @@ def handle_incoming( def handle_incoming( %{ "type" => "Undo", - "object" => %{"type" => "Like"} + "object" => %{"type" => type} } = data, _options - ) do + ) + when type in ["Like", "EmojiReact"] do with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index a670ea5bc..067ac875e 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -170,7 +170,7 @@ def unfavorite(id, user) do %Object{} = note <- Object.normalize(activity, false), %Activity{} = like <- Utils.get_existing_like(user.ap_id, note), {:ok, undo, _} <- Builder.undo(user, like), - {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: false) do + {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do {:ok, activity} else {:find_activity, _} -> {:error, :not_found} @@ -189,8 +189,10 @@ def react_with_emoji(id, user, emoji) do end def unreact_with_emoji(id, user, emoji) do - with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji) do - ActivityPub.unreact_with_emoji(user, reaction_activity.data["id"]) + with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji), + {:ok, undo, _} <- Builder.undo(user, reaction_activity), + {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do + {:ok, activity} else _ -> {:error, dgettext("errors", "Could not remove reaction emoji")} 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 1bdb3aa4d..4aa5c1dd8 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -98,7 +98,8 @@ def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{ "id" => activity_id, "emoji" => emoji }) do - with {:ok, _activity, _object} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji), + with {:ok, _activity} <- + CommonAPI.unreact_with_emoji(activity_id, user, emoji), activity <- Activity.get_by_id(activity_id) do conn |> put_view(StatusView) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 797af66a0..cb2d41f0b 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -939,62 +939,6 @@ test "reverts emoji reaction on error" do end end - describe "unreacting to an object" do - test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do - Config.put([:instance, :federating], true) - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert called(Federator.publish(reaction_activity)) - - {:ok, unreaction_activity, _object} = - ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) - - assert called(Federator.publish(unreaction_activity)) - end - - test "adds an undo activity to the db" do - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - {:ok, unreaction_activity, _object} = - ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) - - assert unreaction_activity.actor == reactor.ap_id - assert unreaction_activity.data["object"] == reaction_activity.data["id"] - - object = Object.get_by_ap_id(object.data["id"]) - assert object.data["reaction_count"] == 0 - assert object.data["reactions"] == [] - end - - test "reverts emoji unreact on error" do - [user, reactor] = insert_list(2, :user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"}) - object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "😀") - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = - ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) - end - - object = Object.get_by_ap_id(object.data["id"]) - - assert object.data["reaction_count"] == 1 - assert object.data["reactions"] == [["😀", [reactor.ap_id]]] - end - end - describe "announcing an object" do test "adds an announce activity to the db" do note_activity = insert(:note_activity) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 61ef72742..abcfdfa2f 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -23,10 +23,38 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, like} = CommonAPI.favorite(user, post.id) + {:ok, reaction, _} = CommonAPI.react_with_emoji(post.id, user, "👍") + {:ok, undo_data, _meta} = Builder.undo(user, like) {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true) - %{like_undo: like_undo, post: post, like: like} + {:ok, undo_data, _meta} = Builder.undo(user, reaction) + {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + %{ + like_undo: like_undo, + post: post, + like: like, + reaction_undo: reaction_undo, + reaction: reaction + } + end + + test "a reaction undo removes the reaction from the object", %{ + reaction_undo: reaction_undo, + post: post + } do + {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["reaction_count"] == 0 + assert object.data["reactions"] == [] + end + + test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do + {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo) + refute Activity.get_by_id(reaction.id) end test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index bc0c1a791..0664b7f90 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -295,10 +295,11 @@ test "unreacting to a status with an emoji" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") - {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") + {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") assert unreaction.data["type"] == "Undo" assert unreaction.data["object"] == reaction.data["id"] + assert unreaction.local end test "repeating a status" do 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 61a1689b9..299dbad41 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -3,12 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do + use Oban.Testing, repo: Pleroma.Repo use Pleroma.Web.ConnCase alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -41,7 +43,9 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) - {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _reaction, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + + ObanHelpers.perform_all() result = conn @@ -52,7 +56,9 @@ test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do assert %{"id" => id} = json_response(result, 200) assert to_string(activity.id) == id - object = Object.normalize(activity) + ObanHelpers.perform_all() + + object = Object.get_by_ap_id(activity.data["object"]) assert object.data["reaction_count"] == 0 end From a3bb2e5474ee068bf375b24df8906e51654c9699 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 16:42:34 +0200 Subject: [PATCH 085/337] Undoing: Move undoing announcements to the pipeline everywhere. --- lib/pleroma/user.ex | 10 +--- lib/pleroma/web/activity_pub/activity_pub.ex | 28 ----------- lib/pleroma/web/activity_pub/side_effects.ex | 8 ++++ .../web/activity_pub/transmogrifier.ex | 21 +-------- lib/pleroma/web/common_api/common_api.ex | 9 ++-- .../controllers/status_controller.ex | 6 +-- test/notification_test.exs | 2 +- test/web/activity_pub/activity_pub_test.exs | 46 ------------------- test/web/activity_pub/side_effects_test.exs | 26 ++++++++++- .../transmogrifier/undo_handling_test.exs | 5 +- 10 files changed, 45 insertions(+), 116 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 0136ba119..aa675a521 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1554,7 +1554,7 @@ defp delete_activity(%{data: %{"type" => "Create"}} = activity) do |> ActivityPub.delete() end - defp delete_activity(%{data: %{"type" => "Like"}} = activity) do + defp delete_activity(%{data: %{"type" => type}} = activity) when type in ["Like", "Announce"] do actor = activity.actor |> get_cached_by_ap_id() @@ -1564,14 +1564,6 @@ defp delete_activity(%{data: %{"type" => "Like"}} = activity) do Pipeline.common_pipeline(undo, local: true) end - defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do - object = Object.normalize(activity) - - activity.actor - |> get_cached_by_ap_id() - |> ActivityPub.unannounce(object) - end - defp delete_activity(_activity), do: "Doing nothing" def html_filter_policy(%User{no_rich_text: true}) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c94af3b5f..be3d72c82 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -402,34 +402,6 @@ defp do_announce(user, object, activity_id, local, public) do end end - @spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) :: - {:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()} - def unannounce( - %User{} = actor, - %Object{} = object, - activity_id \\ nil, - local \\ true - ) do - with {:ok, result} <- - Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do - result - end - end - - defp do_unannounce(actor, object, activity_id, local) do - with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), - unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), - {:ok, unannounce_activity} <- insert(unannounce_data, local), - :ok <- maybe_federate(unannounce_activity), - {:ok, _activity} <- Repo.delete(announce_activity), - {:ok, object} <- remove_announce_from_object(announce_activity, object) do - {:ok, unannounce_activity, object} - else - nil -> {:ok, object} - {:error, error} -> Repo.rollback(error) - end - end - @spec follow(User.t(), User.t(), String.t() | nil, boolean()) :: {:ok, Activity.t()} | {:error, any()} def follow(follower, followed, activity_id \\ nil, local \\ true) do diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index d58df9394..146d30ac1 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -53,5 +53,13 @@ def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do end end + def handle_undoing(%{data: %{"type" => "Announce"}} = object) do + with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]), + {:ok, _} <- Utils.remove_announce_from_object(object, liked_object), + {:ok, _} <- Repo.delete(object) do + :ok + end + end + def handle_undoing(object), do: {:error, ["don't know how to handle", object]} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 94849b5f5..afa171448 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -768,25 +768,6 @@ def handle_incoming( end end - def handle_incoming( - %{ - "type" => "Undo", - "object" => %{"type" => "Announce", "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, _} <- ActivityPub.unannounce(actor, object, id, false) do - {:ok, activity} - else - _e -> :error - end - end - def handle_incoming( %{ "type" => "Undo", @@ -847,7 +828,7 @@ def handle_incoming( } = data, _options ) - when type in ["Like", "EmojiReact"] do + when type in ["Like", "EmojiReact", "Announce"] do with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 067ac875e..fc8246871 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -107,9 +107,12 @@ def repeat(id, user, params \\ %{}) do def unrepeat(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- - {:find_activity, Activity.get_by_id(id)} do - object = Object.normalize(activity) - ActivityPub.unannounce(user, object) + {:find_activity, Activity.get_by_id(id)}, + %Object{} = note <- Object.normalize(activity, false), + %Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note), + {:ok, undo, _} <- Builder.undo(user, announce), + {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do + {:ok, activity} else {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not unrepeat")} diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 2a5eac9d9..12e3ba15e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -206,9 +206,9 @@ def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do end @doc "POST /api/v1/statuses/:id/unreblog" - def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do + def unreblog(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + with {:ok, _unannounce} <- CommonAPI.unrepeat(activity_id, user), + %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) end end diff --git a/test/notification_test.exs b/test/notification_test.exs index 7d5b82993..09714f4c5 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -758,7 +758,7 @@ test "repeating an activity results in 1 notification, then 0 if the activity is assert length(Notification.for_user(user)) == 1 - {:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user) + {:ok, _} = CommonAPI.unrepeat(activity.id, other_user) assert Enum.empty?(Notification.for_user(user)) end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index cb2d41f0b..2c3d354f2 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1008,52 +1008,6 @@ test "does not add an announce activity to the db if the announcer is not the au end end - describe "unannouncing an object" do - test "unannouncing a previously announced object" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - # Unannouncing an object that is not announced does nothing - {:ok, object} = ActivityPub.unannounce(user, object) - refute object.data["announcement_count"] - - {:ok, announce_activity, object} = ActivityPub.announce(user, object) - assert object.data["announcement_count"] == 1 - - {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object) - assert object.data["announcement_count"] == 0 - - assert unannounce_activity.data["to"] == [ - User.ap_followers(user), - object.data["actor"] - ] - - assert unannounce_activity.data["type"] == "Undo" - assert unannounce_activity.data["object"] == announce_activity.data - assert unannounce_activity.data["actor"] == user.ap_id - assert unannounce_activity.data["context"] == announce_activity.data["context"] - - assert Activity.get_by_id(announce_activity.id) == nil - end - - test "reverts unannouncing on error" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - {:ok, _announce_activity, object} = ActivityPub.announce(user, object) - assert object.data["announcement_count"] == 1 - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.unannounce(user, object) - end - - object = Object.get_by_ap_id(object.data["id"]) - assert object.data["announcement_count"] == 1 - end - end - describe "uploading files" do test "copies the file to the configured folder" do file = %Plug.Upload{ diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index abcfdfa2f..00241320b 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -22,8 +22,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do user = insert(:user) {:ok, post} = CommonAPI.post(poster, %{"status" => "hey"}) {:ok, like} = CommonAPI.favorite(user, post.id) - {:ok, reaction, _} = CommonAPI.react_with_emoji(post.id, user, "👍") + {:ok, announce, _} = CommonAPI.repeat(post.id, user) {:ok, undo_data, _meta} = Builder.undo(user, like) {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true) @@ -31,15 +31,37 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, undo_data, _meta} = Builder.undo(user, reaction) {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true) + {:ok, undo_data, _meta} = Builder.undo(user, announce) + {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true) + %{ like_undo: like_undo, post: post, like: like, reaction_undo: reaction_undo, - reaction: reaction + reaction: reaction, + announce_undo: announce_undo, + announce: announce } end + test "an announce undo removes the announce from the object", %{ + announce_undo: announce_undo, + post: post + } do + {:ok, _announce_undo, _} = SideEffects.handle(announce_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["announcement_count"] == 0 + assert object.data["announcements"] == [] + end + + test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do + {:ok, _announce_undo, _} = SideEffects.handle(announce_undo) + refute Activity.get_by_id(announce.id) + end + test "a reaction undo removes the reaction from the object", %{ reaction_undo: reaction_undo, post: post diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs index bf2a6bc5b..281cf5b0d 100644 --- a/test/web/activity_pub/transmogrifier/undo_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -125,11 +125,8 @@ test "it works for incoming unannounces with an existing notice" do {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) assert data["type"] == "Undo" - assert object_data = data["object"] - assert object_data["type"] == "Announce" - assert object_data["object"] == activity.data["object"] - assert object_data["id"] == + assert data["object"] == "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" end From 92caae592338a3ca307686e7644f2de18bb57ce5 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 May 2020 18:00:37 +0200 Subject: [PATCH 086/337] Undoing: Move undoing blocks to the pipeline everywhere. --- lib/pleroma/web/activity_pub/activity_pub.ex | 21 -------- lib/pleroma/web/activity_pub/side_effects.ex | 12 +++++ .../web/activity_pub/transmogrifier.ex | 51 ++++++------------- lib/pleroma/web/activity_pub/utils.ex | 49 ------------------ lib/pleroma/web/common_api/common_api.ex | 8 +++ .../controllers/account_controller.ex | 3 +- test/web/activity_pub/activity_pub_test.exs | 34 +------------ test/web/activity_pub/side_effects_test.exs | 25 ++++++++- .../transmogrifier/undo_handling_test.exs | 4 +- test/web/activity_pub/utils_test.exs | 28 ---------- 10 files changed, 63 insertions(+), 172 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index be3d72c82..78e8c0cbe 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -532,27 +532,6 @@ defp do_block(blocker, blocked, activity_id, local) do end end - @spec unblock(User.t(), User.t(), String.t() | nil, boolean()) :: - {:ok, Activity.t()} | {:error, any()} | nil - def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do - with {:ok, result} <- - Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do - result - end - end - - defp do_unblock(blocker, blocked, activity_id, local) do - with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked), - unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id), - {:ok, activity} <- insert(unblock_data, local), - :ok <- maybe_federate(activity) do - {:ok, activity} - else - nil -> nil - {:error, error} -> Repo.rollback(error) - end - end - @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()} def flag( %{ diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 146d30ac1..3fad6e4d8 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils def handle(object, meta \\ []) @@ -61,5 +62,16 @@ def handle_undoing(%{data: %{"type" => "Announce"}} = object) do end end + def handle_undoing( + %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object + ) do + with %User{} = blocker <- User.get_cached_by_ap_id(blocker), + %User{} = blocked <- User.get_cached_by_ap_id(blocked), + {:ok, _} <- User.unblock(blocker, blocked), + {:ok, _} <- Repo.delete(object) do + :ok + end + end + def handle_undoing(object), do: {:error, ["don't know how to handle", object]} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index afa171448..65ae643ed 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -787,40 +787,6 @@ def handle_incoming( end end - def handle_incoming( - %{ - "type" => "Undo", - "object" => %{"type" => "Block", "object" => blocked}, - "actor" => blocker, - "id" => id - } = _data, - _options - ) do - with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked), - {:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker), - {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do - User.unblock(blocker, blocked) - {:ok, activity} - else - _e -> :error - end - end - - def handle_incoming( - %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data, - _options - ) do - with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked), - {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker), - {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do - User.unfollow(blocker, blocked) - User.block(blocker, blocked) - {:ok, activity} - else - _e -> :error - end - end - def handle_incoming( %{ "type" => "Undo", @@ -828,7 +794,7 @@ def handle_incoming( } = data, _options ) - when type in ["Like", "EmojiReact", "Announce"] do + when type in ["Like", "EmojiReact", "Announce", "Block"] do with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} end @@ -852,6 +818,21 @@ def handle_incoming( end end + def handle_incoming( + %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data, + _options + ) do + with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked), + {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker), + {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do + User.unfollow(blocker, blocked) + User.block(blocker, blocked) + {:ok, activity} + else + _e -> :error + end + end + def handle_incoming( %{ "type" => "Move", diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2d685ecc0..95fb382f0 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -562,45 +562,6 @@ def make_announce_data( |> maybe_put("id", activity_id) end - @doc """ - Make unannounce activity data for the given actor and object - """ - def make_unannounce_data( - %User{ap_id: ap_id} = user, - %Activity{data: %{"context" => context, "object" => object}} = activity, - activity_id - ) do - object = Object.normalize(object) - - %{ - "type" => "Undo", - "actor" => ap_id, - "object" => activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => context - } - |> maybe_put("id", activity_id) - end - - def make_unlike_data( - %User{ap_id: ap_id} = user, - %Activity{data: %{"context" => context, "object" => object}} = activity, - activity_id - ) do - object = Object.normalize(object) - - %{ - "type" => "Undo", - "actor" => ap_id, - "object" => activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => context - } - |> maybe_put("id", activity_id) - end - def make_undo_data( %User{ap_id: actor, follower_address: follower_address}, %Activity{ @@ -688,16 +649,6 @@ def make_block_data(blocker, blocked, activity_id) do |> maybe_put("id", activity_id) end - def make_unblock_data(blocker, blocked, block_activity, activity_id) do - %{ - "type" => "Undo", - "actor" => blocker.ap_id, - "to" => [blocked.ap_id], - "object" => block_activity.data - } - |> maybe_put("id", activity_id) - end - #### Create-related helpers def make_create_data(params, additional) do diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index fc8246871..2a1eb7f37 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -24,6 +24,14 @@ defmodule Pleroma.Web.CommonAPI do require Pleroma.Constants require Logger + def unblock(blocker, blocked) do + with %Activity{} = block <- Utils.fetch_latest_block(blocker, blocked), + {:ok, unblock_data, _} <- Builder.undo(blocker, block), + {:ok, unblock, _} <- Pipeline.common_pipeline(unblock_data, local: true) do + {:ok, unblock} + end + end + def follow(follower, followed) do timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 61b0e2f63..2b208ddab 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -356,8 +356,7 @@ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do @doc "POST /api/v1/accounts/:id/unblock" def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do - with {:ok, _user_block} <- User.unblock(blocker, blocked), - {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do + with {:ok, _activity} <- CommonAPI.unblock(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 2c3d354f2..7824095c7 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1114,7 +1114,7 @@ test "creates an undo activity for a pending follow request" do end end - describe "blocking / unblocking" do + describe "blocking" do test "reverts block activity on error" do [blocker, blocked] = insert_list(2, :user) @@ -1136,38 +1136,6 @@ test "creates a block activity" do assert activity.data["actor"] == blocker.ap_id assert activity.data["object"] == blocked.ap_id end - - test "reverts unblock activity on error" do - [blocker, blocked] = insert_list(2, :user) - {:ok, block_activity} = ActivityPub.block(blocker, blocked) - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked) - end - - assert block_activity.data["type"] == "Block" - assert block_activity.data["actor"] == blocker.ap_id - - assert Repo.aggregate(Activity, :count, :id) == 1 - assert Repo.aggregate(Object, :count, :id) == 1 - end - - test "creates an undo activity for the last block" do - blocker = insert(:user) - blocked = insert(:user) - - {:ok, block_activity} = ActivityPub.block(blocker, blocked) - {:ok, activity} = ActivityPub.unblock(blocker, blocked) - - assert activity.data["type"] == "Undo" - assert activity.data["actor"] == blocker.ap_id - - embedded_object = activity.data["object"] - assert is_map(embedded_object) - assert embedded_object["type"] == "Block" - assert embedded_object["object"] == blocked.ap_id - assert embedded_object["id"] == block_activity.data["id"] - end end describe "deletion" do diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 00241320b..f41a7f3c1 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.SideEffects @@ -24,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, like} = CommonAPI.favorite(user, post.id) {:ok, reaction, _} = CommonAPI.react_with_emoji(post.id, user, "👍") {:ok, announce, _} = CommonAPI.repeat(post.id, user) + {:ok, block} = ActivityPub.block(user, poster) + User.block(user, poster) {:ok, undo_data, _meta} = Builder.undo(user, like) {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true) @@ -34,6 +37,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, undo_data, _meta} = Builder.undo(user, announce) {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true) + {:ok, undo_data, _meta} = Builder.undo(user, block) + {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true) + %{ like_undo: like_undo, post: post, @@ -41,10 +47,27 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do reaction_undo: reaction_undo, reaction: reaction, announce_undo: announce_undo, - announce: announce + announce: announce, + block_undo: block_undo, + block: block, + poster: poster, + user: user } end + test "deletes the original block", %{block_undo: block_undo, block: block} do + {:ok, _block_undo, _} = SideEffects.handle(block_undo) + refute Activity.get_by_id(block.id) + end + + test "unblocks the blocked user", %{block_undo: block_undo, block: block} do + blocker = User.get_by_ap_id(block.data["actor"]) + blocked = User.get_by_ap_id(block.data["object"]) + + {:ok, _block_undo, _} = SideEffects.handle(block_undo) + refute User.blocks?(blocker, blocked) + end + test "an announce undo removes the announce from the object", %{ announce_undo: announce_undo, post: post diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs index 281cf5b0d..6f5e61ac3 100644 --- a/test/web/activity_pub/transmogrifier/undo_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -176,9 +176,7 @@ test "it works for incoming unblocks with an existing block" do {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) assert data["type"] == "Undo" - assert data["object"]["type"] == "Block" - assert data["object"]["object"] == user.ap_id - assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["object"] == block_data["id"] blocker = User.get_cached_by_ap_id(data["actor"]) diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index b0bfed917..b8d811c73 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -102,34 +102,6 @@ test "works with an object has tags as map" do end end - describe "make_unlike_data/3" do - test "returns data for unlike activity" do - user = insert(:user) - like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"}) - - object = Object.normalize(like_activity.data["object"]) - - assert Utils.make_unlike_data(user, like_activity, nil) == %{ - "type" => "Undo", - "actor" => user.ap_id, - "object" => like_activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => like_activity.data["context"] - } - - assert Utils.make_unlike_data(user, like_activity, "9mJEZK0tky1w2xD2vY") == %{ - "type" => "Undo", - "actor" => user.ap_id, - "object" => like_activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => like_activity.data["context"], - "id" => "9mJEZK0tky1w2xD2vY" - } - end - end - describe "make_like_data" do setup do user = insert(:user) From 0a1394cc1a38ce66b1b30d728856ae891aa3d7b0 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 20:14:22 +0400 Subject: [PATCH 087/337] Add OpenAPI spec for PollController --- .../web/api_spec/operations/poll_operation.ex | 76 +++++++++++++++++++ lib/pleroma/web/api_spec/schemas/poll.ex | 62 +++++++++++++-- .../controllers/poll_controller.ex | 8 +- .../controllers/poll_controller_test.exs | 38 +++++++--- 4 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/poll_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/poll_operation.ex b/lib/pleroma/web/api_spec/operations/poll_operation.ex new file mode 100644 index 000000000..b953323e9 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/poll_operation.ex @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PollOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Poll + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def show_operation do + %Operation{ + tags: ["Polls"], + summary: "View a poll", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [id_param()], + operationId: "PollController.show", + responses: %{ + 200 => Operation.response("Poll", "application/json", Poll), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def vote_operation do + %Operation{ + tags: ["Polls"], + summary: "Block a domain", + parameters: [id_param()], + operationId: "PollController.vote", + requestBody: vote_request(), + security: [%{"oAuth" => ["write:statuses"]}], + responses: %{ + 200 => Operation.response("Poll", "application/json", Poll), + 422 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + defp id_param do + Operation.parameter(:id, :path, FlakeID, "Poll ID", + example: "123", + required: true + ) + end + + defp vote_request do + request_body( + "Parameters", + %Schema{ + type: :object, + properties: %{ + choices: %Schema{ + type: :array, + items: %Schema{type: :integer}, + description: "Array of own votes containing index for each option (starting from 0)" + } + }, + required: [:choices] + }, + required: true, + example: %{ + "choices" => [0, 1, 2] + } + ) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 0474b550b..c62096db0 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -11,26 +11,72 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do OpenApiSpex.schema(%{ title: "Poll", - description: "Response schema for account custom fields", + description: "Represents a poll attached to a status", type: :object, properties: %{ id: FlakeID, - expires_at: %Schema{type: :string, format: "date-time"}, - expired: %Schema{type: :boolean}, - multiple: %Schema{type: :boolean}, - votes_count: %Schema{type: :integer}, - voted: %Schema{type: :boolean}, - emojis: %Schema{type: :array, items: Emoji}, + expires_at: %Schema{ + type: :string, + format: :"date-time", + nullable: true, + description: "When the poll ends" + }, + expired: %Schema{type: :boolean, description: "Is the poll currently expired?"}, + multiple: %Schema{ + type: :boolean, + description: "Does the poll allow multiple-choice answers?" + }, + votes_count: %Schema{ + type: :integer, + nullable: true, + description: "How many votes have been received. Number, or null if `multiple` is false." + }, + voted: %Schema{ + type: :boolean, + nullable: true, + description: + "When called with a user token, has the authorized user voted? Boolean, or null if no current user." + }, + emojis: %Schema{ + type: :array, + items: Emoji, + description: "Custom emoji to be used for rendering poll options." + }, options: %Schema{ type: :array, items: %Schema{ + title: "PollOption", type: :object, properties: %{ title: %Schema{type: :string}, votes_count: %Schema{type: :integer} } - } + }, + description: "Possible answers for the poll." } + }, + example: %{ + id: "34830", + expires_at: "2019-12-05T04:05:08.302Z", + expired: true, + multiple: false, + votes_count: 10, + voters_count: nil, + voted: true, + own_votes: [ + 1 + ], + options: [ + %{ + title: "accept", + votes_count: 6 + }, + %{ + title: "deny", + votes_count: 4 + } + ], + emojis: [] } }) end diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index af9b66eff..db46ffcfc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.MastodonAPI.PollController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug( OAuthScopesPlug, %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show @@ -22,8 +24,10 @@ defmodule Pleroma.Web.MastodonAPI.PollController do plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation + @doc "GET /api/v1/polls/:id" - def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do @@ -35,7 +39,7 @@ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do end @doc "POST /api/v1/polls/:id/votes" - def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do + def vote(%{assigns: %{user: user}, body_params: %{choices: choices}} = conn, %{id: id}) do with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user), diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs index 88b13a25a..d8f34aa86 100644 --- a/test/web/mastodon_api/controllers/poll_controller_test.exs +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -24,7 +24,7 @@ test "returns poll entity for object id", %{user: user, conn: conn} do conn = get(conn, "/api/v1/polls/#{object.id}") - response = json_response(conn, 200) + response = json_response_and_validate_schema(conn, 200) id = to_string(object.id) assert %{"id" => ^id, "expired" => false, "multiple" => false} = response end @@ -43,7 +43,7 @@ test "does not expose polls for private statuses", %{conn: conn} do conn = get(conn, "/api/v1/polls/#{object.id}") - assert json_response(conn, 404) + assert json_response_and_validate_schema(conn, 404) end end @@ -65,9 +65,12 @@ test "votes are added to the poll", %{conn: conn} do object = Object.normalize(activity) - conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) - assert json_response(conn, 200) + assert json_response_and_validate_schema(conn, 200) object = Object.get_by_id(object.id) assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> @@ -85,8 +88,9 @@ test "author can't vote", %{user: user, conn: conn} do object = Object.normalize(activity) assert conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) - |> json_response(422) == %{"error" => "Poll's author can't vote"} + |> json_response_and_validate_schema(422) == %{"error" => "Poll's author can't vote"} object = Object.get_by_id(object.id) @@ -105,8 +109,9 @@ test "does not allow multiple choices on a single-choice question", %{conn: conn object = Object.normalize(activity) assert conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) - |> json_response(422) == %{"error" => "Too many choices"} + |> json_response_and_validate_schema(422) == %{"error" => "Too many choices"} object = Object.get_by_id(object.id) @@ -126,15 +131,21 @@ test "does not allow choice index to be greater than options count", %{conn: con object = Object.normalize(activity) - conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) - assert json_response(conn, 422) == %{"error" => "Invalid indices"} + assert json_response_and_validate_schema(conn, 422) == %{"error" => "Invalid indices"} end test "returns 404 error when object is not exist", %{conn: conn} do - conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) - assert json_response(conn, 404) == %{"error" => "Record not found"} + assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"} end test "returns 404 when poll is private and not available for user", %{conn: conn} do @@ -149,9 +160,12 @@ test "returns 404 when poll is private and not available for user", %{conn: conn object = Object.normalize(activity) - conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) - assert json_response(conn, 404) == %{"error" => "Record not found"} + assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"} end end end From 6ba25d11973e56008e5d674313421197ff418d6d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 23:19:16 +0400 Subject: [PATCH 088/337] Add Attachment schema --- .../web/api_spec/schemas/attachment.ex | 68 +++++++++++++++++++ .../web/api_spec/schemas/scheduled_status.ex | 53 +++++++++++++++ lib/pleroma/web/api_spec/schemas/status.ex | 18 +---- 3 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/attachment.ex create mode 100644 lib/pleroma/web/api_spec/schemas/scheduled_status.ex diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex new file mode 100644 index 000000000..c146c416e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/attachment.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Attachment", + description: "Represents a file or media attachment that can be added to a status.", + type: :object, + requried: [:id, :url, :preview_url], + properties: %{ + id: %Schema{type: :string}, + url: %Schema{ + type: :string, + format: :uri, + description: "The location of the original full-size attachment" + }, + remote_url: %Schema{ + type: :string, + format: :uri, + description: + "The location of the full-size original attachment on the remote website. String (URL), or null if the attachment is local", + nullable: true + }, + preview_url: %Schema{ + type: :string, + format: :uri, + description: "The location of a scaled-down preview of the attachment" + }, + text_url: %Schema{ + type: :string, + format: :uri, + description: "A shorter URL for the attachment" + }, + description: %Schema{ + type: :string, + nullable: true, + description: + "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load" + }, + type: %Schema{ + type: :string, + enum: ["image", "video", "audio", "unknown"], + description: "The type of the attachment" + }, + pleroma: %Schema{ + type: :object, + properties: %{ + mime_type: %Schema{type: :string, description: "mime type of the attachment"} + } + } + }, + example: %{ + id: "1638338801", + type: "image", + url: "someurl", + remote_url: "someurl", + preview_url: "someurl", + text_url: "someurl", + description: nil, + pleroma: %{mime_type: "image/png"} + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex new file mode 100644 index 000000000..f0bc4ee3c --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -0,0 +1,53 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + alias Pleroma.Web.ApiSpec.Schemas.Poll + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ScheduledStatus", + description: "Represents a status that will be published at a future scheduled date.", + type: :object, + required: [:id, :scheduled_at, :params], + properties: %{ + id: %Schema{type: :string}, + scheduled_at: %Schema{type: :string, format: :"date-time"}, + media_attachments: %Schema{type: :array, format: :"date-time"}, + params: %Schema{ + type: :object, + required: [:text, :visibility], + properties: %{ + text: %Schema{type: :string, nullable: true}, + media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, + sensitive: %Schema{type: :boolean, nullable: true}, + spoiler_text: %Schema{type: :string, nullable: true}, + visibility: %Schema{type: VisibilityScope, nullable: true}, + scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, + poll: %Schema{type: Poll, nullable: true}, + in_reply_to_id: %Schema{type: :string, nullable: true} + } + } + }, + example: %{ + id: "3221", + scheduled_at: "2019-12-05T12:33:01.000Z", + params: %{ + text: "test content", + media_ids: nil, + sensitive: nil, + spoiler_text: nil, + visibility: nil, + scheduled_at: nil, + poll: nil, + idempotency: nil, + in_reply_to_id: nil + }, + media_attachments: [] + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index aef0588d4..d44636a48 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Poll @@ -50,22 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do language: %Schema{type: :string, nullable: true}, media_attachments: %Schema{ type: :array, - items: %Schema{ - type: :object, - properties: %{ - id: %Schema{type: :string}, - url: %Schema{type: :string, format: :uri}, - remote_url: %Schema{type: :string, format: :uri}, - preview_url: %Schema{type: :string, format: :uri}, - text_url: %Schema{type: :string, format: :uri}, - description: %Schema{type: :string}, - type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]}, - pleroma: %Schema{ - type: :object, - properties: %{mime_type: %Schema{type: :string}} - } - } - } + items: Attachment }, mentions: %Schema{ type: :array, From 332e016bcdbda5dca90d916bc62a9c67544b5323 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 5 May 2020 23:42:18 +0400 Subject: [PATCH 089/337] Add OpenAPI spec for ScheduledActivityController --- .../scheduled_activity_operation.ex | 96 +++++++++++++++++++ .../web/api_spec/schemas/scheduled_status.ex | 7 +- .../scheduled_activity_controller.ex | 12 ++- test/support/helpers.ex | 8 +- .../scheduled_activity_controller_test.exs | 34 ++++--- .../mastodon_api/views/status_view_test.exs | 8 +- 6 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex new file mode 100644 index 000000000..fe675a923 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex @@ -0,0 +1,96 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "View scheduled statuses", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: pagination_params(), + operationId: "ScheduledActivity.index", + responses: %{ + 200 => + Operation.response("Array of ScheduledStatus", "application/json", %Schema{ + type: :array, + items: ScheduledStatus + }) + } + } + end + + def show_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "View a single scheduled status", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [id_param()], + operationId: "ScheduledActivity.show", + responses: %{ + 200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "Schedule a status", + operationId: "ScheduledActivity.update", + security: [%{"oAuth" => ["write:statuses"]}], + parameters: [id_param()], + requestBody: + request_body("Parameters", %Schema{ + type: :object, + properties: %{ + scheduled_at: %Schema{ + type: :string, + format: :"date-time", + description: + "ISO 8601 Datetime at which the status will be published. Must be at least 5 minutes into the future." + } + } + }), + responses: %{ + 200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "Cancel a scheduled status", + security: [%{"oAuth" => ["write:statuses"]}], + parameters: [id_param()], + operationId: "ScheduledActivity.delete", + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + defp id_param do + Operation.parameter(:id, :path, FlakeID, "Poll ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index f0bc4ee3c..0520d0848 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -4,8 +4,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Poll + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex @@ -17,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do properties: %{ id: %Schema{type: :string}, scheduled_at: %Schema{type: :string, format: :"date-time"}, - media_attachments: %Schema{type: :array, format: :"date-time"}, + media_attachments: %Schema{type: :array, items: Attachment}, params: %Schema{ type: :object, required: [:text, :visibility], @@ -47,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do idempotency: nil, in_reply_to_id: nil }, - media_attachments: [] + media_attachments: [Attachment.schema().example] } }) end diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 899b78873..1719c67ea 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do alias Pleroma.ScheduledActivity alias Pleroma.Web.MastodonAPI.MastodonAPI - plug(:assign_scheduled_activity when action != :index) - @oauth_read_actions [:show, :index] + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) + plug(:assign_scheduled_activity when action != :index) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation + @doc "GET /api/v1/scheduled_statuses" def index(%{assigns: %{user: user}} = conn, params) do + params = Map.new(params, fn {key, value} -> {to_string(key), value} end) + with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do conn |> add_link_headers(scheduled_activities) @@ -35,7 +39,7 @@ def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) end @doc "PUT /api/v1/scheduled_statuses/:id" - def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do + def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do render(conn, "show.json", scheduled_activity: scheduled_activity) end @@ -48,7 +52,7 @@ def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params end end - defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do + defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case ScheduledActivity.get(user, id) do %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/test/support/helpers.ex b/test/support/helpers.ex index e68e9bfd2..26281b45e 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -40,12 +40,18 @@ defmacro __using__(_opts) do clear_config: 2 ] - def to_datetime(naive_datetime) do + def to_datetime(%NaiveDateTime{} = naive_datetime) do naive_datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.truncate(:second) end + def to_datetime(datetime) when is_binary(datetime) do + datetime + |> NaiveDateTime.from_iso8601!() + |> to_datetime() + end + def collect_ids(collection) do collection |> Enum.map(& &1.id) diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index f86274d57..1ff871c89 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -24,19 +24,19 @@ test "shows scheduled activities" do # min_id conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") - result = json_response(conn_res, 200) + result = json_response_and_validate_schema(conn_res, 200) assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result # since_id conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") - result = json_response(conn_res, 200) + result = json_response_and_validate_schema(conn_res, 200) assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result # max_id conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") - result = json_response(conn_res, 200) + result = json_response_and_validate_schema(conn_res, 200) assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result end @@ -46,12 +46,12 @@ test "shows a scheduled activity" do res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) + assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200) assert scheduled_activity_id == scheduled_activity.id |> to_string() res_conn = get(conn, "/api/v1/scheduled_statuses/404") - assert %{"error" => "Record not found"} = json_response(res_conn, 404) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) end test "updates a scheduled activity" do @@ -74,22 +74,32 @@ test "updates a scheduled activity" do assert job.args == %{"activity_id" => scheduled_activity.id} assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) - new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) + new_scheduled_at = + NaiveDateTime.utc_now() + |> Timex.shift(minutes: 120) + |> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime) res_conn = - put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ scheduled_at: new_scheduled_at }) - assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) + assert %{"scheduled_at" => expected_scheduled_at} = + json_response_and_validate_schema(res_conn, 200) + assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) job = refresh_record(job) assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) - res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + res_conn = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) - assert %{"error" => "Record not found"} = json_response(res_conn, 404) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) end test "deletes a scheduled activity" do @@ -115,7 +125,7 @@ test "deletes a scheduled activity" do |> assign(:user, user) |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{} = json_response(res_conn, 200) + assert %{} = json_response_and_validate_schema(res_conn, 200) refute Repo.get(ScheduledActivity, scheduled_activity.id) refute Repo.get(Oban.Job, job.id) @@ -124,6 +134,6 @@ test "deletes a scheduled activity" do |> assign(:user, user) |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{"error" => "Record not found"} = json_response(res_conn, 404) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) end end diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 6791c2fb0..451723e60 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -402,11 +402,17 @@ test "attachments" do pleroma: %{mime_type: "image/png"} } + api_spec = Pleroma.Web.ApiSpec.spec() + assert expected == StatusView.render("attachment.json", %{attachment: object}) + OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec) # If theres a "id", use that instead of the generated one object = Map.put(object, "id", 2) - assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) + result = StatusView.render("attachment.json", %{attachment: object}) + + assert %{id: "2"} = result + OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec) end test "put the url advertised in the Activity in to the url attribute" do From 06c69c0a0a03d7797213fc520b6bf24fab65a7e3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 6 May 2020 14:18:19 +0400 Subject: [PATCH 090/337] Fix description --- lib/pleroma/web/api_spec/operations/poll_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/poll_operation.ex b/lib/pleroma/web/api_spec/operations/poll_operation.ex index b953323e9..e15c7dc95 100644 --- a/lib/pleroma/web/api_spec/operations/poll_operation.ex +++ b/lib/pleroma/web/api_spec/operations/poll_operation.ex @@ -33,7 +33,7 @@ def show_operation do def vote_operation do %Operation{ tags: ["Polls"], - summary: "Block a domain", + summary: "Vote on a poll", parameters: [id_param()], operationId: "PollController.vote", requestBody: vote_request(), From bd261309cc27ebf5d2f78ea3c1474fe71ae8046d Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 6 May 2020 15:08:38 +0300 Subject: [PATCH 091/337] added `unread_notifications_count` for `/api/v1/accounts/verify_credentials` --- docs/API/differences_in_mastoapi_responses.md | 1 + lib/pleroma/notification.ex | 8 +++++ .../web/mastodon_api/views/account_view.ex | 29 ++++++++++++++++--- .../controllers/account_controller_test.exs | 3 ++ .../mastodon_api/views/account_view_test.exs | 18 ++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 8d1da936f..6d37d9008 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -61,6 +61,7 @@ Has these additional fields under the `pleroma` object: - `deactivated`: boolean, true when the user is deactivated - `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts - `unread_conversation_count`: The count of unread conversations. Only returned to the account owner. +- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner. ### Source diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 7fd1b2ff6..c135306ca 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -36,6 +36,14 @@ defmodule Pleroma.Notification do timestamps() end + @spec unread_notifications_count(User.t()) :: integer() + def unread_notifications_count(%User{id: user_id}) do + from(q in __MODULE__, + where: q.user_id == ^user_id and q.seen == false + ) + |> Repo.aggregate(:count, :id) + end + def changeset(%Notification{} = notification, attrs) do notification |> cast(attrs, [:seen]) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b4b61e74c..420bd586f 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -36,9 +36,11 @@ def render("index.json", %{users: users} = opts) do end def render("show.json", %{user: user} = opts) do - if User.visible_for?(user, opts[:for]), - do: do_render("show.json", opts), - else: %{} + if User.visible_for?(user, opts[:for]) do + do_render("show.json", opts) + else + %{} + end end def render("mention.json", %{user: user}) do @@ -221,7 +223,7 @@ defp do_render("show.json", %{user: user} = opts) do fields: user.fields, bot: bot, source: %{ - note: (user.bio || "") |> String.replace(~r(
), "\n") |> Pleroma.HTML.strip_tags(), + note: prepare_user_bio(user), sensitive: false, fields: user.raw_fields, pleroma: %{ @@ -253,8 +255,17 @@ defp do_render("show.json", %{user: user} = opts) do |> maybe_put_follow_requests_count(user, opts[:for]) |> maybe_put_allow_following_move(user, opts[:for]) |> maybe_put_unread_conversation_count(user, opts[:for]) + |> maybe_put_unread_notification_count(user, opts[:for]) end + defp prepare_user_bio(%User{bio: ""}), do: "" + + defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do + bio |> String.replace(~r(
), "\n") |> Pleroma.HTML.strip_tags() + end + + defp prepare_user_bio(_), do: "" + defp username_from_nickname(string) when is_binary(string) do hd(String.split(string, "@")) end @@ -350,6 +361,16 @@ defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{ defp maybe_put_unread_conversation_count(data, _, _), do: data + defp maybe_put_unread_notification_count(data, %User{id: user_id}, %User{id: user_id} = user) do + Kernel.put_in( + data, + [:pleroma, :unread_notifications_count], + Pleroma.Notification.unread_notifications_count(user) + ) + end + + defp maybe_put_unread_notification_count(data, _, _), do: data + defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(_), do: nil end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index b9da7e924..256a8b304 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -1196,12 +1196,15 @@ test "returns lists to which the account belongs" do describe "verify_credentials" do test "verify_credentials" do %{user: user, conn: conn} = oauth_access(["read:accounts"]) + [notification | _] = insert_list(7, :notification, user: user) + Pleroma.Notification.set_read_up_to(user, notification.id) conn = get(conn, "/api/v1/accounts/verify_credentials") response = json_response_and_validate_schema(conn, 200) assert %{"id" => id, "source" => %{"privacy" => "public"}} = response assert response["pleroma"]["chat_token"] + assert response["pleroma"]["unread_notifications_count"] == 6 assert id == to_string(user.id) end diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index 85fa4f6a2..5fb162141 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -466,6 +466,24 @@ test "shows unread_conversation_count only to the account owner" do :unread_conversation_count ] == 1 end + + test "shows unread_count only to the account owner" do + user = insert(:user) + insert_list(7, :notification, user: user) + other_user = insert(:user) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert AccountView.render( + "show.json", + %{user: user, for: other_user} + )[:pleroma][:unread_notifications_count] == nil + + assert AccountView.render( + "show.json", + %{user: user, for: user} + )[:pleroma][:unread_notifications_count] == 7 + end end describe "follow requests counter" do From 3c42caa85c51b4eaa447d6aafcfaa0bfceaa9beb Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 6 May 2020 16:20:47 +0300 Subject: [PATCH 092/337] apache chain issue fix --- installation/pleroma-apache.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf index b5640ac3d..0d627f2d7 100644 --- a/installation/pleroma-apache.conf +++ b/installation/pleroma-apache.conf @@ -32,9 +32,8 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on - SSLCertificateFile /etc/letsencrypt/live/${servername}/cert.pem + SSLCertificateFile /etc/letsencrypt/live/${servername}/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/${servername}/privkey.pem - SSLCertificateChainFile /etc/letsencrypt/live/${servername}/fullchain.pem # Mozilla modern configuration, tweak to your needs SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 From d7537a37c77dfef469106f12f0dd3649aad197da Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 6 May 2020 08:55:09 -0500 Subject: [PATCH 093/337] Add :chat to cheatsheet --- docs/configuration/cheatsheet.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 705c4c15e..2524918d4 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -8,6 +8,10 @@ For from source installations Pleroma configuration works by first importing the To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted. +## :chat + +* `enabled` - Enables the backend chat. Defaults to `true`. + ## :instance * `name`: The instance’s name. * `email`: Email used to reach an Administrator/Moderator of the instance. From 4b00eb93fe2cef97a5570b9cc6e6844898d31b9a Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 6 May 2020 18:04:16 +0300 Subject: [PATCH 094/337] fix for syslog compile with updated rebar3 --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index ee9d93bfb..28287cf97 100644 --- a/mix.lock +++ b/mix.lock @@ -37,7 +37,7 @@ "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, - "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "f3b4b184dcdd5f356b7c26c6cd72ab0918ba9dfb4061ccfaf519e562942af87b"}, + "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, @@ -102,7 +102,7 @@ "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"}, "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, - "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm", "769ddfabd0d2a16f3f9c17eb7509951e0ca4f68363fb26f2ee51a8ec4a49881a"}, + "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, "tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]}, "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, From 57736c18332b0017e01d90e56547af1f5f830b7a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 6 May 2020 16:30:05 -0500 Subject: [PATCH 095/337] Privacy option affects all push notifications, not just Direct Messages --- lib/pleroma/web/push/impl.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index a9f893f7b..7f80bb0c9 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -106,14 +106,13 @@ def build_content(notification, actor, object, mastodon_type \\ nil) def build_content( %{ - activity: %{data: %{"directMessage" => true}}, user: %{notification_settings: %{privacy_option: true}} - }, + } = notification, actor, _object, - _mastodon_type + mastodon_type ) do - %{title: "New Direct Message", body: "@#{actor.nickname}"} + %{title: format_title(notification, mastodon_type), body: "@#{actor.nickname}"} end def build_content(notification, actor, object, mastodon_type) do From a2580adc91ac757e47b88839f5fb723fb15305b1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 6 May 2020 16:42:27 -0500 Subject: [PATCH 096/337] Hide the sender when privacy option is enabled --- lib/pleroma/web/push/impl.ex | 4 ++-- test/web/push/impl_test.exs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 7f80bb0c9..691725702 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -108,11 +108,11 @@ def build_content( %{ user: %{notification_settings: %{privacy_option: true}} } = notification, - actor, + _actor, _object, mastodon_type ) do - %{title: format_title(notification, mastodon_type), body: "@#{actor.nickname}"} + %{body: format_title(notification, mastodon_type)} end def build_content(notification, actor, object, mastodon_type) do diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index b2664bf28..3de911810 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -209,8 +209,7 @@ test "returns info content for direct message with enabled privacy option" do object = Object.normalize(activity) assert Impl.build_content(notif, actor, object) == %{ - body: "@Bob", - title: "New Direct Message" + body: "New Direct Message" } end From 3d0c567fbc3506770fdac5f1269c45b244928747 Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 7 May 2020 08:14:54 +0000 Subject: [PATCH 097/337] Pleroma.Web.TwitterAPI.TwoFactorAuthenticationController -> Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController --- config/config.exs | 13 +- config/description.exs | 56 ++++ config/test.exs | 13 + docs/API/admin_api.md | 9 + docs/API/pleroma_api.md | 44 ++- docs/configuration/cheatsheet.md | 9 +- lib/pleroma/mfa.ex | 156 +++++++++ lib/pleroma/mfa/backup_codes.ex | 31 ++ lib/pleroma/mfa/changeset.ex | 64 ++++ lib/pleroma/mfa/settings.ex | 24 ++ lib/pleroma/mfa/token.ex | 106 ++++++ lib/pleroma/mfa/totp.ex | 86 +++++ .../plugs/ensure_authenticated_plug.ex | 14 + lib/pleroma/user.ex | 8 + .../web/admin_api/admin_api_controller.ex | 14 + lib/pleroma/web/auth/pleroma_authenticator.ex | 4 +- lib/pleroma/web/auth/totp_authenticator.ex | 45 +++ lib/pleroma/web/common_api/utils.ex | 1 + lib/pleroma/web/oauth/mfa_controller.ex | 97 ++++++ lib/pleroma/web/oauth/mfa_view.ex | 8 + lib/pleroma/web/oauth/oauth_controller.ex | 48 ++- lib/pleroma/web/oauth/token/clean_worker.ex | 38 +++ lib/pleroma/web/oauth/token/response.ex | 9 + .../two_factor_authentication_controller.ex | 133 ++++++++ lib/pleroma/web/router.ex | 15 + .../templates/o_auth/mfa/recovery.html.eex | 24 ++ .../web/templates/o_auth/mfa/totp.html.eex | 24 ++ .../remote_follow/follow_mfa.html.eex | 13 + .../controllers/remote_follow_controller.ex | 47 ++- mix.exs | 1 + mix.lock | 33 +- ...factor_authentication_settings_to_user.exs | 9 + .../20190508193213_create_mfa_tokens.exs | 16 + .../static/fonts/element-icons.535877f.woff | Bin 28200 -> 0 bytes .../static/fonts/element-icons.732389d.ttf | Bin 55956 -> 0 bytes test/mfa/backup_codes_test.exs | 11 + test/mfa/totp_test.exs | 17 + test/mfa_test.exs | 53 +++ test/plugs/ensure_authenticated_plug_test.exs | 25 ++ test/support/builders/user_builder.ex | 1 + test/support/factory.ex | 12 +- test/user_search_test.exs | 1 + .../admin_api/admin_api_controller_test.exs | 33 ++ test/web/auth/pleroma_authenticator_test.exs | 43 +++ test/web/auth/totp_authenticator_test.exs | 51 +++ test/web/oauth/mfa_controller_test.exs | 306 ++++++++++++++++++ test/web/oauth/oauth_controller_test.exs | 77 +++++ ..._factor_authentication_controller_test.exs | 260 +++++++++++++++ .../remote_follow_controller_test.exs | 116 +++++++ 49 files changed, 2184 insertions(+), 34 deletions(-) create mode 100644 lib/pleroma/mfa.ex create mode 100644 lib/pleroma/mfa/backup_codes.ex create mode 100644 lib/pleroma/mfa/changeset.ex create mode 100644 lib/pleroma/mfa/settings.ex create mode 100644 lib/pleroma/mfa/token.ex create mode 100644 lib/pleroma/mfa/totp.ex create mode 100644 lib/pleroma/web/auth/totp_authenticator.ex create mode 100644 lib/pleroma/web/oauth/mfa_controller.ex create mode 100644 lib/pleroma/web/oauth/mfa_view.ex create mode 100644 lib/pleroma/web/oauth/token/clean_worker.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex create mode 100644 lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex create mode 100644 lib/pleroma/web/templates/o_auth/mfa/totp.html.eex create mode 100644 lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex create mode 100644 priv/repo/migrations/20190506054542_add_multi_factor_authentication_settings_to_user.exs create mode 100644 priv/repo/migrations/20190508193213_create_mfa_tokens.exs delete mode 100644 priv/static/adminfe/static/fonts/element-icons.535877f.woff delete mode 100644 priv/static/adminfe/static/fonts/element-icons.732389d.ttf create mode 100644 test/mfa/backup_codes_test.exs create mode 100644 test/mfa/totp_test.exs create mode 100644 test/mfa_test.exs create mode 100644 test/web/auth/pleroma_authenticator_test.exs create mode 100644 test/web/auth/totp_authenticator_test.exs create mode 100644 test/web/oauth/mfa_controller_test.exs create mode 100644 test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs diff --git a/config/config.exs b/config/config.exs index ca9bbab64..e703c1632 100644 --- a/config/config.exs +++ b/config/config.exs @@ -238,7 +238,18 @@ account_field_value_length: 2048, external_user_synchronization: true, extended_nickname_format: true, - cleanup_attachments: false + cleanup_attachments: false, + multi_factor_authentication: [ + totp: [ + # digits 6 or 8 + digits: 6, + period: 30 + ], + backup_codes: [ + number: 5, + length: 16 + ] + ] config :pleroma, :extensions, output_relationships_in_statuses_by_default: true diff --git a/config/description.exs b/config/description.exs index 1b2afebef..39e094082 100644 --- a/config/description.exs +++ b/config/description.exs @@ -919,6 +919,62 @@ key: :external_user_synchronization, type: :boolean, description: "Enabling following/followers counters synchronization for external users" + }, + %{ + key: :multi_factor_authentication, + type: :keyword, + description: "Multi-factor authentication settings", + suggestions: [ + [ + totp: [digits: 6, period: 30], + backup_codes: [number: 5, length: 16] + ] + ], + children: [ + %{ + key: :totp, + type: :keyword, + description: "TOTP settings", + suggestions: [digits: 6, period: 30], + children: [ + %{ + key: :digits, + type: :integer, + suggestions: [6], + description: + "Determines the length of a one-time pass-code, in characters. Defaults to 6 characters." + }, + %{ + key: :period, + type: :integer, + suggestions: [30], + description: + "a period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds." + } + ] + }, + %{ + key: :backup_codes, + type: :keyword, + description: "MFA backup codes settings", + suggestions: [number: 5, length: 16], + children: [ + %{ + key: :number, + type: :integer, + suggestions: [5], + description: "number of backup codes to generate." + }, + %{ + key: :length, + type: :integer, + suggestions: [16], + description: + "Determines the length of backup one-time pass-codes, in characters. Defaults to 16 characters." + } + ] + } + ] } ] }, diff --git a/config/test.exs b/config/test.exs index cbf775109..e38b9967d 100644 --- a/config/test.exs +++ b/config/test.exs @@ -56,6 +56,19 @@ ignore_hosts: [], ignore_tld: ["local", "localdomain", "lan"] +config :pleroma, :instance, + multi_factor_authentication: [ + totp: [ + # digits 6 or 8 + digits: 6, + period: 30 + ], + backup_codes: [ + number: 2, + length: 6 + ] + ] + config :web_push_encryption, :vapid_details, subject: "mailto:administrator@example.com", public_key: diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 23af08961..c455047cc 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -409,6 +409,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret ### Get a password reset token for a given nickname + - Params: none - Response: @@ -427,6 +428,14 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - `nicknames` - Response: none (code `204`) +## PUT `/api/pleroma/admin/users/disable_mfa` + +### Disable mfa for user's account. + +- Params: + - `nickname` +- Response: User’s nickname + ## `GET /api/pleroma/admin/users/:nickname/credentials` ### Get the user's email, password, display and settings-related fields diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index b927be026..5895613a3 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -70,7 +70,49 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise * Example response: `{"error": "Invalid password."}` -## `/api/pleroma/admin/`… +## `/api/pleroma/accounts/mfa` +#### Gets current MFA settings +* method: `GET` +* Authentication: required +* OAuth scope: `read:security` +* Response: JSON. Returns `{"enabled": "false", "totp": false }` + +## `/api/pleroma/accounts/mfa/setup/totp` +#### Pre-setup the MFA/TOTP method +* method: `GET` +* Authentication: required +* OAuth scope: `write:security` +* Response: JSON. Returns `{"key": [secret_key], "provisioning_uri": "[qr code uri]" }` when successful, otherwise returns HTTP 422 `{"error": "error_msg"}` + +## `/api/pleroma/accounts/mfa/confirm/totp` +#### Confirms & enables MFA/TOTP support for user account. +* method: `POST` +* Authentication: required +* OAuth scope: `write:security` +* Params: + * `password`: user's password + * `code`: token from TOTP App +* Response: JSON. Returns `{}` if the enable was successful, HTTP 422 `{"error": "[error message]"}` otherwise + + +## `/api/pleroma/accounts/mfa/totp` +#### Disables MFA/TOTP method for user account. +* method: `DELETE` +* Authentication: required +* OAuth scope: `write:security` +* Params: + * `password`: user's password +* Response: JSON. Returns `{}` if the disable was successful, HTTP 422 `{"error": "[error message]"}` otherwise +* Example response: `{"error": "Invalid password."}` + +## `/api/pleroma/accounts/mfa/backup_codes` +#### Generstes backup codes MFA for user account. +* method: `GET` +* Authentication: required +* OAuth scope: `write:security` +* Response: JSON. Returns `{"codes": codes}`when successful, otherwise HTTP 422 `{"error": "[error message]"}` + +## `/api/pleroma/admin/` See [Admin-API](admin_api.md) ## `/api/v1/pleroma/notifications/read` diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 2524918d4..707d7fdbd 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -907,12 +907,18 @@ config :auto_linker, * `runtime_dir`: A path to custom Elixir modules (such as MRF policies). - ## :configurable_from_database Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information. +### Multi-factor authentication - :two_factor_authentication +* `totp` - a list containing TOTP configuration + - `digits` - Determines the length of a one-time pass-code in characters. Defaults to 6 characters. + - `period` - a period for which the TOTP code will be valid in seconds. Defaults to 30 seconds. +* `backup_codes` - a list containing backup codes configuration + - `number` - number of backup codes to generate. + - `length` - backup code length. Defaults to 16 characters. ## Restrict entities access for unauthenticated users @@ -930,6 +936,7 @@ Restrict access for unauthenticated users to timelines (public and federate), us * `local` * `remote` + ## Pleroma.Web.ApiSpec.CastAndValidate * `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`. diff --git a/lib/pleroma/mfa.ex b/lib/pleroma/mfa.ex new file mode 100644 index 000000000..d353a4dad --- /dev/null +++ b/lib/pleroma/mfa.ex @@ -0,0 +1,156 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA do + @moduledoc """ + The MFA context. + """ + + alias Comeonin.Pbkdf2 + alias Pleroma.User + + alias Pleroma.MFA.BackupCodes + alias Pleroma.MFA.Changeset + alias Pleroma.MFA.Settings + alias Pleroma.MFA.TOTP + + @doc """ + Returns MFA methods the user has enabled. + + ## Examples + + iex> Pleroma.MFA.supported_method(User) + "totp, u2f" + """ + @spec supported_methods(User.t()) :: String.t() + def supported_methods(user) do + settings = fetch_settings(user) + + Settings.mfa_methods() + |> Enum.reduce([], fn m, acc -> + if method_enabled?(m, settings) do + acc ++ [m] + else + acc + end + end) + |> Enum.join(",") + end + + @doc "Checks that user enabled MFA" + def require?(user) do + fetch_settings(user).enabled + end + + @doc """ + Display MFA settings of user + """ + def mfa_settings(user) do + settings = fetch_settings(user) + + Settings.mfa_methods() + |> Enum.map(fn m -> [m, method_enabled?(m, settings)] end) + |> Enum.into(%{enabled: settings.enabled}, fn [a, b] -> {a, b} end) + end + + @doc false + def fetch_settings(%User{} = user) do + user.multi_factor_authentication_settings || %Settings{} + end + + @doc "clears backup codes" + def invalidate_backup_code(%User{} = user, hash_code) do + %{backup_codes: codes} = fetch_settings(user) + + user + |> Changeset.cast_backup_codes(codes -- [hash_code]) + |> User.update_and_set_cache() + end + + @doc "generates backup codes" + @spec generate_backup_codes(User.t()) :: {:ok, list(binary)} | {:error, String.t()} + def generate_backup_codes(%User{} = user) do + with codes <- BackupCodes.generate(), + hashed_codes <- Enum.map(codes, &Pbkdf2.hashpwsalt/1), + changeset <- Changeset.cast_backup_codes(user, hashed_codes), + {:ok, _} <- User.update_and_set_cache(changeset) do + {:ok, codes} + else + {:error, msg} -> + %{error: msg} + end + end + + @doc """ + Generates secret key and set delivery_type to 'app' for TOTP method. + """ + @spec setup_totp(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + def setup_totp(user) do + user + |> Changeset.setup_totp(%{secret: TOTP.generate_secret(), delivery_type: "app"}) + |> User.update_and_set_cache() + end + + @doc """ + Confirms the TOTP method for user. + + `attrs`: + `password` - current user password + `code` - TOTP token + """ + @spec confirm_totp(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t() | atom()} + def confirm_totp(%User{} = user, attrs) do + with settings <- user.multi_factor_authentication_settings.totp, + {:ok, :pass} <- TOTP.validate_token(settings.secret, attrs["code"]) do + user + |> Changeset.confirm_totp() + |> User.update_and_set_cache() + end + end + + @doc """ + Disables the TOTP method for user. + + `attrs`: + `password` - current user password + """ + @spec disable_totp(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + def disable_totp(%User{} = user) do + user + |> Changeset.disable_totp() + |> Changeset.disable() + |> User.update_and_set_cache() + end + + @doc """ + Force disables all MFA methods for user. + """ + @spec disable(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + def disable(%User{} = user) do + user + |> Changeset.disable_totp() + |> Changeset.disable(true) + |> User.update_and_set_cache() + end + + @doc """ + Checks if the user has MFA method enabled. + """ + def method_enabled?(method, settings) do + with {:ok, %{confirmed: true} = _} <- Map.fetch(settings, method) do + true + else + _ -> false + end + end + + @doc """ + Checks if the user has enabled at least one MFA method. + """ + def enabled?(settings) do + Settings.mfa_methods() + |> Enum.map(fn m -> method_enabled?(m, settings) end) + |> Enum.any?() + end +end diff --git a/lib/pleroma/mfa/backup_codes.ex b/lib/pleroma/mfa/backup_codes.ex new file mode 100644 index 000000000..2b5ec34f8 --- /dev/null +++ b/lib/pleroma/mfa/backup_codes.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA.BackupCodes do + @moduledoc """ + This module contains functions for generating backup codes. + """ + alias Pleroma.Config + + @config_ns [:instance, :multi_factor_authentication, :backup_codes] + + @doc """ + Generates backup codes. + """ + @spec generate(Keyword.t()) :: list(String.t()) + def generate(opts \\ []) do + number_of_codes = Keyword.get(opts, :number_of_codes, default_backup_codes_number()) + code_length = Keyword.get(opts, :length, default_backup_codes_code_length()) + + Enum.map(1..number_of_codes, fn _ -> + :crypto.strong_rand_bytes(div(code_length, 2)) + |> Base.encode16(case: :lower) + end) + end + + defp default_backup_codes_number, do: Config.get(@config_ns ++ [:number], 5) + + defp default_backup_codes_code_length, + do: Config.get(@config_ns ++ [:length], 16) +end diff --git a/lib/pleroma/mfa/changeset.ex b/lib/pleroma/mfa/changeset.ex new file mode 100644 index 000000000..9b020aa8e --- /dev/null +++ b/lib/pleroma/mfa/changeset.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA.Changeset do + alias Pleroma.MFA + alias Pleroma.MFA.Settings + alias Pleroma.User + + def disable(%Ecto.Changeset{} = changeset, force \\ false) do + settings = + changeset + |> Ecto.Changeset.apply_changes() + |> MFA.fetch_settings() + + if force || not MFA.enabled?(settings) do + put_change(changeset, %Settings{settings | enabled: false}) + else + changeset + end + end + + def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do + user + |> put_change(%Settings{settings | totp: %Settings.TOTP{}}) + end + + def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do + totp_settings = %Settings.TOTP{settings.totp | confirmed: true} + + user + |> put_change(%Settings{settings | totp: totp_settings, enabled: true}) + end + + def setup_totp(%User{} = user, attrs) do + mfa_settings = MFA.fetch_settings(user) + + totp_settings = + %Settings.TOTP{} + |> Ecto.Changeset.cast(attrs, [:secret, :delivery_type]) + + user + |> put_change(%Settings{mfa_settings | totp: Ecto.Changeset.apply_changes(totp_settings)}) + end + + def cast_backup_codes(%User{} = user, codes) do + user + |> put_change(%Settings{ + user.multi_factor_authentication_settings + | backup_codes: codes + }) + end + + defp put_change(%User{} = user, settings) do + user + |> Ecto.Changeset.change() + |> put_change(settings) + end + + defp put_change(%Ecto.Changeset{} = changeset, settings) do + changeset + |> Ecto.Changeset.put_change(:multi_factor_authentication_settings, settings) + end +end diff --git a/lib/pleroma/mfa/settings.ex b/lib/pleroma/mfa/settings.ex new file mode 100644 index 000000000..2764b889c --- /dev/null +++ b/lib/pleroma/mfa/settings.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA.Settings do + use Ecto.Schema + + @primary_key false + + @mfa_methods [:totp] + embedded_schema do + field(:enabled, :boolean, default: false) + field(:backup_codes, {:array, :string}, default: []) + + embeds_one :totp, TOTP, on_replace: :delete, primary_key: false do + field(:secret, :string) + # app | sms + field(:delivery_type, :string, default: "app") + field(:confirmed, :boolean, default: false) + end + end + + def mfa_methods, do: @mfa_methods +end diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex new file mode 100644 index 000000000..25ff7fb29 --- /dev/null +++ b/lib/pleroma/mfa/token.ex @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA.Token do + use Ecto.Schema + import Ecto.Query + import Ecto.Changeset + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token, as: OAuthToken + + @expires 300 + + schema "mfa_tokens" do + field(:token, :string) + field(:valid_until, :naive_datetime_usec) + + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:authorization, Authorization) + + timestamps() + end + + def get_by_token(token) do + from( + t in __MODULE__, + where: t.token == ^token, + preload: [:user, :authorization] + ) + |> Repo.find_resource() + end + + def validate(token) do + with {:fetch_token, {:ok, token}} <- {:fetch_token, get_by_token(token)}, + {:expired, false} <- {:expired, is_expired?(token)} do + {:ok, token} + else + {:expired, _} -> {:error, :expired_token} + {:fetch_token, _} -> {:error, :not_found} + error -> {:error, error} + end + end + + def create_token(%User{} = user) do + %__MODULE__{} + |> change + |> assign_user(user) + |> put_token + |> put_valid_until + |> Repo.insert() + end + + def create_token(user, authorization) do + %__MODULE__{} + |> change + |> assign_user(user) + |> assign_authorization(authorization) + |> put_token + |> put_valid_until + |> Repo.insert() + end + + defp assign_user(changeset, user) do + changeset + |> put_assoc(:user, user) + |> validate_required([:user]) + end + + defp assign_authorization(changeset, authorization) do + changeset + |> put_assoc(:authorization, authorization) + |> validate_required([:authorization]) + end + + defp put_token(changeset) do + changeset + |> change(%{token: OAuthToken.Utils.generate_token()}) + |> validate_required([:token]) + |> unique_constraint(:token) + end + + defp put_valid_until(changeset) do + expires_in = NaiveDateTime.add(NaiveDateTime.utc_now(), @expires) + + changeset + |> change(%{valid_until: expires_in}) + |> validate_required([:valid_until]) + end + + def is_expired?(%__MODULE__{valid_until: valid_until}) do + NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 + end + + def is_expired?(_), do: false + + def delete_expired_tokens do + from( + q in __MODULE__, + where: fragment("?", q.valid_until) < ^Timex.now() + ) + |> Repo.delete_all() + end +end diff --git a/lib/pleroma/mfa/totp.ex b/lib/pleroma/mfa/totp.ex new file mode 100644 index 000000000..1407afc57 --- /dev/null +++ b/lib/pleroma/mfa/totp.ex @@ -0,0 +1,86 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFA.TOTP do + @moduledoc """ + This module represents functions to create secrets for + TOTP Application as well as validate them with a time based token. + """ + alias Pleroma.Config + + @config_ns [:instance, :multi_factor_authentication, :totp] + + @doc """ + https://github.com/google/google-authenticator/wiki/Key-Uri-Format + """ + def provisioning_uri(secret, label, opts \\ []) do + query = + %{ + secret: secret, + issuer: Keyword.get(opts, :issuer, default_issuer()), + digits: Keyword.get(opts, :digits, default_digits()), + period: Keyword.get(opts, :period, default_period()) + } + |> Enum.filter(fn {_, v} -> not is_nil(v) end) + |> Enum.into(%{}) + |> URI.encode_query() + + %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query} + |> URI.to_string() + end + + defp default_period, do: Config.get(@config_ns ++ [:period]) + defp default_digits, do: Config.get(@config_ns ++ [:digits]) + + defp default_issuer, + do: Config.get(@config_ns ++ [:issuer], Config.get([:instance, :name])) + + @doc "Creates a random Base 32 encoded string" + def generate_secret do + Base.encode32(:crypto.strong_rand_bytes(10)) + end + + @doc "Generates a valid token based on a secret" + def generate_token(secret) do + :pot.totp(secret) + end + + @doc """ + Validates a given token based on a secret. + + optional parameters: + `token_length` default `6` + `interval_length` default `30` + `window` default 0 + + Returns {:ok, :pass} if the token is valid and + {:error, :invalid_token} if it is not. + """ + @spec validate_token(String.t(), String.t()) :: + {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token} + def validate_token(secret, token) + when is_binary(secret) and is_binary(token) do + opts = [ + token_length: default_digits(), + interval_length: default_period() + ] + + validate_token(secret, token, opts) + end + + def validate_token(_, _), do: {:error, :invalid_secret_and_token} + + @doc "See `validate_token/2`" + @spec validate_token(String.t(), String.t(), Keyword.t()) :: + {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token} + def validate_token(secret, token, options) + when is_binary(secret) and is_binary(token) do + case :pot.valid_totp(token, secret, options) do + true -> {:ok, :pass} + false -> {:error, :invalid_token} + end + end + + def validate_token(_, _, _), do: {:error, :invalid_secret_and_token} +end diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 9d5176e2b..3fe550806 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -15,6 +15,20 @@ def init(options) do end @impl true + def perform( + %{ + assigns: %{ + auth_credentials: %{password: _}, + user: %User{multi_factor_authentication_settings: %{enabled: true}} + } + } = conn, + _ + ) do + conn + |> render_error(:forbidden, "Two-factor authentication enabled, you must use a access token.") + |> halt() + end + def perform(%{assigns: %{user: %User{}}} = conn, _) do conn end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 323eb2a41..a6f51f0be 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -20,6 +20,7 @@ defmodule Pleroma.User do alias Pleroma.Formatter alias Pleroma.HTML alias Pleroma.Keys + alias Pleroma.MFA alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Registration @@ -190,6 +191,12 @@ defmodule Pleroma.User do # `:subscribers` is deprecated (replaced with `subscriber_users` relation) field(:subscribers, {:array, :string}, default: []) + embeds_one( + :multi_factor_authentication_settings, + MFA.Settings, + on_replace: :delete + ) + timestamps() end @@ -927,6 +934,7 @@ def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do end end + @spec get_by_nickname(String.t()) :: User.t() | nil def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) || if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 80a4ebaac..9f1fd3aeb 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.ConfigDB + alias Pleroma.MFA alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.ReportNote @@ -61,6 +62,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do :right_add, :right_add_multiple, :right_delete, + :disable_mfa, :right_delete_multiple, :update_user_credentials ] @@ -674,6 +676,18 @@ def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nic json_response(conn, :no_content, "") end + @doc "Disable mfa for user's account." + def disable_mfa(conn, %{"nickname" => nickname}) do + case User.get_by_nickname(nickname) do + %User{} = user -> + MFA.disable(user) + json(conn, nickname) + + _ -> + {:error, :not_found} + end + end + @doc "Show a given user's credentials" def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index cb09664ce..a8f554aa3 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -19,8 +19,8 @@ def get_user(%Plug.Conn{} = conn) do {_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)} do {:ok, user} else - error -> - {:error, error} + {:error, _reason} = error -> error + error -> {:error, error} end end diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex new file mode 100644 index 000000000..98aca9a51 --- /dev/null +++ b/lib/pleroma/web/auth/totp_authenticator.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.TOTPAuthenticator do + alias Comeonin.Pbkdf2 + alias Pleroma.MFA + alias Pleroma.MFA.TOTP + alias Pleroma.User + + @doc "Verify code or check backup code." + @spec verify(String.t(), User.t()) :: + {:ok, :pass} | {:error, :invalid_token | :invalid_secret_and_token} + def verify( + token, + %User{ + multi_factor_authentication_settings: + %{enabled: true, totp: %{secret: secret, confirmed: true}} = _ + } = _user + ) + when is_binary(token) and byte_size(token) > 0 do + TOTP.validate_token(secret, token) + end + + def verify(_, _), do: {:error, :invalid_token} + + @spec verify_recovery_code(User.t(), String.t()) :: + {:ok, :pass} | {:error, :invalid_token} + def verify_recovery_code( + %User{multi_factor_authentication_settings: %{enabled: true, backup_codes: codes}} = user, + code + ) + when is_list(codes) and is_binary(code) do + hash_code = Enum.find(codes, fn hash -> Pbkdf2.checkpw(code, hash) end) + + if hash_code do + MFA.invalidate_backup_code(user, hash_code) + {:ok, :pass} + else + {:error, :invalid_token} + end + end + + def verify_recovery_code(_, _), do: {:error, :invalid_token} +end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 6540fa5d1..793f2e7f8 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -402,6 +402,7 @@ defp shortname(name) do end end + @spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()} def confirm_current_password(user, password) do with %User{local: true} = db_user <- User.get_cached_by_id(user.id), true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/oauth/mfa_controller.ex new file mode 100644 index 000000000..e52cccd85 --- /dev/null +++ b/lib/pleroma/web/oauth/mfa_controller.ex @@ -0,0 +1,97 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAController do + @moduledoc """ + The model represents api to use Multi Factor authentications. + """ + + use Pleroma.Web, :controller + + alias Pleroma.MFA + alias Pleroma.Web.Auth.TOTPAuthenticator + alias Pleroma.Web.OAuth.MFAView, as: View + alias Pleroma.Web.OAuth.OAuthController + alias Pleroma.Web.OAuth.Token + + plug(:fetch_session when action in [:show, :verify]) + plug(:fetch_flash when action in [:show, :verify]) + + @doc """ + Display form to input mfa code or recovery code. + """ + def show(conn, %{"mfa_token" => mfa_token} = params) do + template = Map.get(params, "challenge_type", "totp") + + conn + |> put_view(View) + |> render("#{template}.html", %{ + mfa_token: mfa_token, + redirect_uri: params["redirect_uri"], + state: params["state"] + }) + end + + @doc """ + Verification code and continue authorization. + """ + def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do + with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), + {:ok, _} <- validates_challenge(user, mfa_params) do + conn + |> OAuthController.after_create_authorization(auth, %{ + "authorization" => %{ + "redirect_uri" => mfa_params["redirect_uri"], + "state" => mfa_params["state"] + } + }) + else + _ -> + conn + |> put_flash(:error, "Two-factor authentication failed.") + |> put_status(:unauthorized) + |> show(mfa_params) + end + end + + @doc """ + Verification second step of MFA (or recovery) and returns access token. + + ## Endpoint + POST /oauth/mfa/challenge + + params: + `client_id` + `client_secret` + `mfa_token` - access token to check second step of mfa + `challenge_type` - 'totp' or 'recovery' + `code` + + """ + def challenge(conn, %{"mfa_token" => mfa_token} = params) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), + {:ok, _} <- validates_challenge(user, params), + {:ok, token} <- Token.exchange_token(app, auth) do + json(conn, Token.Response.build(user, token)) + else + _error -> + conn + |> put_status(400) + |> json(%{error: "Invalid code"}) + end + end + + # Verify TOTP Code + defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do + TOTPAuthenticator.verify(code, user) + end + + # Verify Recovery Code + defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do + TOTPAuthenticator.verify_recovery_code(user, code) + end + + defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type} +end diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/oauth/mfa_view.ex new file mode 100644 index 000000000..e88e7066b --- /dev/null +++ b/lib/pleroma/web/oauth/mfa_view.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAView do + use Pleroma.Web, :view + import Phoenix.HTML.Form +end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 685269877..7c804233c 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do use Pleroma.Web, :controller alias Pleroma.Helpers.UriHelper + alias Pleroma.MFA alias Pleroma.Plugs.RateLimiter alias Pleroma.Registration alias Pleroma.Repo @@ -14,6 +15,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do alias Pleroma.Web.ControllerHelper alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.MFAController alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken @@ -121,7 +123,8 @@ def create_authorization( %{"authorization" => _} = params, opts \\ [] ) do - with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do + with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do after_create_authorization(conn, auth, params) else error -> @@ -179,6 +182,22 @@ defp handle_create_authorization_error( |> authorize(params) end + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:mfa_required, user, auth, _}, + params + ) do + {:ok, token} = MFA.Token.create_token(user, auth) + + data = %{ + "mfa_token" => token.token, + "redirect_uri" => params["authorization"]["redirect_uri"], + "state" => params["authorization"]["state"] + } + + MFAController.show(conn, data) + end + defp handle_create_authorization_error( %Plug.Conn{} = conn, {:account_status, :password_reset_pending}, @@ -231,7 +250,8 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} json(conn, Token.Response.build(user, token, response_attrs)) else - _error -> render_invalid_credentials_error(conn) + error -> + handle_token_exchange_error(conn, error) end end @@ -244,6 +264,7 @@ def token_exchange( {:account_status, :active} <- {:account_status, User.account_status(user)}, {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build(user, token)) else @@ -270,13 +291,20 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build_for_client_credentials(token)) else - _error -> render_invalid_credentials_error(conn) + _error -> + handle_token_exchange_error(conn, :invalid_credentails) end end # Bad request def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do + conn + |> put_status(:forbidden) + |> json(build_and_response_mfa_token(user, auth)) + end + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do render_error( conn, @@ -434,7 +462,8 @@ def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), %Registration{} = registration <- Repo.get(Registration, registration_id), - {_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)}, + {_, {:ok, auth, _user}} <- + {:create_authorization, do_create_authorization(conn, params)}, %User{} = user <- Repo.preload(auth, :user).user, {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do conn @@ -500,8 +529,9 @@ defp do_create_authorization( %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), {:ok, scopes} <- validate_scopes(app, auth_attrs), - {:account_status, :active} <- {:account_status, User.account_status(user)} do - Authorization.create_authorization(app, user, scopes) + {:account_status, :active} <- {:account_status, User.account_status(user)}, + {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + {:ok, auth, user} end end @@ -515,6 +545,12 @@ defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :re defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) + defp build_and_response_mfa_token(user, auth) do + with {:ok, token} <- MFA.Token.create_token(user, auth) do + Token.Response.build_for_mfa_token(user, token) + end + end + @spec validate_scopes(App.t(), map()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} defp validate_scopes(%App{} = app, params) do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex new file mode 100644 index 000000000..2c3bb9ded --- /dev/null +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.CleanWorker do + @moduledoc """ + The module represents functions to clean an expired OAuth and MFA tokens. + """ + use GenServer + + @ten_seconds 10_000 + @one_day 86_400_000 + + alias Pleroma.MFA + alias Pleroma.Web.OAuth + alias Pleroma.Workers.BackgroundWorker + + def start_link(_), do: GenServer.start_link(__MODULE__, %{}) + + def init(_) do + Process.send_after(self(), :perform, @ten_seconds) + {:ok, nil} + end + + @doc false + def handle_info(:perform, state) do + BackgroundWorker.enqueue("clean_expired_tokens", %{}) + interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) + + Process.send_after(self(), :perform, interval) + {:noreply, state} + end + + def perform(:clean) do + OAuth.Token.delete_expired_tokens() + MFA.Token.delete_expired_tokens() + end +end diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex index 6f4713dee..0e72c31e9 100644 --- a/lib/pleroma/web/oauth/token/response.ex +++ b/lib/pleroma/web/oauth/token/response.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.Token.Response do @moduledoc false + alias Pleroma.MFA alias Pleroma.User alias Pleroma.Web.OAuth.Token.Utils @@ -32,5 +33,13 @@ def build_for_client_credentials(token) do } end + def build_for_mfa_token(user, mfa_token) do + %{ + error: "mfa_required", + mfa_token: mfa_token.token, + supported_challenge_types: MFA.supported_methods(user) + } + end + defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) end diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex new file mode 100644 index 000000000..eb9989cdf --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex @@ -0,0 +1,133 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do + @moduledoc "The module represents actions to manage MFA" + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.MFA + alias Pleroma.MFA.TOTP + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.CommonAPI.Utils + + plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:security"]} when action in [:setup, :confirm, :disable, :backup_codes] + ) + + @doc """ + Gets user multi factor authentication settings + + ## Endpoint + GET /api/pleroma/accounts/mfa + + """ + def settings(%{assigns: %{user: user}} = conn, _params) do + json(conn, %{settings: MFA.mfa_settings(user)}) + end + + @doc """ + Prepare setup mfa method + + ## Endpoint + GET /api/pleroma/accounts/mfa/setup/[:method] + + """ + def setup(%{assigns: %{user: user}} = conn, %{"method" => "totp"} = _params) do + with {:ok, user} <- MFA.setup_totp(user), + %{secret: secret} = _ <- user.multi_factor_authentication_settings.totp do + provisioning_uri = TOTP.provisioning_uri(secret, "#{user.email}") + + json(conn, %{provisioning_uri: provisioning_uri, key: secret}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def setup(conn, _params) do + json_response(conn, :bad_request, %{error: "undefined method"}) + end + + @doc """ + Confirms setup and enable mfa method + + ## Endpoint + POST /api/pleroma/accounts/mfa/confirm/:method + + - params: + `code` - confirmation code + `password` - current password + """ + def confirm( + %{assigns: %{user: user}} = conn, + %{"method" => "totp", "password" => _, "code" => _} = params + ) do + with {:ok, _user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.confirm_totp(user, params) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def confirm(conn, _) do + json_response(conn, :bad_request, %{error: "undefined mfa method"}) + end + + @doc """ + Disable mfa method and disable mfa if need. + """ + def disable(%{assigns: %{user: user}} = conn, %{"method" => "totp"} = params) do + with {:ok, user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.disable_totp(user) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def disable(%{assigns: %{user: user}} = conn, %{"method" => "mfa"} = params) do + with {:ok, user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.disable(user) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def disable(conn, _) do + json_response(conn, :bad_request, %{error: "undefined mfa method"}) + end + + @doc """ + Generates backup codes. + + ## Endpoint + GET /api/pleroma/accounts/mfa/backup_codes + + ## Response + ### Success + `{codes: [codes]}` + + ### Error + `{error: [error_message]}` + + """ + def backup_codes(%{assigns: %{user: user}} = conn, _params) do + with {:ok, codes} <- MFA.generate_backup_codes(user) do + json(conn, %{codes: codes}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 281516bb8..7a171f9fb 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -132,6 +132,7 @@ defmodule Pleroma.Web.Router do post("/users/follow", AdminAPIController, :user_follow) post("/users/unfollow", AdminAPIController, :user_unfollow) + put("/users/disable_mfa", AdminAPIController, :disable_mfa) delete("/users", AdminAPIController, :user_delete) post("/users", AdminAPIController, :users_create) patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) @@ -258,6 +259,16 @@ defmodule Pleroma.Web.Router do post("/follow_import", UtilController, :follow_import) end + scope "/api/pleroma", Pleroma.Web.PleromaAPI do + pipe_through(:authenticated_api) + + get("/accounts/mfa", TwoFactorAuthenticationController, :settings) + get("/accounts/mfa/backup_codes", TwoFactorAuthenticationController, :backup_codes) + get("/accounts/mfa/setup/:method", TwoFactorAuthenticationController, :setup) + post("/accounts/mfa/confirm/:method", TwoFactorAuthenticationController, :confirm) + delete("/accounts/mfa/:method", TwoFactorAuthenticationController, :disable) + end + scope "/oauth", Pleroma.Web.OAuth do scope [] do pipe_through(:oauth) @@ -269,6 +280,10 @@ defmodule Pleroma.Web.Router do post("/revoke", OAuthController, :token_revoke) get("/registration_details", OAuthController, :registration_details) + post("/mfa/challenge", MFAController, :challenge) + post("/mfa/verify", MFAController, :verify, as: :mfa_verify) + get("/mfa", MFAController, :show) + scope [] do pipe_through(:browser) diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex new file mode 100644 index 000000000..750f65386 --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -0,0 +1,24 @@ +<%= if get_flash(@conn, :info) do %> + +<% end %> +<%= if get_flash(@conn, :error) do %> + +<% end %> + +

Two-factor recovery

+ +<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> +
+ <%= label f, :code, "Recovery code" %> + <%= text_input f, :code %> + <%= hidden_input f, :mfa_token, value: @mfa_token %> + <%= hidden_input f, :state, value: @state %> + <%= hidden_input f, :redirect_uri, value: @redirect_uri %> + <%= hidden_input f, :challenge_type, value: "recovery" %> +
+ +<%= submit "Verify" %> +<% end %> +"> + Enter a two-factor code + diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex new file mode 100644 index 000000000..af6e546b0 --- /dev/null +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -0,0 +1,24 @@ +<%= if get_flash(@conn, :info) do %> + +<% end %> +<%= if get_flash(@conn, :error) do %> + +<% end %> + +

Two-factor authentication

+ +<%= form_for @conn, mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> +
+ <%= label f, :code, "Authentication code" %> + <%= text_input f, :code %> + <%= hidden_input f, :mfa_token, value: @mfa_token %> + <%= hidden_input f, :state, value: @state %> + <%= hidden_input f, :redirect_uri, value: @redirect_uri %> + <%= hidden_input f, :challenge_type, value: "totp" %> +
+ +<%= submit "Verify" %> +<% end %> +"> + Enter a two-factor recovery code + diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex new file mode 100644 index 000000000..adc3a3e3d --- /dev/null +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex @@ -0,0 +1,13 @@ +<%= if @error do %> +

<%= @error %>

+<% end %> +

Two-factor authentication

+

<%= @followee.nickname %>

+ +<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> +<%= text_input f, :code, placeholder: "Authentication code", required: true %> +
+<%= hidden_input f, :id, value: @followee.id %> +<%= hidden_input f, :token, value: @mfa_token %> +<%= submit "Authorize" %> +<% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 89da760da..521dc9322 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -8,10 +8,12 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do require Logger alias Pleroma.Activity + alias Pleroma.MFA alias Pleroma.Object.Fetcher alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.Auth.Authenticator + alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.CommonAPI @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] @@ -68,6 +70,8 @@ defp is_status?(acct) do # POST /ostatus_subscribe # + # adds a remote account in followers if user already is signed in. + # def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, {:ok, _, _, _} <- CommonAPI.follow(user, followee) do @@ -78,9 +82,33 @@ def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => end end + # POST /ostatus_subscribe + # + # step 1. + # checks login\password and displays step 2 form of MFA if need. + # def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do - with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + with {_, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee}, + {_, _, _, false} <- {:mfa_required, followee, user, MFA.require?(user)}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + redirect(conn, to: "/users/#{followee.id}") + else + error -> + handle_follow_error(conn, error) + end + end + + # POST /ostatus_subscribe + # + # step 2 + # checks TOTP code. otherwise displays form with errors + # + def do_follow(conn, %{"mfa" => %{"code" => code, "token" => token, "id" => id}}) do + with {_, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {_, _, {:ok, %{user: user}}} <- {:mfa_token, followee, MFA.Token.validate(token)}, + {_, _, _, {:ok, _}} <- + {:verify_mfa_code, followee, token, TOTPAuthenticator.verify(code, user)}, {:ok, _, _, _} <- CommonAPI.follow(user, followee) do redirect(conn, to: "/users/#{followee.id}") else @@ -94,6 +122,23 @@ def do_follow(%{assigns: %{user: nil}} = conn, _) do render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."}) end + defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do + render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee}) + end + + defp handle_follow_error(conn, {:verify_mfa_code, followee, token, _} = _) do + render(conn, "follow_mfa.html", %{ + error: "Wrong authentication code", + followee: followee, + mfa_token: token + }) + end + + defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do + {:ok, %{token: token}} = MFA.Token.create_token(user) + render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false}) + end + defp handle_follow_error(conn, {:auth, _, followee} = _) do render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee}) end diff --git a/mix.exs b/mix.exs index beb05aab9..6d65e18d4 100644 --- a/mix.exs +++ b/mix.exs @@ -176,6 +176,7 @@ defp deps do {:quack, "~> 0.1.1"}, {:joken, "~> 2.0"}, {:benchee, "~> 1.0"}, + {:pot, "~> 0.10.2"}, {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:ex_const, "~> 0.2"}, {:plug_static_index_html, "~> 1.0.0"}, diff --git a/mix.lock b/mix.lock index 28287cf97..4792249d7 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,7 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, + "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, @@ -19,38 +18,33 @@ "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, - "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "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.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": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, "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"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, - "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, - "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, + "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, + "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, - "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, "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"}, @@ -59,37 +53,34 @@ "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"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "e6d886252f1a41f4ba06ecf2b4c8d38760b34b1c08a11c28f7397b2e03995964"}, "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, - "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, "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": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]}, "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"}, + "phoenix": {:hex, :phoenix, "1.4.12", "b86fa85a2ba336f5de068549de5ccceec356fd413264a9637e7733395d6cc4ea", [: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", "58331ade6d77e1312a3d976f0fa41803b8f004b2b5f489193425bc46aea3ed30"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, - "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "164baaeb382d19beee0ec484492aa82a9c8685770aee33b24ec727a0971b34d0"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.1", "a196e4f428d7f5d6dba5ded314cc55cd0fbddf1110af620f75c0190e77844b33", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "15a3c34ffaccef8a0b575b8d39ab1b9044586d7dab917292cdc44cf2737df7f2"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, - "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, + "pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"}, + "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, diff --git a/priv/repo/migrations/20190506054542_add_multi_factor_authentication_settings_to_user.exs b/priv/repo/migrations/20190506054542_add_multi_factor_authentication_settings_to_user.exs new file mode 100644 index 000000000..8b653c61f --- /dev/null +++ b/priv/repo/migrations/20190506054542_add_multi_factor_authentication_settings_to_user.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddMultiFactorAuthenticationSettingsToUser do + use Ecto.Migration + + def change do + alter table(:users) do + add(:multi_factor_authentication_settings, :map, default: %{}) + end + end +end diff --git a/priv/repo/migrations/20190508193213_create_mfa_tokens.exs b/priv/repo/migrations/20190508193213_create_mfa_tokens.exs new file mode 100644 index 000000000..da9f8fabe --- /dev/null +++ b/priv/repo/migrations/20190508193213_create_mfa_tokens.exs @@ -0,0 +1,16 @@ +defmodule Pleroma.Repo.Migrations.CreateMfaTokens do + use Ecto.Migration + + def change do + create table(:mfa_tokens) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + add(:authorization_id, references(:oauth_authorizations, on_delete: :delete_all)) + add(:token, :string) + add(:valid_until, :naive_datetime_usec) + + timestamps() + end + + create(unique_index(:mfa_tokens, :token)) + end +end diff --git a/priv/static/adminfe/static/fonts/element-icons.535877f.woff b/priv/static/adminfe/static/fonts/element-icons.535877f.woff deleted file mode 100644 index 02b9a2539e425a7a8c244faba92527602be76212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28200 zcmY(IQ*>obw1wkzY}@FV9ou%twr$%sPI!WjZQD-Aw(aEhfA7;>W6ZI?HTS9-^|Y(@ zDt85OaS%|De_=-r0{7o@E#?36|M>rhgo>Il2ngtpe=h4k3DwAvi$RS|K+0p zq|S#a)oE;N=LIoGXa4i@|K$Y#L=L?Lk!4});_=Vx{g-2afIyi18w6rwZ~R}~Ul0&5 zw*TZCR$*Xk=<#nZQ}}=$p z3j%@wci;?(=aFMWSW#Pr zM-sk`m0uq8xYb`Cmsw(7J!k}vp6qi1VS~jP7&6A5mE-EG{5)pI7l~c<3JjAJf7Ao{ z%?06O$C!E2hN3FRmRCu5Ow%tiyBh2ns`-x@zc75e`(i)8rv=+je8;kh-i@>exF|8Zoy0d%E ze^yR-Rn9=!jEdV-)~sl5yJK;fvbNWAZT=0qvKdpinc}dSaI={~ycm_gm}Gd^0er~R z)M9-DIXmj{IvSw8>#@8WklyP7dhek4qeA$TB>3Zo_|qu(V@mi_%=j`yUn2T(`yTQ; zqHm61jJ91ll zVp~T9dV8jNyJ~v-x_ZZaTgTx12W0$5X#9t){Ks&gcFBydQ8lk&_OJ3ir{DE4*RO$x zAEV#D`xGh<%>8yX{Px|y4AcrzlvS)!*GlBewa-%DN&>&QaZ`s&q%5_vQjbc+EH$-K z*`?3tfsOQ+56~*ljNeyZ-{0)jU)J+W*Qx@kC-zG!MBuUtn9Q>kDG@^I6k_nrv_eJ^ zr!eGUS$Sec8K>psVcB^KMj>irsAO_8bj;%w8dybgGtA7x529$sax)yv(+??V`*Jfp z%zY0BXlQacg0d_npv(DUULP)9)=1cYE2euqQ_K9?BF>tg?x+Ykm43d!xh;gizD4>E3L9epi+%` zorRj_F_Y3X_zqm8;Ac8yye*)KjEtAfl=ZQZHs3>2kw*h$p=Q5Krfd!#1JS9vnGU&7 zfF@M)DYt{^z(%TWmP7vArgG2-ds$sUA8RYfJsuSSWEnX*Av#u9sN1e`z6c^&K4Cge zcG$Z9MfyPnU>b>f)?3)i>LTwTBM_0)kG%=yHoH7MVp|SD?8ESk)+n{SX%tt*Ke0(x zPJEfe6<2d)(auCyWhU`aHdbPp)0JqocQMFBM1?3RR(48~gTs}4b#O7wL`B6uX-XDB znuF7oX0?B@!bC=;C#gzSPNtc|m0@*wvD-vj1u6MbHdeNo)0K6#Z*l%03)oG$!otqk z)?7X?<|=C|@5c7Xy*jpdbI=Z~rdnZb;&1CN{~kk-l?m9GZG;((l|2vG_}hp!DkmEq zcww`KXo9s6Ma;>53FE}NP*2>E#R1jUW@($SDccde(cdUIdIh8xwzJftI8X=r3ftLg z(H?LBr-bdSwHOcFfCM6T_F8NQh(KGBD+?{I19@PV$d!#2{{aIKLG+2Kiln~O5-YJw z7B2F_c1`-A8n`5SWwxepa0R3gyE0r;J;(#Pid~tmX&!6=hs3Uo*Ypm$ff(Xf=4-|W zUqDlFLW4DngFs-DIHAd!?Li+9Qi9NE&FP>3s3t*Zw&s4Y3oMZ!G+gsJNCZAg5Sp$9 z9?Sv7Bngez!VcT#(v7JXLXJ@ z){p&>^Um%ZZtNVhmcz^GoNgQ*JD2Oo>FjHq9mA8;&g~p)TpP=ld(Z70YCIUzmdnoT zoNBxpTbJVjbPhDWjPb~=13D*MAQRlPuR!kz7fc1WtlLpW-(^?8JbO;4hVkq?4_ z+Ce^}E@}x#*;jCPC<`S*{8sH;qa(5_(66nhh$H|eU2+ru1zTZ$%Xa0_QrTx%u3ne6 z1aLV{c&<^Gsf1uTcLc6Mm)!(~Y&!y{vePq?@XRYBr`A(cQi%*ZQm5imZc>TND{`mC zQzOz5?6XIF>!kv~e&tCnzJ1Br2aI~fG{E8?Nc{u<;fe`bD(!M^|ESc8`%~*cP6rd8;|6V2x@2uxge@=KyBC$ zi|`bR5K!rXMgz%-+SGYrU?UxdcEKn0MB{=ow_;1K8@Ik?DUSk+>#z`~?)SGAcL>qe+6j#(>P6M{$6J#k$uU#{R z-s{lpIh;j{n>TN#5M7|;A&LN1S5Gb5Z@ugDY*&{Z*Za%xtn`OuARbR}-%5Hcw^6D#e_2aE~PSe-7MjKb4wJ!33 z(UO00wX^f|aZYJ;{}Bj8$PK4MAY^41k{Y=@QXe(UE}6SS=V6B{B+j9W3ZjN_70#Z8 ze2jW9VXfX0r9)+Z(b4nQx^_;mAkB;{k(_6jbV@{qX~iG>E99ftViOjF*0<}%b3h6d zC08EgJC5_Dkaga%63kZv-zn>M`Ou=?caQ$DJnbbILNgE&t0i--sRVb;I1yO|gu@g*36P2j+4 z$rd6RhpMY$mQ__g$Ig_Ja`Ja{6uWErwOlScZqYlvM(_P_qf)zCTaw)CYQE%s+LMfJ zO(DqM#Jk1j^Keb=>NVQmtFrGoY7?~~*~lS_J>!F28Wfa^A*0z0~`fAN#`t$O( zy5#bxO@mI$t3XMB(*Hh|_>>5ttM0ut`nW@*>ho}!zRS2f%-)y?R=n(3%CY7b>2HW0 zCUP6(X*34R>aaC4FSNhlme{6B#*|YG*;4IPqOqi^{9uphXu)g*6Y$FZ#CSX5$hO04 zZU*T?ERi_mnCy)SKN=OGnQ>#f$!CTI1e2`d>hc19*rtTV5s|VX@nJl)Pv5uK&OE*C z2}Rqb)wQTiw>;sRpVIZU*2EQKPBn@bUhDwoj(VmS);oLJRz%?2cgi;DITjQfPMYJP z9^Xh!_U@qI91`~QR@CE9>JuHaWgcc7BV$9UY}Rc!mM{0O9OkJ6@Ggmq$)v@7#%jHJ z^O&i$E|-7tWIgs-KJJGKiiiZ@%CY)9d0#iZn`OkffC>1oHm92#C+lz9xpG&nr#e2+ z-+I*%4~C>LsU*~z8lsnaf9QcXqOu8+iz1{_%JrR$L-Ho-L>)jGrVSTpzS!KnMUDu4 zLX6me!Ucs$j#b);7sfVojBBtp&o>Xk>vF8FizA+D6J5nX4ZS9IUFSSygS3m zHi(kIix6ZDUj9hVGyQyCXIE;>-N_~qDhN_`+%O|_XfCP^MHPtppE@bzt*L~ z{_Q(lz)u=OkiCOy1~;HcrO%BO2{})w&mH<{FMDjjK%GjrLXRo;UFUpHT z`_V1WfO(#mz+cgGdoqeSvoK#0&rT;eTjHl%skfy~wD8zaL3i^tU?zw6p>+kDOji7x z8hy0SzG~Id#U6)C%6=={;CZP9d0tsTFF1M%@Il};%S8x*-z0^({Jv?T@0We8%{ zt78~Q>;b}GCK$nn2BNpX#bnjM#p^EU?MWI@WrnsTFg19NRP0*^x_3_O@X&j`{uqC` z{r%hbmk?^Aoo=Pj)(Y~1tHjQo&fWVP-bzhR;)kT0d*XmoFff}iy||DZgZx9HrtN@3 z+P-6O11*u%vcV@)xhQ+evUSc_zae=(_m0dd5WS$}w=>tqO_QiYW!mtYfEoC(B#Ti<;t7f~Vs`Xf`N(Q6xm^eE#1-Y3 zT`9C(n+;;oh&(htVZ9)uwhNb6b;(19DVsdvkma1^&tG6A&zB78x#Hk)K~rsGyN}!) zx9wwK7$E1wK4Jkg#D5`ckkJc;c?2_q{eF}Fa6Abw?kkh%v}YPF*o^%OfTjr)2 z$vkbnEmR=&8&M&$jC0~!*Ym6b&#$|9B|Y!hvbKqReN7tp^0t3h?W}g^*O{|&PvOMg zcTrz8tDh1(#@i^7%mnu~4w4M>HY}90`0p!7RHkNc1Qq%QYCC3{NQ{#s=%MxFPi3MS zK2LI(i z(`8yUH)YgFb&}h^?X6Bl@$9z#CE%CFDD1HyUwt53(s%%XTQk=PDj$I+<3m2j04g7V zK1-lDff@BEtPFbqwk`Va&~NmDnKTb_t?sju3!#(DH0!!si*51vbd2e>-1O@VEYpJc zl#{y);fp(%@o1u2l3xB{gdtZ$pr~zZ!{GMKB~bj&bl2>Pk=+Aw!_>-V29EVv?%XzY z(?~;ZZl;NLyK5+Wy7rlErWAlBa?k>Ca+SQtPb_iwQl46)CwSP%q-18b$FVh8t_zoQ>{liC%y|> z>3YN1WMK@~ch4(H`L`FId5=6X%fZHY)ok;8=}vY*C90)u z#4~^%i>K8bV)&fgE6x)J&6Y0}hWEb}?10!ovua#D?;)*~g1Sena|R;34k7+ZKj_o^ zqny~-?P&K1!ajr|9pYgVhVn1?s{s9U@GIIe+O(p0c|h*iW_Ekc^?J2&i%p%b14^V` zx8b9Gb=%QT`l%w%dAG`|r48S5@AvxP1^YG~zwbgg8|}NIDSG|3qpa=9Fh>iMmqQ_o zZMMl$&wduessya*aOG8E*xi$R9_kNCbZR^4$&wRdHm-TG)Q{`>8^=eVC^1tHbd_K~a&#uAI0o0B&j#&Q(-lfAuW{)0$J z{*(Wj1Qz9hEjHWzJSAhBu?;uh>uJw>x2Lo9V}?i^iD#RfWwx&FAtnuy9kGMxM0WK! zfozwL(_*s5+`Oh-2wQU~2JBM_=(}TD=Pi&2hN)K9!n*^M=^`?WhrW104QIP-=Pjq! zs1?dpG09!Y#1I@R4hGh*$b((^=C0zKD|G%>%kB&;bWKBu9Y=6FYH$*Q3DECN1XEI_ z2~l+T#DHBi@HG5cah5C)tAvRg7|6=fz7wNL=p_CNebNlsr^$Q)9O-ErTL2c21%3=% z~Yzh^L<@QvQuEWJOAZoiMs`StnunB{Qk$O6s5<(>5x|!PFXz_vK4s&@n&dQ3JX ztm)8tC&?Mw?qv}ajGfqu1Vp36g2i{6K4q)EW>i#K{fQ~13R)gfCjNnv49Yj8so)k} zF{!I9f~c7JV!5@mGS`QEg_#go7JAg%O06V>I#S-~@939vONBI64+ih*_qZlZBH(wa zvD9w-iXeQh>dJ^!Hp>T6-F|dfe^9lTxY-dO0Z+#*W@!S&8|n^1Ub0ma6&{eXoPbPQDjVXp&vBq$nSso=nfEl8C1@v${QKYX1*X|(bh!x@idwn@x_4O>f) zyFU7drfQZr4hD^3R$+%arp8raXeOgpI=voJb&KZAxu;Jg!LZb(}BF>+H3<)2NQaWa-&3RTIggc1U@!%Ld+ zN!mDIq?0KE62X58Wedq1S{A7OXhxlvh6YKL1>vWu^)jImVH5KNqYMQvB`HEfiqMG2 z2I0mMT!M6(GBQM%j+BLXP5;nh={SMLxzPJFA{7^5I!f(8vGzlC93d`1<`utY+nwnq?y)207lDC(quzEp0}@ zXJ+Bzk;5ATa+?U!(*kj41&U;nT%8gI0W}m-3QdF!CW(8W@nO6#hE9T5412^e_qP8q zuD{(iJ==-Qi`0J%m3=}YOlq{Xu*M!zQ$kC2;{82s!akY1SJB^gm1CjX?%V38i-F@S zLY&kJ~Q`-)%5q%!j%M*jH4ibKgzNI)6}I-USwsL=m_Eo*+Ruvw%*f zADgLC9jdAOVZ+USQtT@4Fg{jX>@Iq zM0uM8==%J$1iqPUU1ioVJnGllmp@wQmR5#JN6sHi_AvdPO00X%=zPat)y5x{;2{$t z9duj$wQ~LDxP_PL=U3#;k=zMB4L8&1T?IbGo&0?5t~PW&KZ<**>guLulwT z2cd0DA+W8;GxCRIr_z zmL=^hD?{-eW*fjOdcs<73vPggQw#UHm0@GgzU~WY)WZH3fn!y;*yy-4o&MeBc(!+; zqc+{0kB@0mQ8odV<&16ntF!M%lG5om1$qxgjt)9BB$YwCp5c$-vO-!#HE1qz)mCD3 zpdnrwji%lJ_&iTVt9!R1 z;c?NTNdQ}{bGn0&5_uacNCQStRu+W5fTj*HSfEV{N5Nj{sk$~Tb(4$s)FJ zcPPF*ES6TK`a~#(9;jy@`GO#L)76ylI~awK0SYwOzwTu)4wgnTQ|C#1$2@UO#5kJZ zH9u)@uU#C8Z{9YN<+sn`*x)D@;@P>cjFOT@!YJby$Ucld=r68&7Ux*qys4Lg^b2dV zJ8$~Uo^-hP5%uwBr^}j*?{EQuvR*BN+G&%lb=DBInmJtRnWiK)`d&bGPacRRIGDup zOgPW(19eG}Wm=McVrC`jcC(L<7@_lKV`u}lww==$z>%;Hto|m zOc>M%Gcc=YaMOfLa}M6qY1q1iZxZ!JU*q8drrP}9FLxIYEh`V%%{u%J%cJp;oOw1Z z%VJ%=&3BgH$tyVL1S^>XY?xZiS+$321B<-(7mzUC_m>lKjK9s^7YBYG=ZZ~7P4QVT zf6*U(HQ9g9b!CaZWa2(i#i;QP@JhtlJufrLGq2~#N5C?>x1wHx9P|J_ z50`d^P9ddnnTMUDDd-wgC$!gePjPK)O7xpH`n+YYb}@#+a!~TD@Uc7!Py4ZdTM=gc z*Nvn}?G{TX`%ihK@o(0eU>PQY`-p-%k(tBoDQFs#nC9@KuWE6XS}}WjsnLl{h?E)u zpCz?$jGSAJ8wtb$r3etJ5!c~S`IpUM$$ok(>ePzZNv6FcGRStOY+Xqrj}7-d%5RNo zjLZjDuu=(WbQb}Bw~LVj%|%X>cAnUc*?t{`nvZQH0a=~;K(yTcI-+wI0m`Xe18Zxh z$s>O9?LcrR$OV)vTF6jFaxlf<6bH%1-o!}Wmhsv%+qbjr>6jR6yb%cP2 z9j0)DzpY1cHMScsO+3q^a5zkN-mrY+OwcB`>T}atq0ASfYZdod&a^rRX-CT74I>Go z;=nHl14`?yj+>xAFh6yvNPs@l5>GZ85BR$0h%Cb>`pyq@vF>hs-ZVeIuq7gnH`5~u ze&|4g4-n>3uiuOOh0AJ^)C&XNNX_DPPxAvntOwn21~;W^r?9P!qt%qz3%zAv>BA+NgAOpuh81?gt}nnhV;V-* z%Kr`Hg>xFQ)PVm{%xo#>iWGq5T++~H!jNDKYLg<{iI4x@d(9-Ud=j1?mB9 zq0fybLmD}W!;XPaOMBN1#Om4JwQs7@Q~{iM^ca8nNP^XkAL?ZHI3G<;pX5n8_n+fu zYMWc$aY#Ig{;|&z$vYYi_W|Ci7D1ww^jqv3927Hg@@Qc|mP{zsx7hLOY zo+5^^pg7n76HkJ}9*QyYQH`6RVfLCV;SRnm8?(-1{N@L);9S><#dNsrjcOj3j%wn$ z@%KPe$3YasyWj{aJoLQ`m)y zT%OvYm-06wu>0s&ha{x|zLz0>GaSy&Fl0PXdj|qq*PcUf)83-*Qcl+MKC+rbIIP{H z0=~gkWh0w?s4Ma=wz`1Clnx27+r=^?{tf5Bk-{Jt7l*cklel{n<3_BfgfRSoq4V2S z9(R;)xpjfGhK*h8d!g`;b>lqGcohGT4t@EP#S({aMjZE$r0yx8(rY1IF4k|(C8em_ zXsKXQ`wW7+@5mp%m^knyG(d=nGQ>pvhic3B;)2)cSRZf7QT*Dqokvu$+nVLXa<1JmSfM zac@$*tg%_oo5ajpFfH)efc63PGBmtHz(M~C~lUE6q5d8MuSK6YqS$a z=v*P4L~>;yrksG7j*jwvSLBS&c8(eA$c1M#g?)Uc?Sf?GCLt%!-I2J=mMrfhW~cG( zPAZqZ<-_l_!)IVYFt|=Hg2$}<-6i4+y4~-)g!H0Za$rGkn5Whm-{1zrcQFA!djsd> z3(SU~KAaCYk2S6oHTQ&s0lYWP<8e^viV&(42>VKGua{RMWcV9)M;%no2C9otZ9AX% ztArWr!yO>XLul<4k{1mJ&SS3yvs5blIoK@vP~m~PBgFo%sU>hPuis*@H3RED%8qc2 z?|7fP5x=&LdRf#U&zq8Kid>D~KzJ@cQ8`hX`dZq7P@U}xOrX2OU{E+urqwet>~$4J zbvAn3nu3>bHzR#aZyQw~1?z_|@%gkleq^vGglfz;^R#a-KBB`{h@82J47X%d;Vsf{ zUA_@zM?FyH?c`?0(N}(F#1%%wyz_fz(AMeGR{QPlcl>GYWuM))b)(JC$rR1E!ou^P zOlst>YWK}D%k3j>Dk!iCroC#`O>F6NLa@HFSO7H2>f;VO7(LyX(^Y zZ63iW{YtGlHBbQKXPBRZaU-I(Kl3ef*O#9l7GKq?H#Qa=Q z5@+wM%5-}N4+{a;Rr{U#l0hNTZA}P9y8Z&4fIzK)0@-`lr}SaZfg4p!azL>36ZdzP zZ_1VS{xcFCnaOH^zMa;`PoI5_Xh#Dqx->9ZRJHE!t#9v7+66ac4^FY#uaHL(PSz$X z#L5e*a{Zt3mL+;_CDj#nXqGcfH$@g>XJR!N@ub5ka&%FG`+IvbPzU$`Y)3I(pWZv> ztYtk1BGMzxunIEDBS{@0`6#grt&&1v$nIez^f{0kh@6zaIJpVMNuqG|ie^6=CxuYB ztok2yP4F$ccII9nFhtcYA}#UmO^*VY2;P54ZhcJn0y!{BaBz{m+$h3G31H$Ht;(+V7aFgXPuwp|Y(JLiPRh*kvUFOx|0 zATAPBbz6`?LT&f5p^n~z>LY2+p;5^b=khxCBZB8UZAlaHJA$2(>j(;EIonADcS@W9 zGN1GWB_u?9WAYCs1G17!H%MwS&ZkTkZPMbi&o|BHsd~)5ZWgs4I4P4q%G&1W1gx9} zR3ashye80}*_akVx8s-uJHw$c7W%H_RD?_W8)4G|vE*5taVOVm=uhqeo)A%8#oUERPxuJ+?W%65frzV2MP=KhY}=p9nNV_UU+ z&ZmX+e;6jKClkj4JmD0GW6<%D$z+f}2 zInWYK^V4T*->xFQzBbac^#zXEXDBanCszCP^5 z9{Z5Q+1WV>Jz6Bz20;$3V#PhHwc01)r`g02z!i%c8!pIgwX<9QbOBkY#GvHtG|0jcaoT7Q((gKxUO)4jJ=%_fSd^0 zQQ9?9qyb%g&!`|D2JlZ$bxu|@MWa=wGxaoc{}9s@N+z|tc-1=%8f*?;wvI9*+?-i3 z_W`q2>eq#vk>i;9E@YMx@)b7c*vkR#uD#@d-=v*PLmwYg1(7Q&` zNy1n?RwkT33Kn$xLPmYphcK)Y@?(Su;CJE46N22IGD?L+BpZ%c&u#MRMY?1N3ZPrq zU1_NvpAwk*MQVNnMkIX8;s7z~=fls=s{Kypm%qao;GLn1r=1DB0sP0Uhy#{ zxdw&X7?(aKE(>qO3c1l82Ny3UDp1#&AoHgh%7Rg*edgQDj3bPPLxQ2^VT}88Cz_$~ z7l|T7hI}^lsQDH)@n)Zp4V*jzNFf6yG?j_5>;;}D-m?d0Jilzqz6+zJ4&Ls&Q?R^E zynoY$4Nw)|{CZ9_zQ1#{OBVuGIJ~+;BmCt5z8EeD=1c?Tk)Qrn`?)5qg~*yDpo@*|IK>$>@J>Rk0Qy$^|2RKPV^rc%*x-*O^zk3izLp6rQ*0 z_-Q;6`9$wFM9h-?xD4TeVL2sIwBs$TRuDu|ZXMyB1a-xUu|T+kKEZvB$J(%*!(hPv zklS55?~1J%#Y$@Ddw$=*y86|VQ5{V`6Ag@JxPY7D_tIGH*$&G(jK5jV-fafM0+Z&$Czpc&FZzyd4gk68!lrq{D- zDbK(?VbErfa*@lyjZA6%&Y>qeRFpn0(Y$%abiK95(`t4p*Eols$7jTCO>OC>&)x2U zhJX|!uibcD`9}6CbA@u+q}{T)P=(RzjAQBdXLia*ZW(qxBs$c(4a1ujQLwU{ zOa*dG1>Nu#)*MYvRo5X@7HOqTRd>;Z(oLhh;h=>+_6R-7BG+sU>UTNXk~)pErNj_| zE;{XT3Et$9e9kkCGudP2?M@%w5N_oUU-|ngN+K_iJE2o77V1x6(hRhUVE9QfF838@ z{=lO+A@EOJ{?cIZ3pxHxM=UeGzf$8ic2k5{P1mmu+kvm2lAjpwoQ+eq`mM&t;m6m% z|9)v=L?V;O?#K=|Xh(WZRj64XAlY}F1)IvG(Y^1`#<4N&@=L(dsV4x>GR0jau`xn) zFbEgWt71Yk_R#VPz`ds08M@4PL3CtmLN1?qFdBK?pV9`6HFRBNO|H_*3OLu%EdhLg4>1SaC&$>Rhz5x~j-ITEH)7u}#)cl_JTLUSCTX2JqE~&`qSrE={qBo$3 zV=!d>n*Bgp9V697`&EbmvN`lArWKlQu*wRfQ`V0Bo`}_RcXW;w&9!h1_8$?~awY#w5P;59sB}ZW^CyijuN^3whUkJcPNxHsGO%t@!&9SCKZ&6r-lo zSh`_#Q(WaaZpT*B9aym6r_;6EU0dq#%Zq4%^9-|p2uH7h@Wx1Ds+Q@&Gb?=hu2ZNq z*)8HDj}&gI*hU{9qy<0!aLuMWvfHi*tn*36BtyoWXylf>S1P6#)&=s zo@w$HPM<2h(M;h#%51Y;XRs?@+PntWOh-=disri8PIY6!`WMeep{(0KwOg(adkU7- zF(=RS8t7Yx{}D5e;t!~No;H>7yR&+O;g(G*X8IT|sgHvrh_~s@7E}6pA?5xI5>Id* z2j(vBlv#Wt{bq#IwP#-LUgCR;?;ImFNo*6fFHH*)oCi1|E&i$0u1z{r_0-P&uC4?N z0D%C5euZn^UOnhZ;C46eR!En_mojnnCI7JNz-i4VbK_)AUst1DX#1tu?zh;HixSET zRtGi+JqU{oph5%BC*wz+WwmtKKy3_()IQdHLngYh6Ri)u@jy8MCJQSiMMEOkX8!3$ zpz%Z+^q`ywt{tjiM28(JoK&`vuqDt6DV~LN%>e)Hm0GLxmqF)&xhWH(A>4Ya3rdMk z$|;+=!TL7&SAK_1GxRDeFAR$Pe7v=UH;IZHi=>y;a#xk|`Yo-M$8X1Qb*%f(anOjV z`5h90=9Vm5!4!XE)|RzEVZVR}{3iD%t?21$Hbj~-894L_6SYj4MLH$82+ig+II%N? zP}*Nj`8Y&0Ij)IoMFZx8VbVtd-;tp|q7syXp>Z|$<4}u&fKrH-Ik<5o(bJXch*Fvk zO`7wqr4|)j8vA>KHM>qAlvI$Whb!r@p-}|OqZf}e(f7akgcai}nKXXud7BJ^Q&%1D ziKh%EiR%8y+|i@!_Ap9-ilIKkCOc^x`pC0Vm+7vqnV3K0NYuYC`Z#u68in^|T{hzL zR(Os91|t69qnNg>tM2?!1Ju4yj(wX(09@LX^JJGCU@jL`z5%cJ%(b=6{?ac%XPH?{ z|7=8gpM!n`3^SDpdMvGGL6TAf!R$Lbt;83Iy%ZoQr3V)f%hceVs}Gvj?R(_%5=OZG zkjCGqAvqNx%1E8Nc@$79*pye)3iN!JfiV zLF$les_t$V?o4_0W>2OO&N=C+XoRVDDIa~DEUF_k!YRmWMN`v&o?b!RWcYbJLD&{wRKdo78c_Xt)^SZgvExSJk{}~ zc1LoA!j{WIGU;lY+rLY%q90x|a)-AzuB&i`3p4OM9iQ|fDTqxk9k~P54J@53nGLGi z6|~>OR>nR^+PD=z_Jc4}tv{B}u)gofD6?B%`XuBy5ODG4S}W)Ji;x&FZGjSm_!zc9 zdpvroF1@Ws-dxY>%9sTQvtIm~&>xP;(hz5a@eW6jCAfq8VnaW37zJU{U{y7}mG>m> zR#m995+&=^VZ`7nO!b7PZ1c+=%V6$xDE z&A9&iVKMBPOJI2pA?ub&$6_1a?3|>U&w+}TkyT1I?4qmW%&?Gl(bQ|S)5o>vI*SWE z^Eh4(HM%M)7@WUc#=_7;9Eqw2j+^mW)uKmZ4k-Pp3i4LVV~1d^lsoyv?xsBgZ(~ik ze+9=LAjuYi)+@@0=x5YUUe);l@8&EU)k1Zc%_!46@*QbLK*)VRCqbAi#mC+%;rL`t zxnTWm_dHfVBcJsl|GMzX+qQNJp!;b6AAr6Pwiw2ZkR(HwJUz&g_pJs=XjH%a@?D5~ zz-K}busXd`IZcj_^_JnKDC)SHbwbZZ{HVJ`xzulCKla^VzWU+nt=h#JUqS}sxx>GZ zB{o@#uV!uJm9*Pn1Y%2)j43J~*DFF9Ktrb01D^+0FD&kPMzudw&(*6m`7=XyM?z;g z92nPmr_vcqxt+AwRz)mSNGc{2+j1B5YjTai*y~4|D8a(j5)Q&{u|UqmG6kApQ9;}b zI9;J2VqB|UhC`JYX{KClFBs#d!+@O0yIjKfvrT8tgHE^m_2C^}`ZWdCh%mvI&}o6G z`2LYbIvwl;k}WNR7P57G*gCG+6o2y~Q_IJu949ZRe);x1f05&=$b5be8TvUqqt;!; zNAT0%Ah97isDXLo26OgCF*cS?JqPBqR>Cd?1d=Q~bmu`5+FHRDR;`rK4>3)x-kd?Bmx1tYVoRK|sT+ID;L9Dx}^-lW;_}3%* zb`OW`pb2rGC*>7!r!8Oi$Ldw`ZRc%WK9>TbZ6ue%W`u@Ncpe^=i}83IdB31qY9~)q zzoAtt6dlN4NGA6UAx(luO}vR_Pm!7@j>e>ROq9E_fcs-GC}JxIl^MH4x($PwTQgp` zYusc|pJH=&E3YX5MIsBE=*j$!BECe+zJQMacFYE#n}R}EHW_(Vj$JI<1gOU{VP&ZX z$0GDP8Q)z|IeKRkHeqP8iSIA;I=L0@btDVOxvs}A)k*;_R?aHMtxIoX$x=KPkBcw< z^rkU;qWbm&=bJHj1F0_E+ipu}1SpRZXu^lr+Y*uv(m!{vhUjP5j0s?f7J;;Xa6f&z zaH_w}5-Iafg-IDmj9Lm}>pd8+pmDK!)c}Ril&Rc(qSju$v+fQCxfAS*Tx;_SuG2lP zZdHhEbUwx%<@WogclH|oz81@|(LuWeEm!tz;z#;27bLosO{UWX_cyQWHvKnJEq~tE zUX_e*>g^f0*<{|{taN`he@;Qh1}^C?gg@I~kh#0I(8(jTuW|Aw|K@S91sDqAwi(;W z&;hM8omYpu=ar`x4?S*mv483khvyU7_5yPIbWSWuquRSLO|A-NG(p&#=@}P7g{&$s)f?<(~nLM(BVfSMGUpl=J|G6_0eT)0l|`0%u17p{qXG_5}un_}qKy39Da!F83b5)#Q_k zsdMpFsR5W1@k2~j-oI)na;Tls>LXx@mAEzA0;tZcsU}?BrJA1#6Nf}^QnBuPXJ;Im zI9O4K|FHde<RXh%dnq#?Q$dj@l%%TygRLpIvLK}|z3 z{{BQ9$ER=n%Il=((Y#2{qL)I?B$Uwz@%^=QPm#)-g?f`rcM=@Dm?mmEo+*m&qjLtz z|5-t4E{bc}1k^S+W&@sIfF?Jg__1dt@eZ`fR?2DOZeIa-7O_wCXQcqHnL&21x z%uH>0IwN0oxQq2>f{PVKR?DZpYJlppYOC9V8H-T=>benjT7ij))qH=3hPHB#9tKoC1aJzAlVS)90p~v z_Eyw@lh53J!Woa_&%U128LQr}XIw98Cxyr33t8)de^=aawcZI;rsmd^LP-#)V`*~v z2EoOw{VDuv@*s#|LV@!blIM)&y%XeR8H^Z%`*+qBI3jR3H0X-Ebfj%50m5lvk;P^7 zisuYilDo6F^9Ykz#DCYc=6IYo{*F=T>p+8lm_@uS_Wp{xINAe6cU+=DatkOH=*^GD zV~WBMf=jwZLiJ3BQ2Fu-V^;9VFeb(BG9}XfTyNk=8~3}qaxES;NcE2Z;;_=!2a}^n zjZa4aHp_9{BV1OCVxe@9ZED>{R2sC*F{hKhugM%lgs*bD3tz(2_8|Ti_%e$p_oq|a zME0jR`(t!;Iz~XlKtFyX!Rda_~q!Dh=+44tpS7C?BH~Ig< zUT{_}lgu2r(G>$UI;})v%|U~G65$(mTg3uIs+CuJ2OQ~!5AMOkbcri5oAbZDqD*wg zD{NdrLnerBj1w|)X5hLK*^WMV*A7!s!O|$Bl7Q`QI4^ER!1vK(9`MH{8M3tSFcuOT zJ~zFyPF4A=ihgCQrPW)A4FvYBnoGs`R9I=|!bP9<#%RtlDUzm9Gn-4eXBmyB>T}y) zj2O9vFvU9?PgRTjpObkrCr$WDX-y4qN$@M(tnxTBi6GI5KN0=ogfHP)IT zgiSXWeWOZF^M~goG^&F&HRpbMj90$VI7HJKB}DuID2@GAOdX}LFf?gaLvZ?o{a$P7 zvShIk{-@{q>h#>v9}qVYgc}{=csAZEk-|>?T~C2)OVTIs^5M5lgw5cC^x_11WEt_= zr-1c_J*`AZtIZdNc%z0(WP5K~vF1eNN}Xkg4vDy_iDfMIb1z`>mrm-!&^~WWz3_0V zJ>}E7j-HpGXJ&b5MrvhQQ>fa`p}Gf2HRN(C52V)8be_5b=^2cpKW{7A*U{1G)KvUU zun(v}oVAc2g$M0q?u+^(0PJ0xZYve;Mgr@m5U#ES{L3XX$?LNfnKCeuf%WhQ`CKvd ztk5+vR?K|XeZq-AODvO*|4&CRu2}b|oV3+4Mdu}kqmtbjLW&UJF zYCyBAR_W>YOd{F01d`?T)AW%&UKZbXMw{~6ygqzbj}Oc|1Izl>>|cZMH(7pIT13V7 zqsFegTRH!B4)qg{628QJQ$17j<-#?g>;=6XAs+6D;NN0U=JPJPYk{(V3+y&iP{uwGeq<*w(S{5rL1stpVq@yFZ`oX+Af#HF6d>HkX0km(v}3OQ(8Wf9#JR zj<7)Dv{_dsX$FLpC$Cc`_VCl6z!(V3l%|(qwH53^?`2JIE3Nuzw#)8j^AvBAi{n6= z)@5`~Zw6GVJ}{fAqD%RcvC`}ALb&r6FG{4VZ5+lJWp^PQh}@!cY92+0Hd%aQZ&@ef zglV@7tbWqOLf9^X%k>s5$s6rpT?<1wV66_t-{qRDOl{Aeb~`rsPzi*!i`=Ax0iBte zT%b#M(&|$PLt0)r6BvT9Ue7uGMfA3E@-pqhR#bwcmsUtVil?LpB2*_ve1s0a+!HPECKTm3r{ZP7jRd+*NM$`M&5F%V=o1Z z5DRX_-5!$%^E*1plQm%u3kTPDL_$#rAU{1XNAP`8ouLQE;~q)FI!0KRi2pfKH=Zp2 zij+IJ+Ge)ZE-%w zaw^d!Mp`o$^xPEw{gECpxFbJ&wAa|XQ-S*Pnz{%5%{Z`@&~%kLhciAqD@F_HWZKZ$ z*2IXYT$A(t3=$mhi#uWm8d4}7!DH{=A;12eBHTq) zVP3+wN>weaDD>?z2wbo$N2Y|RFmQeB8waJ%RUQ*p@69A^mO0ltG}mG1ah@GJmQ=4Z z{q%Su!~>YV{gTt$ZL6lY*Dp=}VzI+(wUZ%1Y9Fc*Do0FN+2&$+kDj4IUpjLi*b8Mt zoPY0Joy4qXJ?^SpOg>gTl>_yo)b*_@m-(0K=SWcrHOd)KtgubY|9o&c@0f7J^+a(- zVjg)ef+$k7N@Oh))r4B8VQJ|vfX;7%Pa!x2nNU(n1>mpxD54|TO|ya~>Nouy=4-=7HNpn zCA+<<*7PNFi8KY9wRfUFx$SOW4~pcdQQ1nw%k*8tIx!ef^05ClQc_(z3Yn1NhnqCA%xvU ziph=*F1v!_z7xh6h#&7z-aWd#6R&81%HAa{u!gK#XC~$%a^2?%S3RWwm|LnVs4FjL z9;#lraoD_z;2ph!4wj2G>7F|K6EB8aVlk3L$!m?R^{Y{>sA&!48ZsDfC)!n-F!9iM zg6v?(iEzoY$FzwZzDd~Z3&d=ByuK>kziO$s+@-K=kY$Lyw>tt8y0mGuW%;78f2{td z)TpNZLqo)ql-8&)=rJUD4Jie=`(wcHvfh+H+xZE>F>IDD#L#WZ0J&%)RAdv2GF$vn z6K?D^VLVZ19s@?y=?$c29$8R^|NP-3&7*lLktm~KszLPFF^QdA^%&V-S3;+!{nPE0 zf%7w~qp@<8!<~DT&9}NLGsTRP`%nNW7L|ot)Mi?|{_iCRPNYW_g=N%~CW8Q_RA|+L zS3p5#vl@>5Z1v0>X>z-RdpwF^IdR#ogsMG}e{XQMAvMa5@pxivn+wNrR4eJJ3H;v# z{Nh9E8?VQM_6QNp&v%OKXtVMW$GHCqqb*$0F1BFhf|%}lehm5s7Z3WL2zJT>eQ2rY zfpB(?^D}kP&dw1a;n(xyxZA;35$ByYdEDurwy3jn(KMD`5Q;`aNf+5E=L^HS@R#y= zd7R)kBb`J!3f#v_;hj`mjB@5uVI%6QYWX22tJIsdRL^R>C1@rLb0g~28@P!G5TXF0 z$XY{erHZ_34np}b!yrIr@}i@`uNy1U(hQl#9jgb@?J0@e@J#A(h@dEs#ZWJwW(fd9YBR&z~Wjx-$OqK^wLs zlt8~=-mtKnmC5GQL(9gMHRY<^kE94!$pC?pC5%zaeS=WBCIf0bF`*3*xrakc^W!Bc z%r(5sH6Qnqk(AwY*kqIol|5A|Hs}E0ssSFA%Ak0~at%9IN%u0(kE!n0@+Em7)IxNx z+cdk{$ZQ3VDGVSNp(B$u|@=1DBNwa>P$Kc|MW(k)lyoiUQ6iuDxGqhU(?6o+Nh^g zE@+&iN4UQ<5sIX!ekr@Fzx)v?!f^S~jk zO|BN_v}vW+rcHCUxJ+i{n)Xeb<_N>}iGo<*zXH-|I;%P(`B3#Wxk5gqe5vF_uBgg6 zA>js0THCQ6=54cLnKUB$gINmLT{BOCi^RZBabQN0v1Hog;=F0bMP@%I3eU;A$OBHx z8zw$aO>bBx5L{9y^fdJ-tfnZuQ+kKCy(3vaFxyIxv5C6MX8ynmf^;9okaNJ!!OTL_{n)( zyo_E2QrW7PKNVfxG{veEV5$UEY@vdnptvjWm1U=5&!MR>Y9)TPD0pBNGm11BZ|c=B zBr!EKaFIf3syPEv@;jcX;*O26sHmg_r{eo06cznPQPGDKwd)LAAdSr6+$fXABPgoR zCNYGCmzb`GXrLVb*B7Bhw*!SThr57rtvCb~Uk=9v;oRj5xJ3iSRagbU{CNf;heZn~ z*Y#PXGA@5fvEjC({Z_A1kn#zsa3~5}%C|Ygedfp$N{rCy{8&1Nu^+&@Md@azN6b@~ zO_t#D>ZiypD@*?tR)XLW6oC0%R%t7gUMVGboW6B0ii8;+aP91rfR>Ld`4OqHeQk`NX)QglRgR070 z)$P@VlJF;Mvv|^&L${1i+<4@obI$4T%tQ#|A{tBmHRRUlHpe-$L?cznSE1yzb=Za)|t# zSbss{Mx*=_;OG9EO)8hOygv8p@_Jz&4WWC|DlIFCh}h&PCB>A;eXL*9Q9w6C$zhcRXMMes3RZ_K@Flx)p)AW( z`o5LHg=4HzCBBpG=PU%2upEnV~;h{w?l_Q0RP;yH<>2BiyV zjrVfuDI$FU)E^$XbSGawC&OUINLdpT^uU%a2Pi%8f`)f9m1&ewqjzLBK;nwIVpCB| zt%A>2^Md51{AA>jLfc*SAGD^xf081YI8aC_mJNN}+Gnkz{H5)5}fr%wHw0(z+=&`C8YR{1Yz#`Khs=U~j@uP{kyTSki|XjTDbv{Qx|j=+j; zu)y{K^$JbEdvu~d0!X_!;&SDpjA)2-u^3IcS$07QZoiAz3e zG|3M!g0s$KBs*N3zvptVs6@Nzt$~41GvCcL^WD8-(u0~ie>UgAEmkqkym1`5FsunQ z`UVoO8++cVU*2-S+F*XC=beV-E!S8ZOmt!jJOlqqPbNuYizRTLNwg40oBYQXQ~frR zXr1K0iZi6IY^~%NMYCkJ3QlH2LpZthjKNvkH5Y-9?@oQQaU^sT5Xk?T_P5}0)6M39 zS=ol-RQJN4uusgXZ!{Qu=NhSTqHDsh^TL@)sQ4u@+*rr95TdKJ{FoX%) zSuHkspTr#FW~z;v1#Xy8EK$HB6mV-_G=sr_p0tMauN?F9R~7Xyj17KW;GX^XIsRIM zgwr{#hyF0~AW(`@Sg=*IHdfv9F?ULxm{R(a*M)mAN&kyFoFVLD)p*a<80(od+)euw zLd$+gF=83Mm=J%b4tEgh#@RoV>WcVps*ye}kCXg3qpNTtkMmPSzkgIjDK^R^D%IgP zrqPZu0U?6ke<{L(#2N{1@Q?t>8$ANKGoB(oDZzP><@;=gjNmFM`5AIwQS8B50_NQ&ytIqmN&yU)JX$%=Ua_rNmJRMM`HAqMGt1o ze|dp0%(N&1hO2~$@N#fY@a573>f8W_egt#tBS53baLoIH&-VrT3=)}N0H*x5(fDn^ zm147_;!`;RP16iqC$2$Sh%0kq$(S+574%hG%wSt#B<89xO1YXB^yo#4FS-|guL50T z-NPCJKnbHZN)B_c_Q$IZ1?*O6r!e<=EKT6r2U#GR;A5I~)fy2(Q2buMA^u9-4kND~ zFs*joNSa6d8zg!cn;7UC86m~be$`y}Z%2>36_i5qhfh8EYrXgb(oQxzUwHG}<2TPW z$PsbYaOgjde(q(R_{6PW`+>>zx2GQc3z6@??S-xu!`z#XpDR?gBhnog)4;-f^+nQb z?5kre5X%Mhv1BeR7EegP1eYXLz47AW+jC^$;%jrj4lpfhiH(+tzlVSFlQ6H>PXQk^ zvVGPfg3w7^dP6eq^634!c-9|4br{~@-mu8MP+&ym!w!dh*i826^<-aj2WhF7uhHw} zYwBA_2f0*|B3XJcLusaT9sO2@kc^mUE?rk2)8TH8x>!9u*qm5jk`!L=KVVKvtbSIY ztSVP6AYYGbfC-DPllI{*DEQN(JtCP4KwS`nj|0zKERA&@LT`yfou&1iWHvt)C8V!F z*%6a8zzz8ikQ^^Rue2}c;V>cuix-E|CfofGP$G9VL0O}gWsXb$6cgOR06j68C8PxC zjAUy#!9)8MIbJ&tke$SkUJYk=6~=F|`HH&Cg~BRfC%`yag$c}}qQZ2kYR=>-Dq+;= zRVy_ET2U{jOt6IN!3-57|I*Xr4%JyqCQDt&-P3dDq{}-8CI5^DJN#>y;g<%hFLxq` z$uS#4X&8Q(7L-rr52~{wUgcU+@{&KO&YO6Z>jLL;^UAbE|MhIUqE}OK4(=B?C8Fsd z91WpPJkB?y2=M(Vl4Qpz2<26dY3M?RSOU1*Aag~w{+oDQ?1hmyjeE2cV|j;nz^ggjLct&4ySTv2ggKcLJs7#w zC?*MR-wD+FgmrWZn*i%-8Y8@#U)>e(zibuu2Xf!K_RUO7;PFJe%xLM?R z4=RYDVwhCoOS66TI@qpy>e3j_mU8X^_)*ljC{L6CB%-85;Xx%8bA&=Ima>Z+*Je>k zv*|MMq1;&tK9Yx{Aq& zF_u;=r!8cXJ*gHyg%nN{UJc7{hJQVtGKV~*+Z{5e#>)yP^0?NPtl7kjG7(ymHr9DO zbsMz^2Bvt$PCQ4mg_%(HZJ&aMzj!WS2A8EW`X@<^DEUFB{1ULuevM#p9y9q8+mUt0e9^3hRpcS_#b z*9W)A?0CFwds2QE1znQpl;3stq+9JEpB-V(<(EcB;S=(?lzjfc(OG?cWH!8>NWcfU zBMpAwaoAgXyWJWeMAX;JRc!x^6RhTY5$XA+;E#O~)GFxlp{q9~LNhr;p9|v`ib~*D zBB;I)MuRh_iP``lI|WvH1OstT$A z#iEj%{6!^qe7gv!9XPgm>~oft{0u?65#*wwhkB5s{6dtETby@3q8Yj{Y%*LSf=UpK zjv&KuZG2D;6AbUCbow&1n*j|bLP?;~gAX!I<+!Rn#*m=+LbWcJm&@&FwaP|e6~#C- z%!ND%DGMV3)iW5~AjCbTPvz+~J@SKvU(FO< zR=TXz&uB9M02402y*5aZsp$cy|JrKDjof&I5=WkUYG~FrVO?w1bn4noSuH;HpA{b# zR|~jrx`o*xgj~Rr6azU=AO~!Ko^<1C0N<-GPQ3V0QHv+-CE%*H1R6}LXJgU-XQD{E)fp$Ha=zdLSF>P zPlXq+_Fqz>d1^SffaW~+3GYK+BF84PJ-`63@POHsUUgucjI7uzInsCNc8M{PA~tue)00ODY1BIh@>2evGp0}(oHn>Y-8~JHh*gE#_542 zJ?BS*zFB9@i&>kV?OM|wTy@Tnu7ZPzx`(ph(byZ~HO{qLQib9}B(6+KRqRz0KyfFuGF;>IM)+%ok76@p@TW#G!wl5% zcrE?CP!F7ZZp!d(r0GAV4&c^w#njQ+%5opdMNXy_VSC24ZpB_8%IHivt+3@w%!)wS_VfM%4+1A72 zC2N&8FR9`QoxokU3&P|X6lbenKw^kujAl`ToAN5d4ioCcJWHeHVbS_WOUUwhbJK%m z1XKz5;&A`RJd5RF61CZ$u@Vw50x@SOIA>=*YD}gCea>{$az>SPNUBdV5`ZEq5)%a zR)IlCV>FtMM&p!ZOfG|&tkXBO#LjLZc&WFDYmH5@o{?(E{@Bvn0)O)73|{kWvxVv` z#MjiOBmL8z7Is5#l|N`m8cE-zv0GmavB;zI@NFKepo;qI$fa0i`Ifp#%`y*ehyDJl zcy0A)ch=?{h#CSOW}ty4C@*=co)C>u8lavg52VpX9=@fjq-PS1fG=Gxz@3c=Ss+aq zt);1|TrZ3MukC%cDCU8d<{=>yY=J8BCDj1%GW}T1IeD9TcxdR-@UUxmQPP^NMhNqi zvWX25DOou@yvy&MF|$1y^358ZQJ$Lxn49RQ+l;=*W$!3pvm@2p!N#aCp;2Jc5PGte zH-B|dNuv-g4QqFK#i?g5)4a*{cJ#hxiy>Vx5oC%0THJwCh@iKsE9UaB70a;*O5rsX4U-|ah>-*fG}(gfMD?y}ENNs?%I z#jQQ{!wI(|KF2@w^{qSa3bXvKt?u zCyMle8!vl{?q9cVKV>PoXxcls-ulk8uaG0^El8C--(mBDVP)XPq2CRtaxf%~$y_1{ z-Ji`RbAfCi7ZA%-1JKcl!G_(t8wPU^=GLr%r0VJt?PAUAq}9dM(&qeTzaJ9qh5VSt zQ9?_rj3CFmm_G*UcCbfCl887yisK~-6^2)&j+5D)^6!U;fKFX>LX!WJ-}(djSw62B z7Z@ubSoS?kGqs$N#h%SE9!U&DsKWTag}wbm7~M%R*~WbAI8#4P!{g)sUtQN18b=Yv zcV=#HFL%3px4ZWzIon(=m&-M^sYx%_)8$yE7^2pw~M(z(0PQHz`Ma1D?JTp6LU2ljS_NE%1S$GB3gj`hbD1!;47=6Bc%J zJvKLeV>KRMy)iu(Ykt^~B@SP$$5(&md*Fx8T%cc4pd}XtZ9`atG;Jv1!n8H|!r{8W%Z6f{PX-x4d!HMmfj=eJs|CF8G0m2|9FI!%-T)Xa!y4~>AP6zo$Y;5VHZpwIzu1jt%;tybq97uY(uYqc5|){)xYmghlt57vQF zF)yO|l|c7V$$NoikkQQZpj-2uE!n;~IO-u9-qkoxD@CM6BiN2(UT-fxIfZv2Rm*GB zMQF{VOQTEvf6QyyyrU!of&Fv`HgA4EZTv#qGoOhV2s|4IQb_OzlM&ZO?rEbvXR0GU z$B(cGEA|k}$k@!Ty9bEd{Pe{J03A(xgS_qDbkCEgUh&42%}stB@#ctNYriKACLQ>~ z@)aT+34kvn%v&A57b@gRYr0;4_#|cUF!JW`Dj^01U6p*0ss>x~vyYMFT2q_-0G~qu1wflB;BRMZ7yp;;-;X^^r5>tTaGsa#5ab@M1W?MqmX@Af zj_-*tM~Ifz$zeUM5f6vy;=2oUb&G53h~5Z}XqC%;&GNzbbt?tbtf@@mC=Q>=H*kTE}Lf;D7!kJ7(jUxlIIrHlNTtxa8g}72L-7& ze*R8{9W3uLfocV)oM2D#>5RPr3~Dl>SY`~J%{TXLAPxHmB@~HAnWdXPj=I^PzpUQ&yO@A zMb;s8$5h=_UR)!in$b?H<`glse$_D4e$BYfld!*EZQ)vfvo{#{bDsugK`2L3X`mki zKDrkWQ`F%r$h@xZMF%Ac{{wQ1EV4jchr(5|>lzt8V=_7HI+28Isy)_&2$Kmrc>9HO zqf_>`=aV}%GsMDL;+p%@ndAHEyS!LNkEVx|M0mFxQs}+oGmAN*#N}UQrflQI-;)y& zMyt`W+3(K3JUXRDt!yMNt3AhO-sC6yKE#wlW;~JM#~$K!we-fb$l5MQV|Z7sI8*@| zMmf)ACWgN85d#tL&+~vA%#fM@0|g)`)C@5FTH{l|1}FjajT}E=)7&5K<@rzHSp5)949E_&BQ;_m(Q9;UVAWnGbe< zOdTO%H*2{4^402dM+sffV__PK$BM?>-KQ(1V+XEJQzD1LD@G)mkadMjsB-+M>yo({ zg~>Z)J)BGSXh~UTcDt@IsQaUrT=(@$)#oEoUMOQkViTofb9eWRM8*jV>+)rOie80< zzKZJPiHlYDHq-@mCbcJ%xiFLd10vsvod5uMoMT{QU|;~^O#A7&@%%Pl8Ms*(K;Xvn zy=@5k|4bGJ<^~{_gMkSo3IH~J3rYZZoMT{QU|??e-@p*V!Tv7Aupj&&S|GL|0wHK2s3IUDq9Yh1awFO#6eN};_9coYCMKXJ{wH21wkPx`WGI>` z4k=tIqAB1iE-Iud@+(#=z${KIek|ZEDlLL7;4V-uh%V+YBrm=&7%+4&+%Y6E=rT+) zm@@1$WHazINHm-^5;d+h{5DWFpf?sbsyHk-dN}wwHaUhl(mEbGdOEf{06Q!@cstfS zJUoaz+&w@&ay`;M96oM7%0D(glt17=I6!JZ(m@VEK0$s#zCsW}q(bmROhd3k>O@{d zltk=BGDUzz%0?zeU`D`42uCnSSVyKv97tG5m`Kn`8cAA7j!DKz{7O1XY)Yg`;!6xm zI!lgBI!s7RR!n3}a!jsF@J%93kWIW!7*149XimIN@J}*Nc2BrZ^iWPvdQi4e08utk zc2S~Hyiwdz7*aw~h*GXn;8Pk?FjHDnic`W<`cxoPJXCB{npCn>&Q%&!I#p6tdR3ZL z=2kRTh*r8*-d6%wgjeiXMp(vpoMT{QU|^JF=waYv00AZ-<^nvc1-N6DcL<;RKw36VTFd z3Oar}g9NcPGvE9>GalOjuJ8#Dr|X7xVh>$rCvK4Mgq!4h;TE|s+(r*0;STjDVIPl} z3ioi0oD;m(1+zY0ggsp1Rk%TZ6K;~d5Jp zS{~IlHhE%l=j&8wI(G}b-lvh3OhTw_xiz^O1w&EhI@k7hMtN9|ol8_=O{Qk1YDgZ&N>f;9L~!&gC@gWL-y(+L$4F}LSf`QFGFp`{7}wZSi|YQr zXaBR1(W2zUYLenl2rxXWnb)zZJKv+kfzKIJb=*bKEazmTnQT@~O34aEeYT?#QxCAI zy9!J&;GLY+2lX3fKVSxHu>b&goNZPGnB%$;-rs8qZT9WnJt{N0?OvIgnHdyWNz~Yu zPm(t;S7v5rW@ct)W@cvQj^reJ_u714>=|h^8vMr_!AAS*Zv5XLPD6lAgoqF$L5dE# z=%J4RwlKstPQng$aR`TTGETv%B!4>2g0tdmI6KaPbK+b$H_n6e;(RziE`ST-Lbxz4 zf{P-<#c*+40(&@uOX5JcThrk#UOd)Z1 z%ut|21%(<%p|dwfd!7?9=Ip&g?r;ZxG(O9`{Mz4ARdGV;~{t`9)^eG5qKmX zg-7Etcq|@=$KwfjBA$dN<0*J5o`$F68F(h1g=gbAcrKoY=i>!cr9Ls*W(R%Bi@8J<1KhA-iEj19e5|+g?HmUcrV_E_u~WjAU=c-<0JSe zK8BCu6Zj-Pg-_!%_$)q$&*KaDBEEz#<16?ozJ{;k8~7%^g>U0K_%6PO@8bvfA%27( z<0tqjeukgp7x*Q9gVRjg3~vKl8cOM!OBdlrpmVu zcyqL2TBL<43R$aqP%F!<%8b>rHfbq~S!M<6xC6PC)huxot;Af7$3nzPvuYy3S}+~4 zx-LY_r$XyRch0QPr6^PtO*E@TUyHGp6QN1H-kGRTA?)(@Y}^#Z;Dn{#l5;z8OLw^{ z^45rMdwIs2y5sNh)KuBbbDgz&NiK{L+D4|CFx|0?6wOI}JZdzV(w$XuOxG(t>$*o~ zYNe`#PbHs;DjX}7$GJ4qY%g>#?}8w<5Mw)7G33&$z{T1h&=>89xt9jKsPCRYtrrw;1McB~w zaZ?qF&qDXuw5smVe<|xIrz`SoIAVMjkCe5l?6D1*nXEd6Q|(gI^^{-i&Lyd@ z)m-R^Duz!J|IGFxD@&n!tYEryH}YA(WaN|L%t}=a+c>ZJKFjkpb7)0mvZ7)tJ-xkN zTxLD03&urC<;2y#(1Wqm#%4_B*-TOZwW_C!Y%gw!s1!LX693HhI)>uw4c#myPe;s% z5u^4nigTe;s#fdxE^W+&CsSjY&Zt)gT-6K8EpJLu*`DjF%ut7jYGCHlxjt$rCDkUA zWytC7ROPB9S9Rzj(&tihDnVaVTUwN4`pTi*<({j$b@h)36pl@sa70zQl$B%I z2BS;%I|r$tcWt99XJU4+me$HhC+7&una(K$#;}Rl=2K=fcf}GXhJGPeE8N&x^B(AW zo;_aFpY?lP&wDbaDxwlkSGI(z78QX^RSE9w2%r}Fu(;{=g=|a%)^1ew&x-rv)P$Z|yNGau-3Yn#bOGA)s z`umh~MNuWNU~!Aj3A0u+ZWBtUq!E`MQv`8japDPCQIRptr*V6#Z`n++Ia_2d-A(P_ z|48c4*HIlGWKJWQDnVA%hy7LaW`sHEirHST`qmWr;9!9|ez@jZ;5y*j9!^{wgf&}Z z8YFItE|o0V_RxxJk93zDS+Ux1%_8!+ zZcF?5VJLspUofc|(MA}LU2X=pDr1vPwA0)Mj#yVg^m3sX5E|As&F_ZFVUdzd zL-<{iu%+fQ?odH!+aYPH!HNr_xGG(CoQ8r;dL}EGru?|i0=kO6MhtB^sG*nZ?b!I> z_nlxx?z_WuQ=3)NM^!7RgWMrPbJAC9RVwF2&!5yj1azXQoXK4hD42D_i|(W5p!wvC zT1$4@G?37uwAEEu}1ivXw_Ew0$k5g}UvlZr_9Q=nAimZoZ#0BiV8i1AV{W_pk4B za_-#y&T{6Q&pC5u#5iNj!O~1+?Q>SFn(?sl$R8Qwy*N8#$%;vx(*pCJ#PP*AUcF`4 z<~_akAK%NE1=l?jw(q=P+r(ea7-p>UON@Q=;`-)2XP$k|f1GWuX6%Ot7;`xouBhpwX?C)SUdphg(iHviN^u5fPaC+oZByyg zzcGir_f7q&&(e)DJ$j1?Z^B^-Q|K)I@C=^5hPlNNoweg1V@JiY{F0s3u~x9n;1XD%&bKDoboEpUN2MrF6-oYR$*opXHW3A zbla@0Ov?`GugLNv?`hnYFFk$g|LFhf;Qx5w|9Ig4Egpb~F@s1xwMucL{zJ+i<*ztP zlBg9aBqq+Al$UYbz(Np9N_Hsw_3XE^&t%`v{v~@<_vw{-NS~-r*JtUQ^_TQljzYrX zp2OLDvyW$=&3=&myRPVdy;^V3+w?(wj=n|z0p0Tne;nUyU=!FNTh1O}-(t_REPJ1Q z#O=J3ck>-eJD%qdu`2RphD^3XHrtS22XitPq<$$YV;<&ZK32~BEWm=Sf>p9AR?TWy zE#!VZN)yHxBCL@$u_%kNX4Zo5O+>ldSe#8_?W}`!vM$!mdRT%bSudN+rm#LXl}%&) zY=CKOI-9{}B8RisY&M6@W%Jm4wty{Ui`Zhege_&ukoOgAC0oT-vo&ljJBh7h>)FX{ z1KY?pu@u|PPGMWvR(2}e#@;>dJA>_HyV!1aCfmc#Vtd)y>>PG3+sDph=d%mg zh3q1BF}s9a%J#D~yNq4Vu3%TP&#=$3tJnedId+g;4V`x_yN+GY4zbU(FR(AN8`zEP zCU!IQ@-6IE_GR`J_EmNpyPXZOJJ_A&hBCNvisQmsO@jC2iZgHVfIbb z`y=d8_89v%`wn}YeV0AKz6UJu6nmOI!!qny_8j{@TIL1zB72Fw%znULVgJTnWj|#9 z&VI&z&R%10vR|+tvDeuf?8odU>@D_F_Dl9F_BMNm{TdkKH|)3UckDmd@6on8`vdz= z_FwE>_8$8qTKd1)2kcMm&+ISkuk3H^@9abN5&IwZ5B5)XgyopQj&jC1SGdYe+{`W9 z%1gKn+~MF(?&5A<%FDQid%2I7b3YI8AYl?ty8Hj@e@?0Z7xf?fQLSW$BvG%=9+pHn zD*H7_Bqg$UNg`m9y;~AVknGnb5h}_8SrVZyvh0152t#G>mxTVwvJXfi$&&qsB=lL9 zeNYm5FUvk83H_L5AC^QoF8fVM=-Vv&ElKF*Ec=Kg^mmqhR1$hV%RVLvGJs{jEeX`aFvQJ2YtYF#iNrKd1S)gAc$Pbo%N)jXq%RVg$GKFQIkp$_&vd>C_ zoMGALBthb^?Dr)>_OR^pk|2dx_613hM=blIBuFNfeMu5z6w5*a6G2+B><=VCZn5ku zk|4oY_TMByma*)sk|5Ps_J@)n-&poFNsx3b`y)w^c`W<7B+>@iHzYw0vh0r~K_as3 zPb5J$vMekBB1lP={dY-_mn{1;Nsyc@`*TTjRRhE5A5+p3k{!$WT zEzAB&5~ME6zAXvzmu26P1WC-Yzm^1<%(BChAe~wEHdy=3PSoV*S zpdVQFeM!(1Ec<~Z=nR(qlO$*lmi@CN=n5qG}CJ&L0>byRuVKf)9WNbhcmri60|wfLz1A^ncg4?8lLG9NznC7 zZCMG|lV(GFug+(&<4{xB>{IZy-N}h2-DHGiGW3z-XjU9gy{)Mz$Z*kN&-@0 zdaop47N$>@1oXmm^lc*G7^e400-|C1R7t=#OrItRD2M6&l7M%ZJ|GFmhv}LmU?8TW zZxaCxF?~=Ha1qmINdiJ*`W#8XN=%$C$oc5|A0wS4aXzWBN)-Kx<52B?-8V>8m9H z!7+V}Bw#tFuayK;$MlmV0pBrwog^SVrmvR-%*XVTB?0|0eS;+6K&Ee$1VqU6O_G2O znVymal*shWl7JVPPJI;w0l1Rp;VrBXsNx-g5KT8tJAl)koc$VpB zO9HZG`Z5F4NSjT z5-bR&e@PN-38vp7304KuZ!t~oE!6IS$ zkR;e9Ous`CtQ4l-DGBxp(+^96<-+u@NrDZ-^t&X%nqm6gl3>>`{p*ro;V}IkNw9U8 zey=20Jxsq(66_zQ-!BQ45Yr!!1e=KI-;f0Bi0KbXf}O7noJc#raNn{_1zuoK!9_Q~Uvy}(bI(4`Dchfr4`{o_y+bmNpS6be; zwpw>u?Tet0?&9EP|zvGzU_>$A)+~@qrrMW)qdfDxCuXBG?y0rB1GH2P= zvLl|go)^7#?`rRJzE0nry}<+Uf*K3%u7?)Cbm^$*t{ z39So#JFJ9vgzsqxHQX7gi0qBL)mYwmQ{&&84o0KVp{Nnt82fhYugzPVZ*6h6oZNC} z%kYHwgs)Bb^TgE?Gp&nTFKr!eo6vT7+i-ko{6IWAsd3WN?G^3kwEwE3vEvV&3p>Bm z`Cix7t`EBBc7LVE+w)-0k;Kx(P%@CbueY`L!rqT2CnkS(@{gy~O}TK&xBFOMYv1{O zKbtyr>Vc`hnx;+LJMG8)HT}E$Ul~XY+%Pb#^=hYUKc4QM-aq})=}*rnoAI?7AIw}h z^WmBQHMnu`hQaq{bPf13b|ki#C*UOL@?0i7Nv<7VXB#&S0P%CmSXyR!e!HGs(qa zoR40T`@H*i_DcJ!y}eU!>3h}o`2)2JC-a)C+N!HxUGnOZ1q)uSSyH>M?SRc!Y3G;O zY>&AcZLY`cRa@=$$6OJ+>oHq3zxj7I+pB%IOzrJ`)n4`9RW*F_!rB9EwM%MVU9bS} zc(tZxecJ(hrO&36;7gCWY>jxsR(qBGQL$?yf56d<%2W;3cQaXlJlvV+@pMNc;RcJ} zQy%Ct``x@D8taL~dCb$#li@lZ^ticoBKM5p7FK4(cZZb5tR~u?cBwB{(ri{GVpofO&CFrG)O-KK!zO2rL#Xk`i-)!V8+Ry8_ z#ahSvc~2K|5a*L{A{a;{6VX`IVx`ML%4XE!lg$yur9iNbTirMrvs&tSQsJhWo(ayH zDo@`)OjFhk4z5!!ExmH%#zPh5OIEG}Abdry9?Zoy<;{aT`X^OoPQ zS?QfU|JR2r{V%xe5f{%?PjFQCOtjVny`5pa18=I^c!5>jx&GukTAJDywb*8!KX1_) zR`WXJiPx(6xp6NFCqsob;0r+S=x$mvmi$`H5!Z1b>ltJ5i(eaIV%`Hd$VCUQQpfe-p)A@5LAk1g8h zax{t5X0D!4F_aEgRt6Po$t1t)w%c6(NhMZgnBMffbB}zD(|C28qqNkqEzd{KsF$m0 zcwXZ0G-y<_;Nq^%K)D6Agt`dVDVEVHjf1o-=gEHb*d|nq><#4s?sF>v@Oz>q zO&`RDYq5lBQnXoVtTN88YE+t|lT3+NcQZd=UV5_0x@);P_g6)un{yd#`9A~6IVDq7 z76PlIO1$n6?_50Docmr?nDcOzGRr)9aVO8@GIIj^SFG6Um`;s=|3(S#aV*)hmV9$Z zpHYWU9u>Ufgr960`8v42D;bUiyL|qxIJLjED~Z04FXRZjL>i*qi9Raem@4r5!oIM= zJ}5IXnX-qqk1DRZWpK0aaqXN3D(6tvwaKPPg|Vk1Vwx<9GN=5rn~Lh-4@P^4AL5yG zI?eY#vwDZsyLs@aa`j%yc5g*J=k*o*WgKk!j6YT=c5WEJ1$Y8wNuw+^Xq^~*MSbW2 zGs)-TLmp9pI$jI^Z*k**YZFQ0eK+dM7ba;njm!3_<>i53mp{_S{S!G-ALzx32lnlI zpdu9I(Qt<;Qfch1jF>vYI6Q@aP;u2sCtbyVZg|`Ibw*`TGd-wj8BI%T^ru{W_VtQ# z{n@t8uomvJ8RzCpbhWJuCpulLcPPpZ<31zJGpH$1U2bm%4|3u)A;&W6#gMO5!d4++ z(Jb{m0PaJ>K+HyCDjH@0&4NzZoj^YfltRn~t>IYM3QqK?Nw?FZ1QcIsM~r)2##EP= z$2v-VIOTD=tv;91=l1bAX2WhuDyP_U57-wp@dHgtrqr2s2XpVZJRXKH75lKO5j%Y)4znB>z4HF2}wASG^4qaC<+o!G)A|`CUkSP9}Uz1MW}?P?v8(_)XUAWxY@{=ZQNsy$G&4T zUmV{()myq*UN!!St6VkPjQ<|J>M5K4bk1zfJuSD@jg5)MMm~Cd**Z7h|}v5jc6}a33xJv`luoQBh-zXqirwP%)!q|(AWrk8ljTjS~-7himb8gDK4+GekJx+AU53b(~sS7V>P*69Qj zn_j%>rWZ|oTEba+u5yXf<*`c;=43%#0n!70I@2av`f|XWZ zbE$XpjK$5nvfsBuUCoP^?m#g_Ii6SVRA<5)dos$A2zDWVfnY!ZFhQ9j7U+gJ zie(47QT;yglkkt(GMklTbe7U$NeMOhmPslmqb$;r9C zwe{|@U{6CZ7O<9w>kYC zRXyu!x+hJp(ps7lZEZcV=B~!2j%0FCdHodS(GrzgZNV8EjaSca_IfHSxy`Ox&E^ut zX6K4FXE%49(LYDwPNzeyD{r3Wa+CyJlh3OT*|@jL?y`A!Z~L}Ct!SCfO|^~I;F`d2kq$@qHbt_lj6pl<;IepFfYdix7V!GI|VbcMF1vMTqtPU}hD z7QemsMbF`DHaky0lQ*2Ve$~4dU;I#g?Sd3`#3d9DYrn26Jr9IoX zH+Va0h1|{v;SJ3U0Gs%^Oz!=dM0v_mU(s4KVZ-hP*2uz{E#~eWvu5q+Hn+@N7_lzc zyTpClW55i97n2T zS`xobWm2-QWYj_A$BVp>4Mch@3H;rGZ<12$3Fjq56zmfLB!}3DHAJBelF-}Kp#VKC zR)AXcEK+?0M-Ylt;_B%S#Oh*G8e3+C6ay@I$wm7vc9fS_bkqi#>v_}UNXwkey%(9h zZc@{xi|$QPPLDF$rj)bHW*cyZFIZQ_>#CJ6|8o1q3g5fQRmiFGH#0a#xn71>fw@HIXOS8)%rlrQ&7>O3Hg$utVb>lC*VQ~hJuqK8L; z>=MbY80A!l5BTNqdv@C6skJS&tpj&ED$5l6YsMM;MzkkvRy68!P$>b77q`$@ zDer`YQ%Erg^SRIm$c~N!z>anUp9gi^h0`9D^_P0ZtbUTy;I z!j_p%K8>S(xhQ_>DC6@IiFvtaWl5B@A#UBdoNJQrhJpD) zkSh@y8E*1~>GP;zAE5-Wm^DHhAx=i}+|`*#_MlrU!?_G^tcC^LI9+Q_a-M9~@HIn2 z9%W`V)ER0jrKEG|>PF5Rl|BfPVaSuTC`UpWqGDu>Vf05gw6N3@cdF;B8^IkOVnS_3m814EY48(VKeO`&q+tB?Ep6D)j-%o%Wn?dd?-A4P{#9K%9kNnVA zTV7rZx2BZ$x4<5n0r^_VLa2qfw1oP}5~BZIa4N-MsY4dHxjCQMI3PzbPMWliUuHdH zi95Y^?Rh21K{Ge*vNza_L(_Skb!l(!Qofegmn@mwyTo_{AvN6id;QiauC@DCInaQa zC8t|gpTW&%uMKYO%l*OaRw{bua6YG3Y|l5&=GWfd(WOqFqs*UTpOm*l(~uq2&;^rV zFZ9zmxDPGTuRwYwk=q10Bakb91Z>mP1vEi`lW=4wa2vv6N<^&$tOvN-V2VbKKl$R_aj)5D3N}Ps*E?;F zYVR(Sx4hiTOGmCm8eG@8N*q<*+;0BNG%q(+t+SX+mILKoz8g>5hwVP&m)BP)J{K%p zY(J+&%&~3F#-Hdlrd{4@hs}AtE7A}&`OIE?&BsggSE?K(uIpV5k)ZmPGR<4H&N9bp zIpFi%sPFUPod>S}oXe*un`v7?*aU)PNL|RRkadmHvL)zC%F9Z+N~n91A_Ym}i;%fg z$|FrkELQFh_b9Jui%N1cN)~B>^;;o4R0fyI*7cAs2BZuB;X1Dk78|Zs{C&`eVkM~U%BBokl2KhAK_=?2}%P99NO`|MInVeDk5oy#0j6VP# zC8rNGdBJr6RwV%<3?XzWGK+-bAs-2tQuH+|CuBotYk*vou&1A+uZLh&sFnVx8Yg5;V3)9?r_-8pmIQ|l@zf(@TN8QaOT$rTmhU)^u6@GrtHJRkh` zypIZKynuW{L_Zg<4?^#i9;De7xI&4BmbeU1&XquOKf?TPZ0%q(f;uTdBAgU zvxF#{4||Ujx+1n{1H$8vsL#SzTY_FbE7!c-5 zl047w)Vje=-vH(pj6EJ-U$XhxkYI%h>+i4tl_I-?7g z)O~?3oxS|tE1gSbKei;%`o`+XC7aLdTQ>L$UfvX|v6eNI_c}**CqHtTwZeUwEu=*1 zmM(}olPw{|Hf6<}sH^t#iyxb_*m>o>%V#a)%OW$EP2IQIHhIk(zb|j_SZiZV<=iy4 zJdE8ac2$z=V7Bj}U)RFg=wKS8`f~J{f_Isuw=&YDd!i}|O0b2LljtdwG+4}#>`J5o z&LY%;8pcS0uvD4=F-StB`KGUZUF$sUq?oOy!PI&`(7{m#cyNEKDYQQCl-HE~n>Ovg zRM<`Y+q10B#-6&M-)r|ZRw$bdINM0f8~5_v3sdDeZ+S|o=O?Nue!^=4On)L0Ut22WL7cj1NCbjz(;Px z28My;q|X>-^as4waMY_7X{v%Z5;_jb&Ck=y+(croa_58oBL@QyU8-vI<|7$w;|Os6 z#o@!h;7^jji7UIh2o23mc6J;8eeb>8V_3kv_@6%f3zO*=yfy?kS9M7AeQbUJ5kRP* zm2xx2LE*1ck*R)#OF&SZIX;{coXkY=HhyhihwI1zSI54u8Ad)cjYSz$_D`2Nm~LJD z#cyrc@U1VdHs*@V7;no9jH3@2>b0s%)QSM?1Z@j4s9(SousV2wpd^|bdr=DyZ=f99 zh)s=m_1kBhei0W{VbVxaK5F8-H*9=LS>vr1z>#iqcP?sbYk%?_@%h72Af9@AG(slu zm^=tcCowB2-#+w0^fgFp`nVh>BB7E9sGj22Z&>`3EvKxms=ap)KCj8l@C#2r<89^E zPLWlCVXM8%Bi)JTckjGHK}Ka>&UtcDyotIAbPHs126E1UIMWazX=wWqCt$JYN)l8c zk*t!;mQuPqp|E17rhL2D{)AUISL6RAQD0tFEAmzzc|m4YY#%76+p77K5Y@w!Z6H;F zrzvlbi@epttF@M5?Vy*DyDm=z3ep<0kjn>c6Y+ESaEfVsDgu$l$W87n+U7r_q zS1Edb2jm|bmYli-4j{Av+a~cSYKf3r(24;gtJ;GqAR{lIE>XDEXUmmYoVS#e+h!Xd zM9e1Zte+7|8ST^iO$i1hXms3Cu>; z1?=c14$Me^n;M{>2b-kpNZ8M2+9%%{~mPW&|g(dfsF&e4xa z|Ja|^%Tzb8P#<{EEu7+{)&S{*22SSPB>>$RnMYj+HxCR4szID#N^nG0Mgqu7P&EFn{BziyKJ?o;uXj*gC&hdnrxDN1a8tfiX!x{!o2T z?GhuY`ybn6JTp$qAsZgkJ1IAFs zWCvx|*G(e`;5%yvl|ZUnSYI_>hu3E{er~g&mH(2V1KuJJa7_&n^Bwu4y|lzc=c{dY z`(Kn>#37y$hdE(O#|MR0M$PD%n;RB@98ASGQiFO4nnaJZ9{=+SG+>GF_fA6THe=}7OqI1t=)R| z8t=MY3hJ8AngXbzxxLDsLyB@}k0RwUUv$~I4L7}{sxLh$pCNZ4xiSR?2G+U~?LHA! zJUmU*-|BfJ7V(o}o5o>dh%^Fg0|N_5pszj=>48F6fz7F$ejdx;Ron--lDWd=QG8n2 zHe~H6S;yPD_({eUZl5w)@wl##wk7#EsYE}Rha2V*E2qJzX|S@ADsz2*NLGy88cg$J zq{3AeSiM(K_O1>D`WAD(xG!MT%+FH2W#uFf$Q-q<;amE zq-BpBaY0Ov?l&s;9yvn6h`4|c7475Q3VlEw#EfI|-ZP4UBmpcGO40_=96Rm|f5BE^ z;|pvy<1t%>*T#E|mu(*&e@c0`q{O)2R%J8pFY)35jcc|F`w7k!<)I9?o$`Pj*aSoo z%Hg;ZiibXIyv%!Tcr&tLQx+U|=3V-#kufs1N>2%2Wv{gHRmYu@b*tQf@*|4bMggI4 z^CCJFje|jII4tmTly&rT%DX63rMnCo}+Y&_bAjv^robEjjD-!RCtjOREWrj zGBKb365~LWGE`~#G?&(URF5OXub)QckTIt7JXpkUC2`> zbgnF$A2~wBb|!oBulrBq0h`T+s-f1Y#4e6&lxE}0_6j@4rN|cj^-Jk3;ogS9vTwc9oN}Rmu%Z{TflSL*JvzEcmb82 z{Bgd+2xZRO= zgvAciHx!Mw1VZG8a~YBHc{mMOmZ{!1Yu0McQz;CRowecQuQpMbn~|o6D56E90;c7o z9O><&NSw!}Yfks}Ia;(Tz42gq+3Gb^-%<}yeamu4OEn*A{HaPpV~|3b`>6Vy#?`oO(}J9>bsA~L5MoHwf~27i z&14w1cukF0yk7~?SH^$8)GeFweyWo&h5L13xJyL8OBn$fr2HlK4a&H(;+W!d(tQ6( zU*Px-)yrs}(|D(Y>I7PsOngx@q+DnyR8~2}{BY68cr~}v^Tselj>xunJ-!e!K0Njt z$oJ9T!w2&b<+}-?HN=WSGmeT~s}edv_7FBsWyxLsk={U_lK3j`$y`9P(3(CEwCaf z;Bnh>%okdUKLCHy(F|5$S}0=DrDp@@QjA^eF#keG)<0TBn%_bK(u zqiQ!IT8VGmh%ZSbF+U?|@gca)VuA}Zh9X$JAI$ISyZJ{i%sJ=R_x9J=zOjc#&zoo* zx#ynWmN~w6c&kl0wCJTDUOP!!QqzC$ug@tluG?wbdiZ;evftiw4>wIb&v;AfkE81A zLY{?brhE&RL4y5c{X^t7g$wf&D1sE{RB~WJbXnjdry5jN8-G6i2D>SJdjGrWOD`|4 zdI8}iWxMb2d(YT@TBfTkvLvOJoOwoX-L(C;Us_)ssn~qphKW@Z`Daf*{Y&nBo15F- zy7G!Urt>Ry?tG*%ykv8|$$s{drCa%e$v1v+C0{jPElc*RGxnU>QsA4{)c|B~7~CHI zgq(i5oN1qslfrj1stJ8;0{Do0cru`t93)|cb&yEME)*2$E(|FN4W;}h>d{e0{ zHgB25e)OzaXeqTo6+-hgK%PL7l| zo6-!S*@iA`i`#PBN5jxn7&IF7BW!Y4*lcYi{o=MN#c!j;M<+7lKoRAsqGyZVqg;ab zkoAifY`;HG!+djADrh*XREW|URkfDV*yO!HluUxs>`0&~b-&?$J z;o|o$)I#`6(+1f z3ml<#c)yUfl<<&X@ayn}2;8PI8xr^YF1vk@oMwae$|Jvmk7|&d1cUYrU4)RMY-bQ7 zj)!Hqx1s?g(%Z4Qcl3Vy=xavmn?Z+?&$p9C28zoky**eh(0+VL^5Qym^&_hK$kltUQ>My9D z-)ZdWSToqjcdrdxvU=6Q(8*`#8f4L5J5i!+ur&0|ELk$pY;|p#v0#g$q!unb`x^kG zje~33*In*ee!9hc(vtFn#wnwf@$q}86Vh`}iFkx&NHEsq2U`{u8NJ9#ReV08?EHCn@0uQ4Fm*v0BVK6R zICwOHxDoJak*4^B)s{6wMErJM>3dfp@odEI^TC1f8%A|s#LEj5j_-5n0k5Wtrkpra2>+H0y%tS zy=suPQqMI((@iG7+o#fSRyc(L;4nuH@XZHLT%;%Lhk@SHMS)waV6swBuN<8h+j}JY zdr8QqLqi$ZO-ESC7E-bu~ynyt|#*vSb_Qpr^GBsVqA93n+Bt)fx6T)J|145w- zR;s92vOD36)NwK)8!SFBM&6Gr(7(cp*dZS>AwwGoqKg0zH%2_fgsV+jike>fZHt&< zW>JWPaPG74eymP5K}b*r!A>Ye%vs=!Gy&SP9CbpihO<&z;2c7UeTcI5hkY#o z;A%*slIuVJ`Rk2gV@N_3V@TF~wH(wDTi&=0H)M?AZOWOspDk3Lyvul~I; znQOxmtqf=K?L9p|hC(AL&mLu(##yG2|IOe->hBehn_?_ITnze7To{VT9I-z62=Y@- zTm)Qaq0mtf{>IHKXLK1^$;TJ70*!KC0;7d`$V9oPm}u-GRP-pdk7JAnBUY=6sYsD*-q!DJ54X1n1*~) z+)_rRd>456U*Y5Nc=!0sja#4NWK8C3R8|r?8AndWMx1tV$B0l#^f7sFkLYbM{2^37 z$HXDI@@(+?6&*Y=R z(=yGamHgb3oJ{#mBmHY+Cxd|Pp&}=gk}izQ7e?zxCb~e7@l0Z#K@)67o)C}EeE|Mv!4L!f5=Gn=MQG5d07 zZ?uPlI7(?#%$VM?OVfrGMrzBT?R8Qiiy0IuJDMNe;0(nwh9&SLn>DjH9B}^4$Y@jU za+Pbv2Sz%R*UtqtE&I3F|_v zO1`!*7YXi@=tOP?_P_!ch|TB2OV06!L!I_EOtJ`hPmmDNSOx70-!ZF4+F z8W}@4#4GtZRZhn(7JDXRx7^|YcNb)@CS+I;`Z$7>Rk}Y0S7C~DS7$Kk4+LoqheVPB zc&%{AgAgJ*4$z*P4q^6z*3=R+u0A)`()48|fp~=}_uqH_6va0V)>fyzTvGD#uUvk^ z@Vj_Tpw#$GD#b4<#ezv=Z3S8bAdZ%T*LPer*agcaf^|tEJ-+`{0qzmD*3kc=82oL5 z4glM!af`!fTyIpy)qPAkutrZbiFzJKyw&w-y%#p;1B8Wq8zb3XH~z7E@$9v`solvQ z&xo~jG?oJT>_WGe&=}k;AaYR(-VcxPchSIS9*klDv_3x?Dn~r%QIZUJXJMR=4+E6R z0fdxfQ@Mq(BA|TYD?@_&Or&w0jc=k4KVMd~I7`n7u&TdkX3S~xFc+_Nz=Z%(A8 zA=F$sb=u)YmYE~Z9lYXUJXLsB>9txH-=p;hCe}sTLh}~BWc;)xGG|_9qs6l5@U*Fw z&0*3_dA>0tl7YrPPC`p^OC6R+P?OxPTARou;Bo{c0Om1~!AQ`cPMfdPXfbe&E% zC2+4bhBL8*S)3U+YW~iOU{!N?lCrCzGd!U>Xx!
%#5t+7yTm^Z{)9pPFdh-)p? z70Lyb71b@_PUHS~9CyvfrBIBY6sik5TZ@D3`-%{avZo>tk4%nDNlxf(>hQldU@{F@ ze6B#R$59h-4LCf;$#i8BOsn$trrrtrEf(C;i3_~}x@iDw;0ZY^`-c@CBEkZ3AfPz~ zTh$sM^$+)VKRN)dG~vd;h{`d9Bu=A_&^a)ugxH7x?g_?KA39Z`gQTcBOc6MAkyTMw0?i2G4Ox3=#$`p!h`_Y`L;Hvi4>H9YH@*6wq=g0d!E$hD* zUDJ1Iucc2L{Nl{|NW@e>b~R3zw?V zp;DWxuKb-Xwua^g+m?6A>z%gJp{QCKj`?1^;e2i~8IP(~-lUsMdJ`5DdektPc&}Zp zXexjEEACjVQVspUS6=S>K}fBP#oS+cyS%AFwVhUsm4om-L7Iu>ZqOJch{=Ihz^nvT z3i~I*uPVYArIbV}Fk0ivdqsc}JHBX_$tk!(g~3eNa22_Nzzg zj|?;fP%gsZ2_7jxhWXu$I6;@y5P5sVA!i6!WprnT96%~uf+GjGQn>9X@+)YjR4jZ!)5kD34BKK9qOKpkC6^i9 zA3`mUdLi@pgvux441?MC3 zkhgk((=V(k4Qqpqj;_XOv>MZ8t{|pPgT|>!{ypivq(VvLjlFGwnFCe9$#dG2Cp{(A zUgIs3ctut4*mBbEk6^aY$8bzOoCiHlEp{Z0Tc&qJ&3nBe&V=h~oNO!KD>nY6i!0xk z3jEwWe!fP6i)hT6#xBRr&O{nPrjRB35Do5rZoKR)r>hizGei#@EcEL_ULxq%BisjP zauGTRf&nBuV4;%~C(e`MxDOUMpZLGX|G)*$!PS}?Co_4U(&)Eoo=(x%gF^hth9NN{ zIS~9fUQY7v{Lm+4C{vXAACH$eS_5y?P9eW%H#`IV@L!M1Ma5XEIFCNeLW@F|#uAvs zmkdC~1})<@5>I^T=Cv5@qhsS|G#+GAG9rOvT+M1GGp? z(q9fIQo1w61lh#!rnW zwG$Osi?3YK6h*e{s_rOmP~ML99U?sMNo&=1%8ES7;`5x=#@^6m?Sk&L-Ib@t8&H$S zR)JPMvt>&{o}IX$?(4-3PCkdX)qB)tv^?B6K65n}b9gY>3sx%W&zSlTKG6JLuGLYh zI_#?5=5+%Nzhd$s0-%HcdZO`)vc&iuFAaKO$(l@VukFZ3c86N(;Azx7{6b)=j4~|Z zb?JAt=ohqR;7gwrvs;*PIwp346){B9v7{lZ<8vgj+zml+L16R`^i02?uqd;hog*MU@HBR4^4R?frnN{O_yUX8L~fs{*oUk#mR=4 z58|M^Um8YTMf>5R{fDlqSe_bz0*#Tt$-Zjql;&2`N6$b1kqO&fr~JqrbmKn?ao@{_Fw+DR7lgv6sk(OJ;S6o^ zbv2szPs0V#*h7~Gmv6iO$Ey0q0}s5RHiiT1(89gV6O=!`@x~t&<^9*sKe@Tp*=&0G zh8tfpnO?fFcSmdOvuMI=doG?a(u6!N*4iBwwm6M@(U?FFqH$=Ivo&nXCy#&uB_BU> zKc)&#c*mFs?P1y96|EpE3IZaR5qQtB3u^4kk_4Y@+HwfLGbzi?3*M&A^o2dQ{H zDb4&_g%~oP&5P#ZAb{JcSuju~h4yh*7~wL=ZbNxl;2ANFLWl-3J8xtO`HeFqzJX{u zQAu*OTHGQ|5kP<`F@GnmKe&_cl&Nt8-+A?SO{VW&ZQLN!x{f;hj{|h}#}{o^lnyu?Re;4%`?`u`(Ngne^}R9Tif~nmrTpP zL3$E8DNObU_eW5#1aYaVv0A)X+JbyuRM#XX)gA#EaYWh*i~IyB1d*>BNoLk`*Mi6L>AKM?ZN{J4cxl`EzH2(;uHNT;WscR^-J! zV(ZENPbKk?dSU?OU(*nD45x6*4;T(XaCsv39L;zQl^@K{5>yXeiI)&9V z@^4g5t9Dx)_(_gZg;n}rt8e0sk5+`I&6*Z=I$mq(ph^1%tMBnXhUKX%e66Qq_C5*B ze;zDXyw1kb4_q!j-QQs8w|9i^sctk@`gl4rwXtz(2bu37@jO(}x;$@j3%k2G zhI5(-s^Sc{;!lim{9Qe$+C?;1C2~b^=ZV4?2jMLm-7LzQuQ=AUk9Tw;k^>R5h~rRW z__^0ZoA#^f{!J;dbQeGWc2&LoJh45!<~KF?k4;U@-eu~z@ZGZ>KWn`FtIwSq~R|ot5{2Cw2YRP2gZgubCgK#HDx(=O%l4JsUjWBS!M4v~&$lWkr?@|ya zlLuFn3hBk9s2taMqUOy+`5ea^iXNbKFND{Dw&XV{cwL0A>Ztl%brQx8L+D-P&-6nF zjQ&nW6zd@&vlw&sla|BKvnL!3C(W>FsYv*l7shWY^_dp>w$3o_oUzrn(Bv!St9f0S zTm1umY&rY+&l@*-m2Y`-i))=TTlvP;na*0Cn^sk7{B>Xf=L-h7v$TrO#~c7FS1+~4 zN+L6s7!PP#ddq}P%(a-XCC_iD+^3X`u)65iJqGD_Pz&pipl?8JLE8j<^kWzSa3P#8 zk$ft64oj244;b#kDj#(y1XhD6U|@4$=d!TqBAVNK7k(roq4?YxsjL)lFR>GicaSV5J980Y)Hjcndt)VXQTQrK zguW$(WARP!Mo58!YNBOsRI$btNg&KQqhE0dl8DT6j^v*Lr_g-(g76-f361N71c91K zsqV^vXvDi7nP^s33EA&v@2k7y7-O^ z4xKceslgg46#jw%s$^55bENf)>t|Te zKJ>NvF662c5;X1hI=R}kw)J>pMVXO%TAA@mX=&$A?WH*^1P_mS2Kv@(yhAh? zPtw@~Kmkcdza>JokI+uH{Oup4?)TU1ZVZ-BbyV3d6c{jxR6gFqla3ZN%ZL#QfQ zZd8D*iJnhWOIU4N{0m~MrcD-Wsja3y_i}xWtrYQE`D3Lwf2`R`JoTj$Cgfh8Frl>G zQ)0^>E4>pYxI;KTl2+4_(xju6sbgPc92k4d&o>U_pUl6BpFif!Q4NYxE65k>w`TO) zTGRw-aw~~H^ioLoqJ9b#Nef$HHD^yO66>N`AZ&`?{+RZZ%APWI@;giIua(G^W=meP z4^x&g4%S)InTMJxdo%Z@()_08c+cj{Js|q{N~i5T2MlQ}y~BH7){e6L>O-k~%rfQ8 z3eUfc#h|`BC?a<~fVjwJ3&#ez0{7+x%9ufIAdM3^tWI>24;#Zg1WJi5_KXxwUZ$u5 zEu?C4R4;YBOh0)1DGpG@Xh9Ql5TtV{QP3f1sn7|e{J;s$5gvq2N~7#5W+^wpGj{^H zX{P99h%GU4Nd}fbgP%J{6^1kLRsOzIi}`SDT8m22NJyqdmaxraq)N?Xq{hw^^dWuQ z18ZawMj|0Sgf$)}n|&IOP+lN6@kI!fA`XF3LVg+i-WFO!$zVVYVE~BIP|^_20zD4V zcV(c)Fqi1^S^9|vMN8qnG|-R8q0CEhK1D7KyT%H#{QJlsjp4S?_$_3!)F2}}Bao$@ z#@Pv$lD3Jd^gDC>;rv>ntuoD>Ou51@0xAv4rc$4pQanyW!{J4b@@tmo{ymgm1dr!a zwH_eC?Yz69)X*Gmw^P}MU!zFpb<7iJx*$+|Bjw9C@@)dg#GoEAfQzAJ--y;5$#Y6g z;_G`PdLg6A4xLL;kWY@XX5e4d$kH`MqaV>{JCGE zJ!8j5>D%IlJ6=|l7j(!BdgA$WWtvXEPIsscMR}s}6_N)@a9Kb~18&9`KURn->VN3| zmAT3RtdC`l|(ME*K=)AQd^8g{8jwM=2Zp3fkd^)W% z8W<*-$>=cxn>mIvsob!Z7o8AoX*~B_iic9E6pgW*fN$xSvnGi(vrZt%2_+Q%HRbb4 z2m-%{*N42%oM1U!@ps$$eMsRL*OOcf}YkFbsMfPRJXM^q?Jw>=_5 zD~4$0t6iK8P2Zp>8>Ua+aGTVgD(MG^{?On#JLKF?1I&zo8Dn@@xnwg|22d_}wg|gP zzgZf5Yt7rbBV&Nv6T!dZtqgOqgU>(i0Z(bEV}(bY#_q=cM{P1mU=3lIxK$0EIE25^ z4Q#ezJua>CLQcgxA1Bo7)SJf5;P_cbjg1j{iW`ec4#_@s{PqO7*K$l5&HU(|;Dcj$ z`h>kO37#cFuNh;sEmkr)R4W4vIG)Xj1Ho!c`Ua^@Em)VO2H2yG;t^o&635Z>GT9BR z;5JOv11u*+P#6rtIe#)p>)wjF4~SZz>d25EEU!x;pXNoOMaz|?xrdcQ2pW7D0x4j9aWF2CaVJ=4;^;+wKv0 zg6vz_{~SmIl%0E z)c;7Cgj4!oFN=&~(*74^_>_4^=PTlO%J&I0K@bBgVN4y&D_W6~#=y{um`q3$3Xf-Z z7$As>b;i2`hfoh7?TJd=Tm`+3i;dM$O^a@dyob=7T~PfTeoiAx^>*;i8t{fyDqwP^<~yx;u5n3F*Ahyd=a+nw1>A>nNQP)R$Z|g@2Gw? z)D-tP=~DQs1Dj@BD_xAQ!h5g~{ueh0gVK7qV!TV3ly9xPVCAx$f+fL`V!7H`Z!bzT_`vrhzwbCv2AYB4IbqM~noxM^)FXl?D zc;eY7&+pk2GfA1dM~LBKA)1Z$SthTGs1Ky~raz1C^O(PL^F07+0q((doTRBNIU+*_A|qT7EOVZ?i0%cwEKc4vVn|AF-w3a^T|K9+hA`lPC~KsdIV0CL0j-KOJypSD~BStv$e$S zk;(@mv{ME(y^Qgd#yJ_a;~C5(ZjT&ae2(HR_!dR^>=8ovizBqs&Rq$XC{0axu5q@N zIa)w17F1@zY@d0)SP7|s6+@(5av5Cfwioad9G})0VAQ&pYDg7|xYl~?t=91yf?ipd zc?mod%>e&SS7Ab?v{aa|FOX`^fiqOXtkVbvTrGhuvKjREBkqun?xY1N{YllfMCR@7?6 zbt^CPt@^yF)ruFT#4EDY1(Cx~u)yWAwqWrbEdKO?$Bam&Obw$s7l&pr8)BU}?CIH2 zVo07xF+kI2FqG#dr%^8vj3va|P42rX>Fx7*KY44ho|}NZVB)Jh!lv?{yzX|tz1RO$ zCSswd?A|14GNRcTS0=24&Cg!L@hYwROt|t~V-Q25s;eIp?F^ zRh=a7KM+RzTs<@at>Jnz^J8-sOcD90GG4DA?UO%vvj=sTTRl2=@905UC|53&%W%(* z-JPs1g>Zulx3U^e2c-RLAke$5ms=((D%b5r^$~{!S8oyX&6V=97(^2=-dXM7^UV}& zn8WBSDi>f;2bJe}1!@ni9jjc0A1zV3Y3wqn*5_IIQ z$2Bl>6vuPxceLa15!(&@lCWsULFDK^kRQktb`I1pP7&E<`EkXv#Ha?z92+}elfuXM zAtiRBVcB+2<<*={Qk7ZSyK`r(B`~yQYo`0K`IgHbYn$~x7|NDvMy+ETCr~o z24~jVpjrmDSbUpL`-L>jM`17$GpAwdTdjrytp31MqpTKrBwkWfW4wZfP6hE$Y1~xC z1%O%PXm0$@<2r3PM+Zm+Ov^%V&h~A5PKUIu;toVVF1IT-B;*UO26ll}6&IvP37YNZ zrQuX{@!GPe|4NN!vT9TRdl>cmyy8QP+K3ZALqGIxgM*1=qm zG`J+{TL1;siK$6Z!h)%$&Yn-zd62YH2`fDASLTmD*FLZ9#<|4oU5s~dV<1{jjy3E? z8J?hn&9&AGEj690t-6V~tK9T4^Xa*)W9~-Uee@$O=0WKfOk9ICoq6v~>|EfcWXng6 z((W7Q9N9VZ>7PgkWa3IU%8H*}r)}?ECu^G%MqHL>f%}4eGu~}{_c|UyvEvNj@oM1R zYg(V4Uu0N<*VVxC-5~kyHG8*uaG6p!!s7(Xa4bP>1!ZD%)dsqqy&J9dMv?fScDW46T!rUC?PqSXoTJBY3^Z7XW;-E6#XPdd6cWy zO-m{ZWv)A4z~XdODPtit%(rL(Uxh=baJ8t0`w$3N1X=KTd(e@|)g>;8^`A~0`oS6- zevM0Nrn~hM5Nc`Xpf1r@y};Z>-{pJevskG5Bl-1>;^2XW3V)Y67E~-YrvA4Cq()O_bQC_EDKk)FD&(88gd=`j2PlRo3;MfELP6+IT4}MY) z?Ra4OmM!M9SNziz`}WbN2hEu+TX2hZ_Vt}JHDJEN6J6?$n6ESsZ(IX!>*_t?x%uqA zeRzfLA2j3JAK0;F3++U59B2IsI};Hvv^4tGY#M4-mTVANUvr_g_AeyCdbyXG9y7su zlL=5R>?FDac77PxG^k){S&fpbINUJ?ITk9n)PK*7B-zL!QQC`h=QeExC6@(}8_J*N$Um zm=<{UE0O4Oy@*i+lRGHCRsGm8p{Xn}l4(FP_Ls^D(ES#Si=Y=-u3SeuTiHBWrBm)fn0cJp}_18orE@ON6fF=ZSW)%uSy#i7lW@!A$& zdVKKp&qlbd$QwKdfRU49ZgA!Ajz?a92-H)ZBbEh!Xi=>7!sT>NeE>PzwWF!RejbDi zQ6IH}()_f=DGeSN#&w_xrVw134qY!g8!8_iJPdSKBOR5vgLE*qar8#zOALrJV8DHU z78qcv3@`u?&VjQ*;zDdMz+MFGDL6EEHhBCTdlRb3&g_GZvzE{Of0lB;FB`lp`~eyT zAF2N+TCley%;jKTi)Ll+=^6$p@C#%F{L;9i8R|>RI?x?2q86UvBjhAI?K!)kE?PTO#zLkxl`<5+iTxT%W!Sau_uZBIH zrL4OpcUrzMi#4IVQ>Qex(aFl#yv9b(7NhYzzK=tGBCKT_wExyHJlisErKseDa1 z@_6MeE+DFVD46$#;2nv*E+58Trkrw%#Mm=VXT(A~@(Sd92$=C9RAZfxVk zJey@JHu$K9|9|eIFIi$U`GPQ;2_aNs+>y+sglJgjc>U%3^-=M3xqA>66)f3u24Q(E zf>X&5Sc<|Ac+pPWF~?CF{>$(F!>CFEUv*Vy%x$1h7NRkB{2TH&^VvVxy~n?`54d*w}yQ10(*+u2XxT z?pPT=CpdWU+Vet}4##^|w(WS|o?g$Yr#Bo}yYi}Q&WD_!0MBtL(*k5^_O_?A2;5~= z)h?<%m%27ASB|j~jP+nD)|@|GAE`ZsTCNKcY3=u6-RVC7mx406UTRtk^fd4-~DkfMBXKPDl|~EbG`J4 zw2U4GKK5Z*UL|4xVzd>yFl@_V4V`_{F5ZpC>y>!GIx@Df)RcO44WoLwW{#r0!G}0>TFC!MjhvM;2&1O?|~K%|5CjmH7^!k zX*a9C!ODw*8Ow6+-DuSt%R6veb;I)B*#I-f4>`|beUh}_S<+Vt)?pn6%K9r22cxL! zvzC2cVwxmDndPOR5U|g@P33CZQ{oNz9*0f+Y)`n#pgi#IlE$_az-aau2XyYk@WxCG zD;1+4B=n*asNS}bcUtwTQh`ZSu3Xd<3^r}U!6@VpuL=7JuAjH@EnNa#4}yc9IPz$F zbGWrN+}!bK;RFx{W@(~az$*-rQWeN$;2JPuQWXlb#A+@;NGKOU>K4tFv@r0Ucjlep zd%1T&+F<+7zMr~eX_U(HH$Uj=YBqn*dVsgOM!N7nG)6QwRO7r>EwOG%vEJifsRl4F z>m1zyR2~}|8oOJ%^byx89Ls>C2kZNMk}+F63C_@h$txdTDrT~S*k^1hdc|n=r!j9= z$a9M)>>;P$bFN>j_2^jnb<7j$^xop(hv$0O4o~)%ET@0@a>Pk??BHM*3^GQU=<197 z`V5Nn&dhj2UAQ)_^Y~@Bu4wZk)^nNG?^)M`RCo_{K&N5td>nnko{>-sR;b}0two#B z@=n4yf2KLLs|ARt7`tBxQ5jOWBb2)=$^+^^|5;nKe>PG z0R1i0pnZLt*W9h;WSWBqbFv<&Oc~YjL%Lq8D5p~x&YZ>SGiD69LO848P2*y`)A?FC zxd$%79??9%b9cHw)3EB;A_J0w8B!qK8#u%L5$f@JpYjm(E%KPU{@`8Pwf&C$h^;|v zTjoOxvBR19A7*q6m$o__xc@$;nfrp_;9{V@FJ@~Wi8P`Q*f%HRTdUAs*OY9r+SXvp zrp*IT_Yc4jQ~2U^wmCxRsSxa(F>G`C}2?*@}~z}y}Lf=P>&;oaT=>f z0Kwv>thyq;-qE9m)Rt8(xb}2N_bqiLJ=Nay22`U+p?;z+*Z~#*S2Vp z0)z$HMvw=30OnDEI?E;X6_8~HxtSN>!1=NB&(DX)0%PwP@IW6}vjMcS$10n=%t-0V zv9Uz3b^W%rJ)Lbk_CXgf>a+lTUl?I8&p8Q)pP)=hoCLnUlXa!txXEiOsJ33}p$yROXZ-qgZI?5ARZC#SzZr!H4Arnb1_qs$MP&d_vJ&KFcYv zqQLkhR&O>!{t%;FsQzOr2kbWUiE{8FiGhFNCe**}}%v65wR{D-_M-)TgBFr-!g;A2^;qSQz_lZf?7G=8C_(c~515rmrsCp+c*thTnVF@QShi=ElAYg6BUOoB4}SV9nIs zA2Q}uU3=(eZl)@Dc?tO9;u#@hFt2}wfVFb;!f2-!Zu`7?vaEjow!(|*k*lvhqKYe} z*S&n#T`#Zu8@i`@3lFS3Ul#NzxH6=1^gA%$IpBca2)pRe{hLRjhu92d5^utmqXyW~ z$zAQqWc#ksW1|NS;8GXM)mSyCNA>;X{rU&TKNt++QX4V9SYs%`Jy;#9oS4VM(2+zp zLfCM^@PjnQk8-J^m!QzOVemNg(&lf@$F6{KC9E#!=D)~WzolP}k8@tgu!tW%`GrS$ zS#d1BLoMgT`Z~)@EF)>5Zq*X}U*z6?Jj~{#e3>lrL`Il~Qb`S$O2S~2FKSl_*+Y)R z*ve%{zk|%ps%wqt5T0}bDXIYt%21fSstYT+x;AumyKOhTDul&Pak1;Zt*5MeAsVRgJzs+7jZ`B(+$Jds4e(}IB}R}>hWuz5ho0-!AR z^Js_qn`??sZ1@De>Wb=a7$%sDr~YBxXYi$h+@^x!7iH(LB56lhC1L*!%?|n_?(wFq{hzwGg0TC=wH>+{eT z1PzzLJ6H~CL3y$m;ex*#ZQ(U^bzI~*4u}Zx9c-vrE^$sJ$pg!*&_uPD>h5B{=@j4v z9b~{8H&lImya>j{dmYS$@o@<23UfjJ$oIg~Ag5#0>0=`&9&<;%*f=&0x?=q~diGfL~z{2sUA6zGi5PjclPgA|O@*@FY{qI-k~ zEx|0UIAn;L=e;BJG0)JhArI{_pArG{5A>V}d`vztw_6FGGq=n8#nrvNt95Z99Ko?% zc#-~U$1c`@SO4H}sL0(6fS?JyptCe|EtHDYzDF9+7xppq2@P!b2>dt%Ft(Mk+ygB+ ze~OD^rPY^JE4?MI3~7NkY(mqz#Fxi;nC0*IB4Y-!Ko6iQq61OzBE7NIA8d<&bwD;$ zpj)iGg0#vKSGhl71dk?^%gobMy(oBY*^(ba7=6!_e_rB+Or>*TBZwGtS>!=gtPCi$orTn+sZ@=i6f7pM_MZRDoUD_D*36yjQ zJS&4QSMA-Uc2%-1!K{EGi*>6zy^$T$K&v2+ay)Ald&BsY`EvH!sjI@+oEav3tzQ9d z)a@7AXHhqnxJMY~s0uStUI(q&{Q?x}s5o#i z%vLqvdlntVES>bJjp~;+PC*lSO2I`P4<5*>YW6^|$%loIwl)ZhZ-l?yf zSEE}yRcNGetTk4sRi|{6Dvy81Obm}PnsYNSP_mC8p2%+MN3x}e7U}5eZLb__@9pVO z4^@t>IIpLrrRTgA>LDw1k1r7D2n5VKyv_Xk1rcrb${+O~dpgX{_TJuhdaVOal$!CT z*@-usIYh5<=!s^p`4W=*DB=q&g}u!?DB$b#1_ItrU*J;+a+*%BfF<+j-)lE%?9Y8C z+P>L_BWhqXXJ;=Y>_v^8s|^gYBQ2;aU_=M~3@qxL!c536qind&iAU+YJE;F~vu1ag zC0CQZ+1t?aOP6LRZO^0ou8Zu@e-Le5=WDiU4qww9<|~Jf5{C01{Rl7cl!hQnvZEt9( zDTH?~MqU_cc+4qlW5Q~?7am)_5u`Zf%6drD9O3 z3gt2b&&C!o1%x)yWD>$M09>W<4>mF~u30Ms`bnfvOFUgffOLMurlPfB3>LD^I`D7@ ze8<7o9JIzJVU~#)e13;;$US?FU>{{m292<%i)sobv zSH{Kf^ZC*D;N;`EMvp_a`8|Ds{ZDQg8MyL-PwiC6ZgV^rfTgg#(Mg-UcK>@#llJd& zYW8#NjgGF?24AZkmI;u`t-LmH-hX|CY2J_<-Rp8AQ7GW@Y#O+7|C8I+x`s$|G}@1% zh!sxF_8oU4t;~+XQ5TYNo~^!fTf3m=#-~~Nx#U0yx8WJ2P|iB#s1-)%Dvw%W72&U0VGZHut*{N@ zA6sENY(HMN!VcVj#|pcZ1LU;A9;KhIbzhh@qIqL7JTV`R$I_{(bSm#Q5=PQU<*ziR z3yEmfidjDgjBGBRPKAdCH&}NrHd02mnn&))^l&~u6`snbli`bynUP4O!a0Y0i?`gfiNy|3ZoMSL3s6PE*80Ry3^~CQ;wjVVU(ChUBdWg zkxvq#i>#V)P6>p=aHz=7u18)mq#lG(KEIWLi?{XotG1+?eo(Y9iH>GjRD9gl?`c2O zPAX|+!|JXB8Uq(KkefWv9{E6Berm$1TQknoYo#^{V$fQF^c}R4R)N{G+q*DKV0x&R z)=(eyW4e3}t)+F?m2nNsejnHNoXKkUav<*8_cff*-U3cj;Mi1RELl(muMBE~Cro3VJWSkFKQm(^a$|Ho^z#YWe^jqQi6zT}#)|2Qht( z(gek5k_?)nX_}!pT~D)=pd_UzO*mhRvXrAd73c`f(G4_DAEuAcjdT;;Ot)a7eU$!$ zZl#aX$LLS#HcW=^ppVm?^a;9)?xsJZd+1*JbNVFRM}I+|qEFNP^Z@-O{TKQSJxHIW zhp0rKqtDY{(Zlp#=@|Vt`T{*d|D7JCFVbJrWAr!lI8@VLqA$}Ebez6IPtsTEYxH;Y z6g^FUPhY2RU=r|6dWN2*Z_#t~ZF-)*L*J!;pns%)qVLi7=>>X`en3B@m*_|IWBLjG zlwPKPrdQ}^^grlT`Z@iA7U-AQk^efqLI0C}MgKx?(*L4=rTQfuBk*i4! zsLg7N+N!pxL1;f$sO@Tp;*ZCSNDSnijAYY=)MV3AJoljj=-QP?Clc{YI^)Zv^U-`d zlFMh~nVc&bPs~R0Mzn!nn9RmgvrZ1-jw6+6moX-LTuI^OQds# zZ!!|iX47-Brrv6d8{w7@YJ!vLxs<0Sxl4u%8Ew{>w-r#lqmW9ZW3x8CX!&%e0biN5 zi$k23UBJXP5sl3{CerzQI_Z@^GM&eYh+>m~cqGLzv~jQrRW3}IU_RUv_kF~BYI>$l8wew4GGSgyT&a-;--5pj_fn( zIU{Qa#>8fv;u;yU=i^Bu>xp8lOLB>^S#L6mHl<_HJcc^Ig!5wD8=ah7jI>r$MdBDc zvq|wr)8b7FG3=Q{G-kMfPOTG(H)bjptqYY&tVD9~pAUfIVqUMiWkP zjSMy90AEp2Eg6rwCZaiGA`0@zrDHK8>jJjsIqt@TSwjR}xqLJm0j>CA>CAj&G94@M zXzpREisV2cM#`6)ivs|Wi2}MQrA6aOTMU(Or6Nf;~S|NKQDWjAS%n*!W_fF2pAd+l-OO*iyz^ z&X${rXYBcGAvWurh{xsuwuVerFwN++?Dj$`@0deJ8F^bQ9Z%Vl0K!QHUp)GkNoAu%Ub;17@DZ`*~v`ot}*hF9nC}Q%I3x zMxBs=YseZgBYp&L`!n$vz`KYxZKuWMz~s&qfQ>-x$dIcVv?0@^*O-jwtLW9ZR6ryj zo%T&e^HDI2XktDW&oyM?jHm#JTil;WGyY)g<2HDb(dl>$qZ=4Js+rE>CdMgr0YK0o z(ldhCC$fdyj1ydB5}4samqYYTL|wU9JO`GX^BzH;r_ozr7Ey=f&(3H*pN+=yPIPA+ zL!9fw>2#87>=c1`3P={s#%3JyEpI+LYeeUw^AYauLWbG@u#34bhZ+QsV;GWw32}(X zS#OniBj7nmCn>q98jz%EO!}5?fa3j&G0C`=;*y4)3(6xbubjFM!pB0rw6ftdIuaC78`*;f*LIGsWmZ$cJhE}M%K(BQ>, <<_::bytes-size(4)>>] = codes + end +end diff --git a/test/mfa/totp_test.exs b/test/mfa/totp_test.exs new file mode 100644 index 000000000..50153d208 --- /dev/null +++ b/test/mfa/totp_test.exs @@ -0,0 +1,17 @@ +defmodule Pleroma.MFA.TOTPTest do + use Pleroma.DataCase + + alias Pleroma.MFA.TOTP + + test "create provisioning_uri to generate qrcode" do + uri = + TOTP.provisioning_uri("test-secrcet", "test@example.com", + issuer: "Plerome-42", + digits: 8, + period: 60 + ) + + assert uri == + "otpauth://totp/test@example.com?digits=8&issuer=Plerome-42&period=60&secret=test-secrcet" + end +end diff --git a/test/mfa_test.exs b/test/mfa_test.exs new file mode 100644 index 000000000..94bc48c26 --- /dev/null +++ b/test/mfa_test.exs @@ -0,0 +1,53 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MFATest do + use Pleroma.DataCase + + import Pleroma.Factory + alias Comeonin.Pbkdf2 + alias Pleroma.MFA + + describe "mfa_settings" do + test "returns settings user's" do + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: "xx", confirmed: true} + } + ) + + settings = MFA.mfa_settings(user) + assert match?(^settings, %{enabled: true, totp: true}) + end + end + + describe "generate backup codes" do + test "returns backup codes" do + user = insert(:user) + + {:ok, [code1, code2]} = MFA.generate_backup_codes(user) + updated_user = refresh_record(user) + [hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes + assert Pbkdf2.checkpw(code1, hash1) + assert Pbkdf2.checkpw(code2, hash2) + end + end + + describe "invalidate_backup_code" do + test "invalid used code" do + user = insert(:user) + + {:ok, _} = MFA.generate_backup_codes(user) + user = refresh_record(user) + assert length(user.multi_factor_authentication_settings.backup_codes) == 2 + [hash_code | _] = user.multi_factor_authentication_settings.backup_codes + + {:ok, user} = MFA.invalidate_backup_code(user, hash_code) + + assert length(user.multi_factor_authentication_settings.backup_codes) == 1 + end + end +end diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs index 4e6142aab..a0667c5e0 100644 --- a/test/plugs/ensure_authenticated_plug_test.exs +++ b/test/plugs/ensure_authenticated_plug_test.exs @@ -24,6 +24,31 @@ test "it continues if a user is assigned", %{conn: conn} do end end + test "it halts if user is assigned and MFA enabled", %{conn: conn} do + conn = + conn + |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: true}}) + |> assign(:auth_credentials, %{password: "xd-42"}) + |> EnsureAuthenticatedPlug.call(%{}) + + assert conn.status == 403 + assert conn.halted == true + + assert conn.resp_body == + "{\"error\":\"Two-factor authentication enabled, you must use a access token.\"}" + end + + test "it continues if user is assigned and MFA disabled", %{conn: conn} do + conn = + conn + |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: false}}) + |> assign(:auth_credentials, %{password: "xd-42"}) + |> EnsureAuthenticatedPlug.call(%{}) + + refute conn.status == 403 + refute conn.halted + end + describe "with :if_func / :unless_func options" do setup do %{ diff --git a/test/support/builders/user_builder.ex b/test/support/builders/user_builder.ex index fcfea666f..0d0490714 100644 --- a/test/support/builders/user_builder.ex +++ b/test/support/builders/user_builder.ex @@ -11,6 +11,7 @@ def build(data \\ %{}) do bio: "A tester.", ap_id: "some id", last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second), + multi_factor_authentication_settings: %Pleroma.MFA.Settings{}, notification_settings: %Pleroma.User.NotificationSetting{} } diff --git a/test/support/factory.ex b/test/support/factory.ex index 495764782..c8c45e2a7 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -33,7 +33,8 @@ def user_factory do bio: sequence(:bio, &"Tester Number #{&1}"), last_digest_emailed_at: NaiveDateTime.utc_now(), last_refreshed_at: NaiveDateTime.utc_now(), - notification_settings: %Pleroma.User.NotificationSetting{} + notification_settings: %Pleroma.User.NotificationSetting{}, + multi_factor_authentication_settings: %Pleroma.MFA.Settings{} } %{ @@ -422,4 +423,13 @@ def marker_factory do last_read_id: "1" } end + + def mfa_token_factory do + %Pleroma.MFA.Token{ + token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false), + authorization: build(:oauth_authorization), + valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10), + user: build(:user) + } + end end diff --git a/test/user_search_test.exs b/test/user_search_test.exs index cb847b516..17c63322a 100644 --- a/test/user_search_test.exs +++ b/test/user_search_test.exs @@ -172,6 +172,7 @@ test "works with URIs" do |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil) |> Map.put(:last_digest_emailed_at, nil) + |> Map.put(:multi_factor_authentication_settings, nil) |> Map.put(:notification_settings, nil) assert user == expected diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 7ab7cc15c..4697af50e 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -14,6 +14,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do alias Pleroma.Config alias Pleroma.ConfigDB alias Pleroma.HTML + alias Pleroma.MFA alias Pleroma.ModerationLog alias Pleroma.Repo alias Pleroma.ReportNote @@ -1278,6 +1279,38 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admi "@#{admin.nickname} deactivated users: @#{user.nickname}" end + describe "PUT disable_mfa" do + test "returns 200 and disable 2fa", %{conn: conn} do + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} + } + ) + + response = + conn + |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname}) + |> json_response(200) + + assert response == user.nickname + mfa_settings = refresh_record(user).multi_factor_authentication_settings + + refute mfa_settings.enabled + refute mfa_settings.totp.confirmed + end + + test "returns 404 if user not found", %{conn: conn} do + response = + conn + |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"}) + |> json_response(404) + + assert response == "Not found" + end + end + describe "POST /api/pleroma/admin/users/invite_token" do test "without options", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/invite_token") diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/web/auth/pleroma_authenticator_test.exs new file mode 100644 index 000000000..7125c5081 --- /dev/null +++ b/test/web/auth/pleroma_authenticator_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.Auth.PleromaAuthenticator + import Pleroma.Factory + + setup do + password = "testpassword" + name = "AgentSmith" + user = insert(:user, nickname: name, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) + {:ok, [user: user, name: name, password: password]} + end + + test "get_user/authorization", %{user: user, name: name, password: password} do + params = %{"authorization" => %{"name" => name, "password" => password}} + res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + + assert {:ok, user} == res + end + + test "get_user/authorization with invalid password", %{name: name} do + params = %{"authorization" => %{"name" => name, "password" => "password"}} + res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + + assert {:error, {:checkpw, false}} == res + end + + test "get_user/grant_type_password", %{user: user, name: name, password: password} do + params = %{"grant_type" => "password", "username" => name, "password" => password} + res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + + assert {:ok, user} == res + end + + test "error credintails" do + res = PleromaAuthenticator.get_user(%Plug.Conn{params: %{}}) + assert {:error, :invalid_credentials} == res + end +end diff --git a/test/web/auth/totp_authenticator_test.exs b/test/web/auth/totp_authenticator_test.exs new file mode 100644 index 000000000..e08069490 --- /dev/null +++ b/test/web/auth/totp_authenticator_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.TOTPAuthenticatorTest do + use Pleroma.Web.ConnCase + + alias Pleroma.MFA + alias Pleroma.MFA.BackupCodes + alias Pleroma.MFA.TOTP + alias Pleroma.Web.Auth.TOTPAuthenticator + + import Pleroma.Factory + + test "verify token" do + otp_secret = TOTP.generate_secret() + otp_token = TOTP.generate_token(otp_secret) + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + assert TOTPAuthenticator.verify(otp_token, user) == {:ok, :pass} + assert TOTPAuthenticator.verify(nil, user) == {:error, :invalid_token} + assert TOTPAuthenticator.verify("", user) == {:error, :invalid_token} + end + + test "checks backup codes" do + [code | _] = backup_codes = BackupCodes.generate() + + hashed_codes = + backup_codes + |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + backup_codes: hashed_codes, + totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} + } + ) + + assert TOTPAuthenticator.verify_recovery_code(user, code) == {:ok, :pass} + refute TOTPAuthenticator.verify_recovery_code(code, refresh_record(user)) == {:ok, :pass} + end +end diff --git a/test/web/oauth/mfa_controller_test.exs b/test/web/oauth/mfa_controller_test.exs new file mode 100644 index 000000000..ce4a07320 --- /dev/null +++ b/test/web/oauth/mfa_controller_test.exs @@ -0,0 +1,306 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAControllerTest do + use Pleroma.Web.ConnCase + import Pleroma.Factory + + alias Pleroma.MFA + alias Pleroma.MFA.BackupCodes + alias Pleroma.MFA.TOTP + alias Pleroma.Repo + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.OAuthController + + setup %{conn: conn} do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + backup_codes: [Comeonin.Pbkdf2.hashpwsalt("test-code")], + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + app = insert(:oauth_app) + {:ok, conn: conn, user: user, app: app} + end + + describe "show" do + setup %{conn: conn, user: user, app: app} do + mfa_token = + insert(:mfa_token, + user: user, + authorization: build(:oauth_authorization, app: app, scopes: ["write"]) + ) + + {:ok, conn: conn, mfa_token: mfa_token} + end + + test "GET /oauth/mfa renders mfa forms", %{conn: conn, mfa_token: mfa_token} do + conn = + get( + conn, + "/oauth/mfa", + %{ + "mfa_token" => mfa_token.token, + "state" => "a_state", + "redirect_uri" => "http://localhost:8080/callback" + } + ) + + assert response = html_response(conn, 200) + assert response =~ "Two-factor authentication" + assert response =~ mfa_token.token + assert response =~ "http://localhost:8080/callback" + end + + test "GET /oauth/mfa renders mfa recovery forms", %{conn: conn, mfa_token: mfa_token} do + conn = + get( + conn, + "/oauth/mfa", + %{ + "mfa_token" => mfa_token.token, + "state" => "a_state", + "redirect_uri" => "http://localhost:8080/callback", + "challenge_type" => "recovery" + } + ) + + assert response = html_response(conn, 200) + assert response =~ "Two-factor recovery" + assert response =~ mfa_token.token + assert response =~ "http://localhost:8080/callback" + end + end + + describe "verify" do + setup %{conn: conn, user: user, app: app} do + mfa_token = + insert(:mfa_token, + user: user, + authorization: build(:oauth_authorization, app: app, scopes: ["write"]) + ) + + {:ok, conn: conn, user: user, mfa_token: mfa_token, app: app} + end + + test "POST /oauth/mfa/verify, verify totp code", %{ + conn: conn, + user: user, + mfa_token: mfa_token, + app: app + } do + otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + + conn = + conn + |> post("/oauth/mfa/verify", %{ + "mfa" => %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "totp", + "code" => otp_token, + "state" => "a_state", + "redirect_uri" => OAuthController.default_redirect_uri(app) + } + }) + + target = redirected_to(conn) + target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + assert %{"state" => "a_state", "code" => code} = query + assert target_url == OAuthController.default_redirect_uri(app) + auth = Repo.get_by(Authorization, token: code) + assert auth.scopes == ["write"] + end + + test "POST /oauth/mfa/verify, verify recovery code", %{ + conn: conn, + mfa_token: mfa_token, + app: app + } do + conn = + conn + |> post("/oauth/mfa/verify", %{ + "mfa" => %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "recovery", + "code" => "test-code", + "state" => "a_state", + "redirect_uri" => OAuthController.default_redirect_uri(app) + } + }) + + target = redirected_to(conn) + target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + assert %{"state" => "a_state", "code" => code} = query + assert target_url == OAuthController.default_redirect_uri(app) + auth = Repo.get_by(Authorization, token: code) + assert auth.scopes == ["write"] + end + end + + describe "challenge/totp" do + test "returns access token with valid code", %{conn: conn, user: user, app: app} do + otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + + mfa_token = + insert(:mfa_token, + user: user, + authorization: build(:oauth_authorization, app: app, scopes: ["write"]) + ) + + response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "totp", + "code" => otp_token, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(:ok) + + ap_id = user.ap_id + + assert match?( + %{ + "access_token" => _, + "expires_in" => 600, + "me" => ^ap_id, + "refresh_token" => _, + "scope" => "write", + "token_type" => "Bearer" + }, + response + ) + end + + test "returns errors when mfa token invalid", %{conn: conn, user: user, app: app} do + otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + + response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => "XXX", + "challenge_type" => "totp", + "code" => otp_token, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(400) + + assert response == %{"error" => "Invalid code"} + end + + test "returns error when otp code is invalid", %{conn: conn, user: user, app: app} do + mfa_token = insert(:mfa_token, user: user) + + response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "totp", + "code" => "XXX", + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(400) + + assert response == %{"error" => "Invalid code"} + end + + test "returns error when client credentails is wrong ", %{conn: conn, user: user} do + otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + mfa_token = insert(:mfa_token, user: user) + + response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "totp", + "code" => otp_token, + "client_id" => "xxx", + "client_secret" => "xxx" + }) + |> json_response(400) + + assert response == %{"error" => "Invalid code"} + end + end + + describe "challenge/recovery" do + setup %{conn: conn} do + app = insert(:oauth_app) + {:ok, conn: conn, app: app} + end + + test "returns access token with valid code", %{conn: conn, app: app} do + otp_secret = TOTP.generate_secret() + + [code | _] = backup_codes = BackupCodes.generate() + + hashed_codes = + backup_codes + |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + backup_codes: hashed_codes, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + mfa_token = + insert(:mfa_token, + user: user, + authorization: build(:oauth_authorization, app: app, scopes: ["write"]) + ) + + response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "recovery", + "code" => code, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(:ok) + + ap_id = user.ap_id + + assert match?( + %{ + "access_token" => _, + "expires_in" => 600, + "me" => ^ap_id, + "refresh_token" => _, + "scope" => "write", + "token_type" => "Bearer" + }, + response + ) + + error_response = + conn + |> post("/oauth/mfa/challenge", %{ + "mfa_token" => mfa_token.token, + "challenge_type" => "recovery", + "code" => code, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(400) + + assert error_response == %{"error" => "Invalid code"} + end + end +end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index f2f98d768..7a107584d 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -6,6 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory + alias Pleroma.MFA + alias Pleroma.MFA.TOTP alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Authorization @@ -604,6 +606,41 @@ test "redirects with oauth authorization, " <> end end + test "redirect to on two-factor auth page" do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + + conn = + build_conn() + |> post("/oauth/authorize", %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => app.redirect_uris, + "scope" => "read write", + "state" => "statepassed" + } + }) + + result = html_response(conn, 200) + + mfa_token = Repo.get_by(MFA.Token, user_id: user.id) + assert result =~ app.redirect_uris + assert result =~ "statepassed" + assert result =~ mfa_token.token + assert result =~ "Two-factor authentication" + end + test "returns 401 for wrong credentials", %{conn: conn} do user = insert(:user) app = insert(:oauth_app) @@ -735,6 +772,46 @@ test "issues a token for `password` grant_type with valid credentials, with full assert token.scopes == app.scopes end + test "issues a mfa token for `password` grant_type, when MFA enabled" do + password = "testpassword" + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + password_hash: Comeonin.Pbkdf2.hashpwsalt(password), + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + app = insert(:oauth_app, scopes: ["read", "write"]) + + response = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(403) + + assert match?( + %{ + "supported_challenge_types" => "totp", + "mfa_token" => _, + "error" => "mfa_required" + }, + response + ) + + token = Repo.get_by(MFA.Token, token: response["mfa_token"]) + assert token.user_id == user.id + assert token.authorization_id + end + test "issues a token for request with HTTP basic auth client credentials" do user = insert(:user) app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"]) diff --git a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs new file mode 100644 index 000000000..d23d08a00 --- /dev/null +++ b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs @@ -0,0 +1,260 @@ +defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + alias Pleroma.MFA.Settings + alias Pleroma.MFA.TOTP + + describe "GET /api/pleroma/accounts/mfa/settings" do + test "returns user mfa settings for new user", %{conn: conn} do + token = insert(:oauth_token, scopes: ["read", "follow"]) + token2 = insert(:oauth_token, scopes: ["write"]) + + assert conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> get("/api/pleroma/accounts/mfa") + |> json_response(:ok) == %{ + "settings" => %{"enabled" => false, "totp" => false} + } + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> get("/api/pleroma/accounts/mfa") + |> json_response(403) == %{ + "error" => "Insufficient permissions: read:security." + } + end + + test "returns user mfa settings with enabled totp", %{conn: conn} do + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + enabled: true, + totp: %Settings.TOTP{secret: "XXX", delivery_type: "app", confirmed: true} + } + ) + + token = insert(:oauth_token, scopes: ["read", "follow"], user: user) + + assert conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> get("/api/pleroma/accounts/mfa") + |> json_response(:ok) == %{ + "settings" => %{"enabled" => true, "totp" => true} + } + end + end + + describe "GET /api/pleroma/accounts/mfa/backup_codes" do + test "returns backup codes", %{conn: conn} do + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + backup_codes: ["1", "2", "3"], + totp: %Settings.TOTP{secret: "secret"} + } + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + token2 = insert(:oauth_token, scopes: ["read"]) + + response = + conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> get("/api/pleroma/accounts/mfa/backup_codes") + |> json_response(:ok) + + assert [<<_::bytes-size(6)>>, <<_::bytes-size(6)>>] = response["codes"] + user = refresh_record(user) + mfa_settings = user.multi_factor_authentication_settings + assert mfa_settings.totp.secret == "secret" + refute mfa_settings.backup_codes == ["1", "2", "3"] + refute mfa_settings.backup_codes == [] + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> get("/api/pleroma/accounts/mfa/backup_codes") + |> json_response(403) == %{ + "error" => "Insufficient permissions: write:security." + } + end + end + + describe "GET /api/pleroma/accounts/mfa/setup/totp" do + test "return errors when method is invalid", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + + response = + conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> get("/api/pleroma/accounts/mfa/setup/torf") + |> json_response(400) + + assert response == %{"error" => "undefined method"} + end + + test "returns key and provisioning_uri", %{conn: conn} do + user = + insert(:user, + multi_factor_authentication_settings: %Settings{backup_codes: ["1", "2", "3"]} + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + token2 = insert(:oauth_token, scopes: ["read"]) + + response = + conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> get("/api/pleroma/accounts/mfa/setup/totp") + |> json_response(:ok) + + user = refresh_record(user) + mfa_settings = user.multi_factor_authentication_settings + secret = mfa_settings.totp.secret + refute mfa_settings.enabled + assert mfa_settings.backup_codes == ["1", "2", "3"] + + assert response == %{ + "key" => secret, + "provisioning_uri" => TOTP.provisioning_uri(secret, "#{user.email}") + } + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> get("/api/pleroma/accounts/mfa/setup/totp") + |> json_response(403) == %{ + "error" => "Insufficient permissions: write:security." + } + end + end + + describe "GET /api/pleroma/accounts/mfa/confirm/totp" do + test "returns success result", %{conn: conn} do + secret = TOTP.generate_secret() + code = TOTP.generate_token(secret) + + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + backup_codes: ["1", "2", "3"], + totp: %Settings.TOTP{secret: secret} + } + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + token2 = insert(:oauth_token, scopes: ["read"]) + + assert conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) + |> json_response(:ok) + + settings = refresh_record(user).multi_factor_authentication_settings + assert settings.enabled + assert settings.totp.secret == secret + assert settings.totp.confirmed + assert settings.backup_codes == ["1", "2", "3"] + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) + |> json_response(403) == %{ + "error" => "Insufficient permissions: write:security." + } + end + + test "returns error if password incorrect", %{conn: conn} do + secret = TOTP.generate_secret() + code = TOTP.generate_token(secret) + + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + backup_codes: ["1", "2", "3"], + totp: %Settings.TOTP{secret: secret} + } + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + + response = + conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "xxx", code: code}) + |> json_response(422) + + settings = refresh_record(user).multi_factor_authentication_settings + refute settings.enabled + refute settings.totp.confirmed + assert settings.backup_codes == ["1", "2", "3"] + assert response == %{"error" => "Invalid password."} + end + + test "returns error if code incorrect", %{conn: conn} do + secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + backup_codes: ["1", "2", "3"], + totp: %Settings.TOTP{secret: secret} + } + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + token2 = insert(:oauth_token, scopes: ["read"]) + + response = + conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) + |> json_response(422) + + settings = refresh_record(user).multi_factor_authentication_settings + refute settings.enabled + refute settings.totp.confirmed + assert settings.backup_codes == ["1", "2", "3"] + assert response == %{"error" => "invalid_token"} + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) + |> json_response(403) == %{ + "error" => "Insufficient permissions: write:security." + } + end + end + + describe "DELETE /api/pleroma/accounts/mfa/totp" do + test "returns success result", %{conn: conn} do + user = + insert(:user, + multi_factor_authentication_settings: %Settings{ + backup_codes: ["1", "2", "3"], + totp: %Settings.TOTP{secret: "secret"} + } + ) + + token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + token2 = insert(:oauth_token, scopes: ["read"]) + + assert conn + |> put_req_header("authorization", "Bearer #{token.token}") + |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) + |> json_response(:ok) + + settings = refresh_record(user).multi_factor_authentication_settings + refute settings.enabled + assert settings.totp.secret == nil + refute settings.totp.confirmed + + assert conn + |> put_req_header("authorization", "Bearer #{token2.token}") + |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) + |> json_response(403) == %{ + "error" => "Insufficient permissions: write:security." + } + end + end +end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index 5ff8694a8..f7e54c26a 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -6,11 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Config + alias Pleroma.MFA + alias Pleroma.MFA.TOTP alias Pleroma.User alias Pleroma.Web.CommonAPI import ExUnit.CaptureLog import Pleroma.Factory + import Ecto.Query setup do Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -160,6 +163,119 @@ test "returns success result when user already in followers", %{conn: conn} do end end + describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do + test "render the MFA login form", %{conn: conn} do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + user2 = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + |> response(200) + + mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id)) + + assert response =~ "Two-factor authentication" + assert response =~ "Authentication code" + assert response =~ mfa_token.token + refute user2.follower_address in User.following(user) + end + + test "returns error when password is incorrect", %{conn: conn} do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + user2 = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Wrong username or password" + refute user2.follower_address in User.following(user) + end + + test "follows", %{conn: conn} do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + {:ok, %{token: token}} = MFA.Token.create_token(user) + + user2 = insert(:user) + otp_token = TOTP.generate_token(otp_secret) + + conn = + conn + |> post( + remote_follow_path(conn, :do_follow), + %{ + "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} + } + ) + + assert redirected_to(conn) == "/users/#{user2.id}" + assert user2.follower_address in User.following(user) + end + + test "returns error when auth code is incorrect", %{conn: conn} do + otp_secret = TOTP.generate_secret() + + user = + insert(:user, + multi_factor_authentication_settings: %MFA.Settings{ + enabled: true, + totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} + } + ) + + {:ok, %{token: token}} = MFA.Token.create_token(user) + + user2 = insert(:user) + otp_token = TOTP.generate_token(TOTP.generate_secret()) + + response = + conn + |> post( + remote_follow_path(conn, :do_follow), + %{ + "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} + } + ) + |> response(200) + + assert response =~ "Wrong authentication code" + refute user2.follower_address in User.following(user) + end + end + describe "POST /ostatus_subscribe - follow/2 without assigned user " do test "follows", %{conn: conn} do user = insert(:user) From 9491ba3e49450e80cd1c21358c01e4e06e3d881d Mon Sep 17 00:00:00 2001 From: href Date: Thu, 7 May 2020 09:13:32 +0000 Subject: [PATCH 098/337] Streamer rework --- lib/pleroma/application.ex | 9 +- lib/pleroma/web/activity_pub/activity_pub.ex | 32 +- .../web/mastodon_api/websocket_handler.ex | 47 +- lib/pleroma/web/streamer/ping.ex | 37 -- lib/pleroma/web/streamer/state.ex | 82 --- lib/pleroma/web/streamer/streamer.ex | 250 ++++++- lib/pleroma/web/streamer/streamer_socket.ex | 35 - lib/pleroma/web/streamer/supervisor.ex | 37 -- lib/pleroma/web/streamer/worker.ex | 208 ------ test/integration/mastodon_websocket_test.exs | 7 +- test/notification_test.exs | 18 +- test/support/builders/activity_builder.ex | 10 +- test/support/conn_case.ex | 6 +- test/support/data_case.ex | 6 +- test/web/streamer/ping_test.exs | 36 - test/web/streamer/state_test.exs | 54 -- test/web/streamer/streamer_test.exs | 618 +++++++----------- 17 files changed, 535 insertions(+), 957 deletions(-) delete mode 100644 lib/pleroma/web/streamer/ping.ex delete mode 100644 lib/pleroma/web/streamer/state.ex delete mode 100644 lib/pleroma/web/streamer/streamer_socket.ex delete mode 100644 lib/pleroma/web/streamer/supervisor.ex delete mode 100644 lib/pleroma/web/streamer/worker.ex delete mode 100644 test/web/streamer/ping_test.exs delete mode 100644 test/web/streamer/state_test.exs diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 308d8cffa..a00bc0624 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -173,7 +173,14 @@ defp chat_enabled?, do: Config.get([:chat, :enabled]) defp streamer_child(env) when env in [:test, :benchmark], do: [] defp streamer_child(_) do - [Pleroma.Web.Streamer.supervisor()] + [ + {Registry, + [ + name: Pleroma.Web.Streamer.registry(), + keys: :duplicate, + partitions: System.schedulers_online() + ]} + ] end defp chat_child(_env, true) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 099df5879..8baaf97ac 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -170,12 +170,6 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) - Notification.create_notifications(activity) - - conversation = create_or_bump_conversation(activity, map["actor"]) - participations = get_participations(conversation) - stream_out(activity) - stream_out_participations(participations) {:ok, activity} else %Activity{} = activity -> @@ -198,6 +192,15 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when end end + def notify_and_stream(activity) do + Notification.create_notifications(activity) + + conversation = create_or_bump_conversation(activity, activity.actor) + participations = get_participations(conversation) + stream_out(activity) + stream_out_participations(participations) + end + defp create_or_bump_conversation(activity, actor) do with {:ok, conversation} <- Conversation.create_or_bump_for(activity), %User{} = user <- User.get_cached_by_ap_id(actor), @@ -274,6 +277,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param _ <- increase_poll_votes_if_vote(create_data), {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, {:ok, _actor} <- increase_note_count_if_public(actor, activity), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -301,6 +305,7 @@ def listen(%{to: to, actor: actor, context: context, object: object} = params) d additional ), {:ok, activity} <- insert(listen_data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} end @@ -325,6 +330,7 @@ def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object} |> Utils.maybe_put("id", activity_id), {:ok, activity} <- insert(data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} end @@ -344,6 +350,7 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do }, data <- Utils.maybe_put(data, "id", activity_id), {:ok, activity} <- insert(data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} end @@ -365,6 +372,7 @@ defp do_react_with_emoji(user, object, emoji, options) do reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), {:ok, activity} <- insert(reaction_data, local), {:ok, object} <- add_emoji_reaction_to_object(activity, object), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity, object} else @@ -391,6 +399,7 @@ defp do_unreact_with_emoji(user, reaction_id, options) do unreact_data <- make_undo_data(user, reaction_activity, activity_id), {:ok, activity} <- insert(unreact_data, local), {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity, object} else @@ -413,6 +422,7 @@ defp do_unlike(actor, object, activity_id, local) do {:ok, unlike_activity} <- insert(unlike_data, local), {:ok, _activity} <- Repo.delete(like_activity), {:ok, object} <- remove_like_from_object(like_activity, object), + _ <- notify_and_stream(unlike_activity), :ok <- maybe_federate(unlike_activity) do {:ok, unlike_activity, like_activity, object} else @@ -442,6 +452,7 @@ defp do_announce(user, object, activity_id, local, public) do announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity, object} else @@ -468,6 +479,7 @@ defp do_unannounce(actor, object, activity_id, local) do with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), {:ok, unannounce_activity} <- insert(unannounce_data, local), + _ <- notify_and_stream(unannounce_activity), :ok <- maybe_federate(unannounce_activity), {:ok, _activity} <- Repo.delete(announce_activity), {:ok, object} <- remove_announce_from_object(announce_activity, object) do @@ -490,6 +502,7 @@ def follow(follower, followed, activity_id \\ nil, local \\ true) do defp do_follow(follower, followed, activity_id, local) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -511,6 +524,7 @@ defp do_unfollow(follower, followed, activity_id, local) do {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), {:ok, activity} <- insert(unfollow_data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -540,6 +554,7 @@ defp do_block(blocker, blocked, activity_id, local) do with true <- outgoing_blocks, block_data <- make_block_data(blocker, blocked, activity_id), {:ok, activity} <- insert(block_data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -560,6 +575,7 @@ defp do_unblock(blocker, blocked, activity_id, local) do with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked), unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id), {:ok, activity} <- insert(unblock_data, local), + _ <- notify_and_stream(activity), :ok <- maybe_federate(activity) do {:ok, activity} else @@ -594,6 +610,7 @@ def flag( with flag_data <- make_flag_data(params, additional), {:ok, activity} <- insert(flag_data, local), {:ok, stripped_activity} <- strip_report_status_data(activity), + _ <- notify_and_stream(activity), :ok <- maybe_federate(stripped_activity) do User.all_superusers() |> Enum.filter(fn user -> not is_nil(user.email) end) @@ -617,7 +634,8 @@ def move(%User{} = origin, %User{} = target, local \\ true) do } with true <- origin.ap_id in target.also_known_as, - {:ok, activity} <- insert(params, local) do + {:ok, activity} <- insert(params, local), + _ <- notify_and_stream(activity) do maybe_federate(activity) BackgroundWorker.enqueue("move_following", %{ diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 5652a37c1..6ef3fe2dd 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -12,6 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do @behaviour :cowboy_websocket + # Cowboy timeout period. + @timeout :timer.seconds(30) + # Hibernate every X messages + @hibernate_every 100 + @streams [ "public", "public:local", @@ -25,9 +30,6 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do ] @anonymous_streams ["public", "public:local", "hashtag"] - # Handled by periodic keepalive in Pleroma.Web.Streamer.Ping. - @timeout :infinity - def init(%{qs: qs} = req, state) do with params <- :cow_qs.parse_qs(qs), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), @@ -42,7 +44,7 @@ def init(%{qs: qs} = req, state) do req end - {:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}} + {:cowboy_websocket, req, %{user: user, topic: topic, count: 0}, %{idle_timeout: @timeout}} else {:error, code} -> Logger.debug("#{__MODULE__} denied connection: #{inspect(code)} - #{inspect(req)}") @@ -57,7 +59,13 @@ def init(%{qs: qs} = req, state) do end def websocket_init(state) do - send(self(), :subscribe) + Logger.debug( + "#{__MODULE__} accepted websocket connection for user #{ + (state.user || %{id: "anonymous"}).id + }, topic #{state.topic}" + ) + + Streamer.add_socket(state.topic, state.user) {:ok, state} end @@ -66,19 +74,24 @@ def websocket_handle(_frame, state) do {:ok, state} end - def websocket_info(:subscribe, state) do - Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{ - (state.user || %{id: "anonymous"}).id - }, topic #{state.topic}" - ) + def websocket_info({:render_with_user, view, template, item}, state) do + user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) - Streamer.add_socket(state.topic, streamer_socket(state)) - {:ok, state} + unless Streamer.filtered_by_user?(user, item) do + websocket_info({:text, view.render(template, user, item)}, %{state | user: user}) + else + {:ok, state} + end end def websocket_info({:text, message}, state) do - {:reply, {:text, message}, state} + # If the websocket processed X messages, force an hibernate/GC. + # We don't hibernate at every message to balance CPU usage/latency with RAM usage. + if state.count > @hibernate_every do + {:reply, {:text, message}, %{state | count: 0}, :hibernate} + else + {:reply, {:text, message}, %{state | count: state.count + 1}} + end end def terminate(reason, _req, state) do @@ -88,7 +101,7 @@ def terminate(reason, _req, state) do }, topic #{state.topic || "?"}: #{inspect(reason)}" ) - Streamer.remove_socket(state.topic, streamer_socket(state)) + Streamer.remove_socket(state.topic) :ok end @@ -136,8 +149,4 @@ defp expand_topic("list", params) do end defp expand_topic(topic, _), do: topic - - defp streamer_socket(state) do - %{transport_pid: self(), assigns: state} - end end diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex deleted file mode 100644 index 7a08202a9..000000000 --- a/lib/pleroma/web/streamer/ping.ex +++ /dev/null @@ -1,37 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer.Ping do - use GenServer - require Logger - - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.StreamerSocket - - @keepalive_interval :timer.seconds(30) - - def start_link(opts) do - ping_interval = Keyword.get(opts, :ping_interval, @keepalive_interval) - GenServer.start_link(__MODULE__, %{ping_interval: ping_interval}, name: __MODULE__) - end - - def init(%{ping_interval: ping_interval} = args) do - Process.send_after(self(), :ping, ping_interval) - {:ok, args} - end - - def handle_info(:ping, %{ping_interval: ping_interval} = state) do - State.get_sockets() - |> Map.values() - |> List.flatten() - |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid} -> - Logger.debug("Sending keepalive ping") - send(transport_pid, {:text, ""}) - end) - - Process.send_after(self(), :ping, ping_interval) - - {:noreply, state} - end -end diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex deleted file mode 100644 index 999550b88..000000000 --- a/lib/pleroma/web/streamer/state.ex +++ /dev/null @@ -1,82 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer.State do - use GenServer - require Logger - - alias Pleroma.Web.Streamer.StreamerSocket - - @env Mix.env() - - def start_link(_) do - GenServer.start_link(__MODULE__, %{sockets: %{}}, name: __MODULE__) - end - - def add_socket(topic, socket) do - GenServer.call(__MODULE__, {:add, topic, socket}) - end - - def remove_socket(topic, socket) do - do_remove_socket(@env, topic, socket) - end - - def get_sockets do - %{sockets: stream_sockets} = GenServer.call(__MODULE__, :get_state) - stream_sockets - end - - def init(init_arg) do - {:ok, init_arg} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - def handle_call({:add, topic, socket}, _from, %{sockets: sockets} = state) do - internal_topic = internal_topic(topic, socket) - stream_socket = StreamerSocket.from_socket(socket) - - sockets_for_topic = - sockets - |> Map.get(internal_topic, []) - |> List.insert_at(0, stream_socket) - |> Enum.uniq() - - state = put_in(state, [:sockets, internal_topic], sockets_for_topic) - Logger.debug("Got new conn for #{topic}") - {:reply, state, state} - end - - def handle_call({:remove, topic, socket}, _from, %{sockets: sockets} = state) do - internal_topic = internal_topic(topic, socket) - stream_socket = StreamerSocket.from_socket(socket) - - sockets_for_topic = - sockets - |> Map.get(internal_topic, []) - |> List.delete(stream_socket) - - state = Kernel.put_in(state, [:sockets, internal_topic], sockets_for_topic) - {:reply, state, state} - end - - defp do_remove_socket(:test, _, _) do - :ok - end - - defp do_remove_socket(_env, topic, socket) do - GenServer.call(__MODULE__, {:remove, topic, socket}) - end - - defp internal_topic(topic, socket) - when topic in ~w[user user:notification direct] do - "#{topic}:#{socket.assigns[:user].id}" - end - - defp internal_topic(topic, _) do - topic - end -end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex index 814d5a729..5ad4aa936 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer/streamer.ex @@ -3,53 +3,241 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer do - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.Worker + require Logger + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.StreamerView - @timeout 60_000 @mix_env Mix.env() + @registry Pleroma.Web.StreamerRegistry - def add_socket(topic, socket) do - State.add_socket(topic, socket) + def registry, do: @registry + + def add_socket(topic, %User{} = user) do + if should_env_send?(), do: Registry.register(@registry, user_topic(topic, user), true) end - def remove_socket(topic, socket) do - State.remove_socket(topic, socket) + def add_socket(topic, _) do + if should_env_send?(), do: Registry.register(@registry, topic, false) end - def get_sockets do - State.get_sockets() + def remove_socket(topic) do + if should_env_send?(), do: Registry.unregister(@registry, topic) end - def stream(topics, items) do - if should_send?() do - Task.async(fn -> - :poolboy.transaction( - :streamer_worker, - &Worker.stream(&1, topics, items), - @timeout - ) + def stream(topics, item) when is_list(topics) do + if should_env_send?() do + Enum.each(topics, fn t -> + spawn(fn -> do_stream(t, item) end) end) end + + :ok end - def supervisor, do: Pleroma.Web.Streamer.Supervisor + def stream(topic, items) when is_list(items) do + if should_env_send?() do + Enum.each(items, fn i -> + spawn(fn -> do_stream(topic, i) end) + end) - defp should_send? do - handle_should_send(@mix_env) - end - - defp handle_should_send(:test) do - case Process.whereis(:streamer_worker) do - nil -> - false - - pid -> - Process.alive?(pid) + :ok end end - defp handle_should_send(:benchmark), do: false + def stream(topic, item) do + if should_env_send?() do + spawn(fn -> do_stream(topic, item) end) + end - defp handle_should_send(_), do: true + :ok + end + + def filtered_by_user?(%User{} = user, %Activity{} = item) do + %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} = + User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute]) + + recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids) + recipients = MapSet.new(item.recipients) + domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) + + with parent <- Object.normalize(item) || item, + true <- + Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), + true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, + true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)), + true <- MapSet.disjoint?(recipients, recipient_blocks), + %{host: item_host} <- URI.parse(item.actor), + %{host: parent_host} <- URI.parse(parent.data["actor"]), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), + true <- thread_containment(item, user), + false <- CommonAPI.thread_muted?(user, item) do + false + else + _ -> true + end + end + + def filtered_by_user?(%User{} = user, %Notification{activity: activity}) do + filtered_by_user?(user, activity) + end + + defp do_stream("direct", item) do + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "direct:#{id}" end) + + Enum.each(recipient_topics, fn user_topic -> + Logger.debug("Trying to push direct message to #{user_topic}\n\n") + push_to_socket(user_topic, item) + end) + end + + defp do_stream("participation", participation) do + user_topic = "direct:#{participation.user_id}" + Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") + + push_to_socket(user_topic, participation) + end + + defp do_stream("list", item) do + # filter the recipient list if the activity is not public, see #270. + recipient_lists = + case Visibility.is_public?(item) do + true -> + Pleroma.List.get_lists_from_activity(item) + + _ -> + Pleroma.List.get_lists_from_activity(item) + |> Enum.filter(fn list -> + owner = User.get_cached_by_id(list.user_id) + + Visibility.visible_for_user?(item, owner) + end) + end + + recipient_topics = + recipient_lists + |> Enum.map(fn %{id: id} -> "list:#{id}" end) + + Enum.each(recipient_topics, fn list_topic -> + Logger.debug("Trying to push message to #{list_topic}\n\n") + push_to_socket(list_topic, item) + end) + end + + defp do_stream(topic, %Notification{} = item) + when topic in ["user", "user:notification"] do + Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list -> + Enum.each(list, fn {pid, _auth} -> + send(pid, {:render_with_user, StreamerView, "notification.json", item}) + end) + end) + end + + defp do_stream("user", item) do + Logger.debug("Trying to push to users") + + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "user:#{id}" end) + + Enum.each(recipient_topics, fn topic -> + push_to_socket(topic, item) + end) + end + + defp do_stream(topic, item) do + Logger.debug("Trying to push to #{topic}") + Logger.debug("Pushing item to #{topic}") + push_to_socket(topic, item) + end + + defp push_to_socket(topic, %Participation{} = participation) do + rendered = StreamerView.render("conversation.json", participation) + + Registry.dispatch(@registry, topic, fn list -> + Enum.each(list, fn {pid, _} -> + send(pid, {:text, rendered}) + end) + end) + end + + defp push_to_socket(topic, %Activity{ + data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} + }) do + rendered = Jason.encode!(%{event: "delete", payload: to_string(deleted_activity_id)}) + + Registry.dispatch(@registry, topic, fn list -> + Enum.each(list, fn {pid, _} -> + send(pid, {:text, rendered}) + end) + end) + end + + defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop + + defp push_to_socket(topic, item) do + anon_render = StreamerView.render("update.json", item) + + Registry.dispatch(@registry, topic, fn list -> + Enum.each(list, fn {pid, auth?} -> + if auth? do + send(pid, {:render_with_user, StreamerView, "update.json", item}) + else + send(pid, {:text, anon_render}) + end + end) + end) + end + + defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true + + defp thread_containment(activity, user) do + if Config.get([:instance, :skip_thread_containment]) do + true + else + ActivityPub.contain_activity(activity, user) + end + end + + # In test environement, only return true if the registry is started. + # In benchmark environment, returns false. + # In any other environment, always returns true. + cond do + @mix_env == :test -> + def should_env_send? do + case Process.whereis(@registry) do + nil -> + false + + pid -> + Process.alive?(pid) + end + end + + @mix_env == :benchmark -> + def should_env_send?, do: false + + true -> + def should_env_send?, do: true + end + + defp user_topic(topic, user) + when topic in ~w[user user:notification direct] do + "#{topic}:#{user.id}" + end + + defp user_topic(topic, _) do + topic + end end diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex deleted file mode 100644 index 7d5dcd34e..000000000 --- a/lib/pleroma/web/streamer/streamer_socket.ex +++ /dev/null @@ -1,35 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer.StreamerSocket do - defstruct transport_pid: nil, user: nil - - alias Pleroma.User - alias Pleroma.Web.Streamer.StreamerSocket - - def from_socket(%{ - transport_pid: transport_pid, - assigns: %{user: nil} - }) do - %StreamerSocket{ - transport_pid: transport_pid - } - end - - def from_socket(%{ - transport_pid: transport_pid, - assigns: %{user: %User{} = user} - }) do - %StreamerSocket{ - transport_pid: transport_pid, - user: user - } - end - - def from_socket(%{transport_pid: transport_pid}) do - %StreamerSocket{ - transport_pid: transport_pid - } - end -end diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex deleted file mode 100644 index bd9029bc0..000000000 --- a/lib/pleroma/web/streamer/supervisor.ex +++ /dev/null @@ -1,37 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer.Supervisor do - use Supervisor - - def start_link(opts) do - Supervisor.start_link(__MODULE__, opts, name: __MODULE__) - end - - def init(args) do - children = [ - {Pleroma.Web.Streamer.State, args}, - {Pleroma.Web.Streamer.Ping, args}, - :poolboy.child_spec(:streamer_worker, poolboy_config()) - ] - - opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] - Supervisor.init(children, opts) - end - - defp poolboy_config do - opts = - Pleroma.Config.get(:streamer, - workers: 3, - overflow_workers: 2 - ) - - [ - {:name, {:local, :streamer_worker}}, - {:worker_module, Pleroma.Web.Streamer.Worker}, - {:size, opts[:workers]}, - {:max_overflow, opts[:overflow_workers]} - ] - end -end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex deleted file mode 100644 index f6160fa4d..000000000 --- a/lib/pleroma/web/streamer/worker.ex +++ /dev/null @@ -1,208 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer.Worker do - use GenServer - - require Logger - - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.StreamerSocket - alias Pleroma.Web.StreamerView - - def start_link(_) do - GenServer.start_link(__MODULE__, %{}, []) - end - - def init(init_arg) do - {:ok, init_arg} - end - - def stream(pid, topics, items) do - GenServer.call(pid, {:stream, topics, items}) - end - - def handle_call({:stream, topics, item}, _from, state) when is_list(topics) do - Enum.each(topics, fn t -> - do_stream(%{topic: t, item: item}) - end) - - {:reply, state, state} - end - - def handle_call({:stream, topic, items}, _from, state) when is_list(items) do - Enum.each(items, fn i -> - do_stream(%{topic: topic, item: i}) - end) - - {:reply, state, state} - end - - def handle_call({:stream, topic, item}, _from, state) do - do_stream(%{topic: topic, item: item}) - - {:reply, state, state} - end - - defp do_stream(%{topic: "direct", item: item}) do - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "direct:#{id}" end) - - Enum.each(recipient_topics, fn user_topic -> - Logger.debug("Trying to push direct message to #{user_topic}\n\n") - push_to_socket(State.get_sockets(), user_topic, item) - end) - end - - defp do_stream(%{topic: "participation", item: participation}) do - user_topic = "direct:#{participation.user_id}" - Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") - - push_to_socket(State.get_sockets(), user_topic, participation) - end - - defp do_stream(%{topic: "list", item: item}) do - # filter the recipient list if the activity is not public, see #270. - recipient_lists = - case Visibility.is_public?(item) do - true -> - Pleroma.List.get_lists_from_activity(item) - - _ -> - Pleroma.List.get_lists_from_activity(item) - |> Enum.filter(fn list -> - owner = User.get_cached_by_id(list.user_id) - - Visibility.visible_for_user?(item, owner) - end) - end - - recipient_topics = - recipient_lists - |> Enum.map(fn %{id: id} -> "list:#{id}" end) - - Enum.each(recipient_topics, fn list_topic -> - Logger.debug("Trying to push message to #{list_topic}\n\n") - push_to_socket(State.get_sockets(), list_topic, item) - end) - end - - defp do_stream(%{topic: topic, item: %Notification{} = item}) - when topic in ["user", "user:notification"] do - State.get_sockets() - |> Map.get("#{topic}:#{item.user_id}", []) - |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid, user: socket_user} -> - with %User{} = user <- User.get_cached_by_ap_id(socket_user.ap_id), - true <- should_send?(user, item) do - send(transport_pid, {:text, StreamerView.render("notification.json", socket_user, item)}) - end - end) - end - - defp do_stream(%{topic: "user", item: item}) do - Logger.debug("Trying to push to users") - - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "user:#{id}" end) - - Enum.each(recipient_topics, fn topic -> - push_to_socket(State.get_sockets(), topic, item) - end) - end - - defp do_stream(%{topic: topic, item: item}) do - Logger.debug("Trying to push to #{topic}") - Logger.debug("Pushing item to #{topic}") - push_to_socket(State.get_sockets(), topic, item) - end - - defp should_send?(%User{} = user, %Activity{} = item) do - %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} = - User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute]) - - recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids) - recipients = MapSet.new(item.recipients) - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) - - with parent <- Object.normalize(item) || item, - true <- - Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), - true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, - true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)), - true <- MapSet.disjoint?(recipients, recipient_blocks), - %{host: item_host} <- URI.parse(item.actor), - %{host: parent_host} <- URI.parse(parent.data["actor"]), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), - true <- thread_containment(item, user), - false <- CommonAPI.thread_muted?(user, item) do - true - else - _ -> false - end - end - - defp should_send?(%User{} = user, %Notification{activity: activity}) do - should_send?(user, activity) - end - - def push_to_socket(topics, topic, %Participation{} = participation) do - Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> - send(transport_pid, {:text, StreamerView.render("conversation.json", participation)}) - end) - end - - def push_to_socket(topics, topic, %Activity{ - data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} - }) do - Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> - send( - transport_pid, - {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} - ) - end) - end - - def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop - - def push_to_socket(topics, topic, item) do - Enum.each(topics[topic] || [], fn %StreamerSocket{ - transport_pid: transport_pid, - user: socket_user - } -> - # Get the current user so we have up-to-date blocks etc. - if socket_user do - user = User.get_cached_by_ap_id(socket_user.ap_id) - - if should_send?(user, item) do - send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) - end - else - send(transport_pid, {:text, StreamerView.render("update.json", item)}) - end - end) - end - - @spec thread_containment(Activity.t(), User.t()) :: boolean() - defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true - - defp thread_containment(activity, user) do - if Config.get([:instance, :skip_thread_containment]) do - true - else - ActivityPub.contain_activity(activity, user) - end - end -end diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs index bd229c55f..109c7b4cb 100644 --- a/test/integration/mastodon_websocket_test.exs +++ b/test/integration/mastodon_websocket_test.exs @@ -12,17 +12,14 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth + @moduletag needs_streamer: true, capture_log: true + @path Pleroma.Web.Endpoint.url() |> URI.parse() |> Map.put(:scheme, "ws") |> Map.put(:path, "/api/v1/streaming") |> URI.to_string() - setup_all do - start_supervised(Pleroma.Web.Streamer.supervisor()) - :ok - end - def start_socket(qs \\ nil, headers \\ []) do path = case qs do diff --git a/test/notification_test.exs b/test/notification_test.exs index 601a6c0ca..5c85f3368 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -162,14 +162,18 @@ test "does not create a notification for subscribed users if status is a reply" @tag needs_streamer: true test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do user = insert(:user) - task = Task.async(fn -> assert_receive {:text, _}, 4_000 end) - task_user_notification = Task.async(fn -> assert_receive {:text, _}, 4_000 end) - Streamer.add_socket("user", %{transport_pid: task.pid, assigns: %{user: user}}) - Streamer.add_socket( - "user:notification", - %{transport_pid: task_user_notification.pid, assigns: %{user: user}} - ) + task = + Task.async(fn -> + Streamer.add_socket("user", user) + assert_receive {:render_with_user, _, _, _}, 4_000 + end) + + task_user_notification = + Task.async(fn -> + Streamer.add_socket("user:notification", user) + assert_receive {:render_with_user, _, _, _}, 4_000 + end) activity = insert(:note_activity) diff --git a/test/support/builders/activity_builder.ex b/test/support/builders/activity_builder.ex index 6e5a8e059..7c4950bfa 100644 --- a/test/support/builders/activity_builder.ex +++ b/test/support/builders/activity_builder.ex @@ -21,7 +21,15 @@ def build(data \\ %{}, opts \\ %{}) do def insert(data \\ %{}, opts \\ %{}) do activity = build(data, opts) - ActivityPub.insert(activity) + + case ActivityPub.insert(activity) do + ok = {:ok, activity} -> + ActivityPub.notify_and_stream(activity) + ok + + error -> + error + end end def insert_list(times, data \\ %{}, opts \\ %{}) do diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 91c03b1a8..b23918dd1 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -139,7 +139,11 @@ defp ensure_federating_or_authenticated(conn, url, user) do end if tags[:needs_streamer] do - start_supervised(Pleroma.Web.Streamer.supervisor()) + start_supervised(%{ + id: Pleroma.Web.Streamer.registry(), + start: + {Registry, :start_link, [[keys: :duplicate, name: Pleroma.Web.Streamer.registry()]]} + }) end {:ok, conn: Phoenix.ConnTest.build_conn()} diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 1669f2520..ba8848952 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -40,7 +40,11 @@ defmodule Pleroma.DataCase do end if tags[:needs_streamer] do - start_supervised(Pleroma.Web.Streamer.supervisor()) + start_supervised(%{ + id: Pleroma.Web.Streamer.registry(), + start: + {Registry, :start_link, [[keys: :duplicate, name: Pleroma.Web.Streamer.registry()]]} + }) end :ok diff --git a/test/web/streamer/ping_test.exs b/test/web/streamer/ping_test.exs deleted file mode 100644 index 5df6c1cc3..000000000 --- a/test/web/streamer/ping_test.exs +++ /dev/null @@ -1,36 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PingTest do - use Pleroma.DataCase - - import Pleroma.Factory - alias Pleroma.Web.Streamer - - setup do - start_supervised({Streamer.supervisor(), [ping_interval: 30]}) - - :ok - end - - describe "sockets" do - setup do - user = insert(:user) - {:ok, %{user: user}} - end - - test "it sends pings", %{user: user} do - task = - Task.async(fn -> - assert_receive {:text, received_event}, 40 - assert_receive {:text, received_event}, 40 - assert_receive {:text, received_event}, 40 - end) - - Streamer.add_socket("public", %{transport_pid: task.pid, assigns: %{user: user}}) - - Task.await(task) - end - end -end diff --git a/test/web/streamer/state_test.exs b/test/web/streamer/state_test.exs deleted file mode 100644 index a755e75c0..000000000 --- a/test/web/streamer/state_test.exs +++ /dev/null @@ -1,54 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.StateTest do - use Pleroma.DataCase - - import Pleroma.Factory - alias Pleroma.Web.Streamer - alias Pleroma.Web.Streamer.StreamerSocket - - @moduletag needs_streamer: true - - describe "sockets" do - setup do - user = insert(:user) - user2 = insert(:user) - {:ok, %{user: user, user2: user2}} - end - - test "it can add a socket", %{user: user} do - Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) - - assert(%{"public" => [%StreamerSocket{transport_pid: 1}]} = Streamer.get_sockets()) - end - - test "it can add multiple sockets per user", %{user: user} do - Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) - Streamer.add_socket("public", %{transport_pid: 2, assigns: %{user: user}}) - - assert( - %{ - "public" => [ - %StreamerSocket{transport_pid: 2}, - %StreamerSocket{transport_pid: 1} - ] - } = Streamer.get_sockets() - ) - end - - test "it will not add a duplicate socket", %{user: user} do - Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) - Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) - - assert( - %{ - "activity" => [ - %StreamerSocket{transport_pid: 1} - ] - } = Streamer.get_sockets() - ) - end - end -end diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 3c0f240f5..ee530f4e9 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -12,13 +12,9 @@ defmodule Pleroma.Web.StreamerTest do alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Streamer - alias Pleroma.Web.Streamer.StreamerSocket - alias Pleroma.Web.Streamer.Worker @moduletag needs_streamer: true, capture_log: true - @streamer_timeout 150 - @streamer_start_wait 10 setup do: clear_config([:instance, :skip_thread_containment]) describe "user streams" do @@ -29,69 +25,35 @@ defmodule Pleroma.Web.StreamerTest do end test "it streams the user's post in the 'user' stream", %{user: user} do - task = - Task.async(fn -> - assert_receive {:text, _}, @streamer_timeout - end) - - Streamer.add_socket( - "user", - %{transport_pid: task.pid, assigns: %{user: user}} - ) - + Streamer.add_socket("user", user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) - - Streamer.stream("user", activity) - Task.await(task) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(user, activity) end test "it streams boosts of the user in the 'user' stream", %{user: user} do - task = - Task.async(fn -> - assert_receive {:text, _}, @streamer_timeout - end) - - Streamer.add_socket( - "user", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + Streamer.add_socket("user", user) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) {:ok, announce, _} = CommonAPI.repeat(activity.id, user) - Streamer.stream("user", announce) - Task.await(task) + assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} + refute Streamer.filtered_by_user?(user, announce) end test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do - task = - Task.async(fn -> - assert_receive {:text, _}, @streamer_timeout - end) - - Streamer.add_socket( - "user", - %{transport_pid: task.pid, assigns: %{user: user}} - ) - + Streamer.add_socket("user", user) Streamer.stream("user", notify) - Task.await(task) + assert_receive {:render_with_user, _, _, ^notify} + refute Streamer.filtered_by_user?(user, notify) end test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do - task = - Task.async(fn -> - assert_receive {:text, _}, @streamer_timeout - end) - - Streamer.add_socket( - "user:notification", - %{transport_pid: task.pid, assigns: %{user: user}} - ) - + Streamer.add_socket("user:notification", user) Streamer.stream("user:notification", notify) - Task.await(task) + assert_receive {:render_with_user, _, _, ^notify} + refute Streamer.filtered_by_user?(user, notify) end test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ @@ -100,18 +62,12 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl blocked = insert(:user) {:ok, _user_relationship} = User.block(user, blocked) - task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) - - Streamer.add_socket( - "user:notification", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + Streamer.add_socket("user:notification", user) {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) - {:ok, notif} = CommonAPI.favorite(blocked, activity.id) + {:ok, _} = CommonAPI.favorite(blocked, activity.id) - Streamer.stream("user:notification", notif) - Task.await(task) + refute_receive _ end test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ @@ -119,45 +75,50 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is } do user2 = insert(:user) - task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) - - Streamer.add_socket( - "user:notification", - %{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) + {:ok, _} = CommonAPI.add_mute(user, activity) - Streamer.stream("user:notification", notif) - Task.await(task) + Streamer.add_socket("user:notification", user) + + {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) + + refute_receive _ + assert Streamer.filtered_by_user?(user, favorite_activity) end - test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{ + test "it sends favorite to 'user:notification' stream'", %{ user: user } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) - task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + Streamer.add_socket("user:notification", user) + {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) - Streamer.add_socket( - "user:notification", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + assert_receive {:render_with_user, _, "notification.json", notif} + assert notif.activity.id == favorite_activity.id + refute Streamer.filtered_by_user?(user, notif) + end + + test "it doesn't send the 'user:notification' stream' when a domain is blocked", %{ + user: user + } 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(user2, activity.id) + Streamer.add_socket("user:notification", user) + {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) - Streamer.stream("user:notification", notif) - Task.await(task) + refute_receive _ + assert Streamer.filtered_by_user?(user, favorite_activity) end test "it sends follow activities to the 'user:notification' stream", %{ user: user } do user_url = user.ap_id + user2 = insert(:user) body = File.read!("test/fixtures/users_mock/localhost.json") @@ -169,47 +130,24 @@ test "it sends follow activities to the 'user:notification' stream", %{ %Tesla.Env{status: 200, body: body} end) - user2 = insert(:user) - task = Task.async(fn -> assert_receive {:text, _}, @streamer_timeout end) + Streamer.add_socket("user:notification", user) + {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) - Process.sleep(@streamer_start_wait) - - Streamer.add_socket( - "user:notification", - %{transport_pid: task.pid, assigns: %{user: user}} - ) - - {:ok, _follower, _followed, _activity} = CommonAPI.follow(user2, user) - - # We don't directly pipe the notification to the streamer as it's already - # generated as a side effect of CommonAPI.follow(). - Task.await(task) + assert_receive {:render_with_user, _, "notification.json", notif} + assert notif.activity.id == follow_activity.id + refute Streamer.filtered_by_user?(user, notif) end end - test "it sends to public" do + test "it sends to public authenticated" do user = insert(:user) other_user = insert(:user) - task = - Task.async(fn -> - assert_receive {:text, _}, @streamer_timeout - end) + Streamer.add_socket("public", other_user) - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user - } - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) - - topics = %{ - "public" => [fake_socket] - } - - Worker.push_to_socket(topics, "public", activity) - - Task.await(task) + {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(user, activity) end test "works for deletions" do @@ -217,37 +155,32 @@ test "works for deletions" do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) - task = - Task.async(fn -> - expected_event = - %{ - "event" => "delete", - "payload" => activity.id - } - |> Jason.encode!() + Streamer.add_socket("public", user) - assert_receive {:text, received_event}, @streamer_timeout - assert received_event == expected_event - end) + {:ok, _} = CommonAPI.delete(activity.id, other_user) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) + end - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user - } + test "it sends to public unauthenticated" do + user = insert(:user) - {:ok, activity} = CommonAPI.delete(activity.id, other_user) + Streamer.add_socket("public", nil) - topics = %{ - "public" => [fake_socket] - } + {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) + assert %{"id" => ^activity_id} = Jason.decode!(payload) - Worker.push_to_socket(topics, "public", activity) - - Task.await(task) + {:ok, _} = CommonAPI.delete(activity.id, user) + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) end describe "thread_containment" do - test "it doesn't send to user if recipients invalid and thread containment is enabled" do + test "it filters to user if recipients invalid and thread containment is enabled" do Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) user = insert(:user) @@ -262,12 +195,10 @@ test "it doesn't send to user if recipients invalid and thread containment is en ) ) - task = Task.async(fn -> refute_receive {:text, _}, 1_000 end) - fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} - topics = %{"public" => [fake_socket]} - Worker.push_to_socket(topics, "public", activity) - - Task.await(task) + Streamer.add_socket("public", user) + Streamer.stream("public", activity) + assert_receive {:render_with_user, _, _, ^activity} + assert Streamer.filtered_by_user?(user, activity) end test "it sends message if recipients invalid and thread containment is disabled" do @@ -285,12 +216,11 @@ test "it sends message if recipients invalid and thread containment is disabled" ) ) - task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) - fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} - topics = %{"public" => [fake_socket]} - Worker.push_to_socket(topics, "public", activity) + Streamer.add_socket("public", user) + Streamer.stream("public", activity) - Task.await(task) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(user, activity) end test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do @@ -308,255 +238,168 @@ test "it sends message if recipients invalid and thread containment is enabled b ) ) - task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) - fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} - topics = %{"public" => [fake_socket]} - Worker.push_to_socket(topics, "public", activity) + Streamer.add_socket("public", user) + Streamer.stream("public", activity) - Task.await(task) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(user, activity) end end describe "blocks" do - test "it doesn't send messages involving blocked users" do + test "it filters messages involving blocked users" do user = insert(:user) blocked_user = insert(:user) {:ok, _user_relationship} = User.block(user, blocked_user) + Streamer.add_socket("public", user) {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) - - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) - - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user - } - - topics = %{ - "public" => [fake_socket] - } - - Worker.push_to_socket(topics, "public", activity) - - Task.await(task) + assert_receive {:render_with_user, _, _, ^activity} + assert Streamer.filtered_by_user?(user, activity) end - test "it doesn't send messages transitively involving blocked users" do + test "it filters messages transitively involving blocked users" do blocker = insert(:user) blockee = insert(:user) friend = insert(:user) - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) - - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: blocker - } - - topics = %{ - "public" => [fake_socket] - } + Streamer.add_socket("public", blocker) {:ok, _user_relationship} = User.block(blocker, blockee) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) - Worker.push_to_socket(topics, "public", activity_one) + assert_receive {:render_with_user, _, _, ^activity_one} + assert Streamer.filtered_by_user?(blocker, activity_one) {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) - Worker.push_to_socket(topics, "public", activity_two) + assert_receive {:render_with_user, _, _, ^activity_two} + assert Streamer.filtered_by_user?(blocker, activity_two) {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) - Worker.push_to_socket(topics, "public", activity_three) - - Task.await(task) + assert_receive {:render_with_user, _, _, ^activity_three} + assert Streamer.filtered_by_user?(blocker, activity_three) end end - test "it doesn't send unwanted DMs to list" do - user_a = insert(:user) - user_b = insert(:user) - user_c = insert(:user) + describe "lists" do + test "it doesn't send unwanted DMs to list" do + user_a = insert(:user) + user_b = insert(:user) + user_c = insert(:user) - {:ok, user_a} = User.follow(user_a, user_b) + {:ok, user_a} = User.follow(user_a, user_b) - {:ok, list} = List.create("Test", user_a) - {:ok, list} = List.follow(list, user_b) + {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.follow(list, user_b) - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "@#{user_c.nickname} Test", - "visibility" => "direct" - }) + Streamer.add_socket("list:#{list.id}", user_a) - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) + {:ok, _activity} = + CommonAPI.post(user_b, %{ + "status" => "@#{user_c.nickname} Test", + "visibility" => "direct" + }) - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user_a - } + refute_receive _ + end - topics = %{ - "list:#{list.id}" => [fake_socket] - } + test "it doesn't send unwanted private posts to list" do + user_a = insert(:user) + user_b = insert(:user) - Worker.handle_call({:stream, "list", activity}, self(), topics) + {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.follow(list, user_b) - Task.await(task) + Streamer.add_socket("list:#{list.id}", user_a) + + {:ok, _activity} = + CommonAPI.post(user_b, %{ + "status" => "Test", + "visibility" => "private" + }) + + refute_receive _ + end + + test "it sends wanted private posts to list" do + user_a = insert(:user) + user_b = insert(:user) + + {:ok, user_a} = User.follow(user_a, user_b) + + {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.follow(list, user_b) + + Streamer.add_socket("list:#{list.id}", user_a) + + {:ok, activity} = + CommonAPI.post(user_b, %{ + "status" => "Test", + "visibility" => "private" + }) + + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(user_a, activity) + end end - test "it doesn't send unwanted private posts to list" do - user_a = insert(:user) - user_b = insert(:user) + describe "muted reblogs" do + test "it filters muted reblogs" do + user1 = insert(:user) + user2 = insert(:user) + user3 = insert(:user) + CommonAPI.follow(user1, user2) + CommonAPI.hide_reblogs(user1, user2) - {:ok, list} = List.create("Test", user_a) - {:ok, list} = List.follow(list, user_b) + {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "Test", - "visibility" => "private" - }) + Streamer.add_socket("user", user1) + {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) + assert_receive {:render_with_user, _, _, ^announce_activity} + assert Streamer.filtered_by_user?(user1, announce_activity) + end - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) + test "it filters reblog notification for reblog-muted actors" do + user1 = insert(:user) + user2 = insert(:user) + CommonAPI.follow(user1, user2) + CommonAPI.hide_reblogs(user1, user2) - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user_a - } + {:ok, create_activity} = CommonAPI.post(user1, %{"status" => "I'm kawen"}) + Streamer.add_socket("user", user1) + {:ok, _favorite_activity, _} = CommonAPI.repeat(create_activity.id, user2) - topics = %{ - "list:#{list.id}" => [fake_socket] - } + assert_receive {:render_with_user, _, "notification.json", notif} + assert Streamer.filtered_by_user?(user1, notif) + end - Worker.handle_call({:stream, "list", activity}, self(), topics) + test "it send non-reblog notification for reblog-muted actors" do + user1 = insert(:user) + user2 = insert(:user) + CommonAPI.follow(user1, user2) + CommonAPI.hide_reblogs(user1, user2) - Task.await(task) + {:ok, create_activity} = CommonAPI.post(user1, %{"status" => "I'm kawen"}) + Streamer.add_socket("user", user1) + {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) + + assert_receive {:render_with_user, _, "notification.json", notif} + refute Streamer.filtered_by_user?(user1, notif) + end end - test "it sends wanted private posts to list" do - user_a = insert(:user) - user_b = insert(:user) - - {:ok, user_a} = User.follow(user_a, user_b) - - {:ok, list} = List.create("Test", user_a) - {:ok, list} = List.follow(list, user_b) - - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "Test", - "visibility" => "private" - }) - - task = - Task.async(fn -> - assert_receive {:text, _}, 1_000 - end) - - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user_a - } - - Streamer.add_socket( - "list:#{list.id}", - fake_socket - ) - - Worker.handle_call({:stream, "list", activity}, self(), %{}) - - Task.await(task) - end - - test "it doesn't send muted reblogs" do - user1 = insert(:user) - user2 = insert(:user) - user3 = insert(:user) - CommonAPI.hide_reblogs(user1, user2) - - {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) - {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) - - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) - - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user1 - } - - topics = %{ - "public" => [fake_socket] - } - - Worker.push_to_socket(topics, "public", announce_activity) - - Task.await(task) - end - - test "it does send non-reblog notification for reblog-muted actors" do - user1 = insert(:user) - user2 = insert(:user) - user3 = insert(:user) - CommonAPI.hide_reblogs(user1, user2) - - {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) - {:ok, favorite_activity} = CommonAPI.favorite(user2, create_activity.id) - - task = - Task.async(fn -> - assert_receive {:text, _}, 1_000 - end) - - fake_socket = %StreamerSocket{ - transport_pid: task.pid, - user: user1 - } - - topics = %{ - "public" => [fake_socket] - } - - Worker.push_to_socket(topics, "public", favorite_activity) - - Task.await(task) - end - - test "it doesn't send posts from muted threads" do + test "it filters posts from muted threads" do user = insert(:user) user2 = insert(:user) + Streamer.add_socket("user", user2) {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) - - {:ok, activity} = CommonAPI.add_mute(user2, activity) - - task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) - - Streamer.add_socket( - "user", - %{transport_pid: task.pid, assigns: %{user: user2}} - ) - - Streamer.stream("user", activity) - Task.await(task) + {:ok, _} = CommonAPI.add_mute(user2, activity) + assert_receive {:render_with_user, _, _, ^activity} + assert Streamer.filtered_by_user?(user2, activity) end describe "direct streams" do @@ -568,22 +411,7 @@ test "it sends conversation update to the 'direct' stream", %{} do user = insert(:user) another_user = insert(:user) - task = - Task.async(fn -> - assert_receive {:text, received_event}, @streamer_timeout - - assert %{"event" => "conversation", "payload" => received_payload} = - Jason.decode!(received_event) - - assert %{"last_status" => last_status} = Jason.decode!(received_payload) - [participation] = Participation.for_user(user) - assert last_status["pleroma"]["direct_conversation_id"] == participation.id - end) - - Streamer.add_socket( - "direct", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + Streamer.add_socket("direct", user) {:ok, _create_activity} = CommonAPI.post(another_user, %{ @@ -591,42 +419,47 @@ test "it sends conversation update to the 'direct' stream", %{} do "visibility" => "direct" }) - Task.await(task) + assert_receive {:text, received_event} + + assert %{"event" => "conversation", "payload" => received_payload} = + Jason.decode!(received_event) + + assert %{"last_status" => last_status} = Jason.decode!(received_payload) + [participation] = Participation.for_user(user) + assert last_status["pleroma"]["direct_conversation_id"] == participation.id end test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do user = insert(:user) another_user = insert(:user) + Streamer.add_socket("direct", user) + {:ok, create_activity} = CommonAPI.post(another_user, %{ "status" => "hi @#{user.nickname}", "visibility" => "direct" }) - task = - Task.async(fn -> - assert_receive {:text, received_event}, @streamer_timeout - assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + create_activity_id = create_activity.id + assert_receive {:render_with_user, _, _, ^create_activity} + assert_receive {:text, received_conversation1} + assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) - refute_receive {:text, _}, @streamer_timeout - end) + {:ok, _} = CommonAPI.delete(create_activity_id, another_user) - Process.sleep(@streamer_start_wait) + assert_receive {:text, received_event} - Streamer.add_socket( - "direct", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + assert %{"event" => "delete", "payload" => ^create_activity_id} = + Jason.decode!(received_event) - {:ok, _} = CommonAPI.delete(create_activity.id, another_user) - - Task.await(task) + refute_receive _ end test "it sends conversation update to the 'direct' stream when a message is deleted" do user = insert(:user) another_user = insert(:user) + Streamer.add_socket("direct", user) {:ok, create_activity} = CommonAPI.post(another_user, %{ @@ -636,35 +469,30 @@ test "it sends conversation update to the 'direct' stream when a message is dele {:ok, create_activity2} = CommonAPI.post(another_user, %{ - "status" => "hi @#{user.nickname}", + "status" => "hi @#{user.nickname} 2", "in_reply_to_status_id" => create_activity.id, "visibility" => "direct" }) - task = - Task.async(fn -> - assert_receive {:text, received_event}, @streamer_timeout - assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) - - assert_receive {:text, received_event}, @streamer_timeout - - assert %{"event" => "conversation", "payload" => received_payload} = - Jason.decode!(received_event) - - assert %{"last_status" => last_status} = Jason.decode!(received_payload) - assert last_status["id"] == to_string(create_activity.id) - end) - - Process.sleep(@streamer_start_wait) - - Streamer.add_socket( - "direct", - %{transport_pid: task.pid, assigns: %{user: user}} - ) + assert_receive {:render_with_user, _, _, ^create_activity} + assert_receive {:render_with_user, _, _, ^create_activity2} + assert_receive {:text, received_conversation1} + assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) + assert_receive {:text, received_conversation1} + assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) {:ok, _} = CommonAPI.delete(create_activity2.id, another_user) - Task.await(task) + assert_receive {:text, received_event} + assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + + assert_receive {:text, received_event} + + assert %{"event" => "conversation", "payload" => received_payload} = + Jason.decode!(received_event) + + assert %{"last_status" => last_status} = Jason.decode!(received_payload) + assert last_status["id"] == to_string(create_activity.id) end end end From cdca62e8d4772240c513acc08a627d2f0ee0eed4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 6 May 2020 19:20:26 +0400 Subject: [PATCH 099/337] Add schema for Tag --- lib/pleroma/web/api_spec/schemas/status.ex | 12 ++-------- lib/pleroma/web/api_spec/schemas/tag.ex | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/tag.ex diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 7a804461f..2572c9641 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Poll + alias Pleroma.Web.ApiSpec.Schemas.Tag alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex @@ -106,16 +107,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do replies_count: %Schema{type: :integer}, sensitive: %Schema{type: :boolean}, spoiler_text: %Schema{type: :string}, - tags: %Schema{ - type: :array, - items: %Schema{ - type: :object, - properties: %{ - name: %Schema{type: :string}, - url: %Schema{type: :string, format: :uri} - } - } - }, + tags: %Schema{type: :array, items: Tag}, uri: %Schema{type: :string, format: :uri}, url: %Schema{type: :string, nullable: true, format: :uri}, visibility: VisibilityScope diff --git a/lib/pleroma/web/api_spec/schemas/tag.ex b/lib/pleroma/web/api_spec/schemas/tag.ex new file mode 100644 index 000000000..e693fb83e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/tag.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.Tag do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Tag", + description: "Represents a hashtag used within the content of a status", + type: :object, + properties: %{ + name: %Schema{type: :string, description: "The value of the hashtag after the # sign"}, + url: %Schema{ + type: :string, + format: :uri, + description: "A link to the hashtag on the instance" + } + }, + example: %{ + name: "cofe", + url: "https://lain.com/tag/cofe" + } + }) +end From dc4a448f4863e7d69c55d39273575fb3463c6c3c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 7 May 2020 14:04:48 +0400 Subject: [PATCH 100/337] Add OpenAPI spec for SearchController --- .../api_spec/operations/account_operation.ex | 5 +- .../api_spec/operations/search_operation.ex | 207 ++++++++++++++++++ .../controllers/search_controller.ex | 24 +- .../controllers/search_controller_test.exs | 78 +++---- 4 files changed, 264 insertions(+), 50 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/search_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 470fc0215..70069d6f9 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -556,11 +556,12 @@ defp update_creadentials_request do } end - defp array_of_accounts do + def array_of_accounts do %Schema{ title: "ArrayOfAccounts", type: :array, - items: Account + items: Account, + example: [Account.schema().example] } end diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex new file mode 100644 index 000000000..ec1ae5dcf --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/search_operation.ex @@ -0,0 +1,207 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.SearchOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.AccountOperation + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.Tag + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def account_search_operation do + %Operation{ + tags: ["Search"], + summary: "Search for matching accounts by username or display name", + operationId: "SearchController.account_search", + parameters: [ + Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for", + required: true + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 40}, + "Maximum number of results" + ), + Operation.parameter( + :resolve, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Attempt WebFinger lookup. Use this when `q` is an exact address." + ), + Operation.parameter( + :following, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Only who the user is following." + ) + ], + responses: %{ + 200 => + Operation.response( + "Array of Account", + "application/json", + AccountOperation.array_of_accounts() + ) + } + } + end + + def search_operation do + %Operation{ + tags: ["Search"], + summary: "Search results", + security: [%{"oAuth" => ["read:search"]}], + operationId: "SearchController.search", + deprecated: true, + parameters: [ + Operation.parameter( + :account_id, + :query, + FlakeID, + "If provided, statuses returned will be authored only by this account" + ), + Operation.parameter( + :type, + :query, + %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]}, + "Search type" + ), + Operation.parameter(:q, :query, %Schema{type: :string}, "The search query", required: true), + Operation.parameter( + :resolve, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Attempt WebFinger lookup" + ), + Operation.parameter( + :following, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Only include accounts that the user is following" + ), + Operation.parameter( + :offset, + :query, + %Schema{type: :integer}, + "Offset" + ) + | pagination_params() + ], + responses: %{ + 200 => Operation.response("Results", "application/json", results()) + } + } + end + + def search2_operation do + %Operation{ + tags: ["Search"], + summary: "Search results", + security: [%{"oAuth" => ["read:search"]}], + operationId: "SearchController.search2", + parameters: [ + Operation.parameter( + :account_id, + :query, + FlakeID, + "If provided, statuses returned will be authored only by this account" + ), + Operation.parameter( + :type, + :query, + %Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]}, + "Search type" + ), + Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for", + required: true + ), + Operation.parameter( + :resolve, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Attempt WebFinger lookup" + ), + Operation.parameter( + :following, + :query, + %Schema{allOf: [BooleanLike], default: false}, + "Only include accounts that the user is following" + ) + | pagination_params() + ], + responses: %{ + 200 => Operation.response("Results", "application/json", results2()) + } + } + end + + defp results2 do + %Schema{ + title: "SearchResults", + type: :object, + properties: %{ + accounts: %Schema{ + type: :array, + items: Account, + description: "Accounts which match the given query" + }, + statuses: %Schema{ + type: :array, + items: Status, + description: "Statuses which match the given query" + }, + hashtags: %Schema{ + type: :array, + items: Tag, + description: "Hashtags which match the given query" + } + }, + example: %{ + "accounts" => [Account.schema().example], + "statuses" => [Status.schema().example], + "hashtags" => [Tag.schema().example] + } + } + end + + defp results do + %Schema{ + title: "SearchResults", + type: :object, + properties: %{ + accounts: %Schema{ + type: :array, + items: Account, + description: "Accounts which match the given query" + }, + statuses: %Schema{ + type: :array, + items: Status, + description: "Statuses which match the given query" + }, + hashtags: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Hashtags which match the given query" + } + }, + example: %{ + "accounts" => [Account.schema().example], + "statuses" => [Status.schema().example], + "hashtags" => ["cofe"] + } + } + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index cd49da6ad..0e0d54ba4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1] + import Pleroma.Web.ControllerHelper, only: [skip_relationships?: 1] alias Pleroma.Activity alias Pleroma.Plugs.OAuthScopesPlug @@ -18,6 +18,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do require Logger + plug(Pleroma.Web.ApiSpec.CastAndValidate) + # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) @@ -25,7 +27,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) - def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation + + def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do accounts = User.search(query, search_options(params, user)) conn @@ -36,7 +40,7 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d def search2(conn, params), do: do_search(:v2, conn, params) def search(conn, params), do: do_search(:v1, conn, params) - defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do + defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do options = search_options(params, user) timeout = Keyword.get(Repo.config(), :timeout, 15_000) default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []} @@ -44,7 +48,7 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para result = default_values |> Enum.map(fn {resource, default_value} -> - if params["type"] in [nil, resource] do + if params[:type] in [nil, resource] do {resource, fn -> resource_search(version, resource, query, options) end} else {resource, fn -> default_value end} @@ -68,11 +72,11 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para defp search_options(params, user) do [ skip_relationships: skip_relationships?(params), - resolve: params["resolve"] == "true", - following: params["following"] == "true", - limit: fetch_integer_param(params, "limit"), - offset: fetch_integer_param(params, "offset"), - type: params["type"], + resolve: params[:resolve], + following: params[:following], + limit: params[:limit], + offset: params[:offset], + type: params[:type], author: get_author(params), for_user: user ] @@ -135,7 +139,7 @@ defp with_fallback(f, fallback \\ []) do end end - defp get_author(%{"account_id" => account_id}) when is_binary(account_id), + defp get_author(%{account_id: account_id}) when is_binary(account_id), do: User.get_cached_by_id(account_id) defp get_author(_params), do: nil diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 11133ff66..02476acb6 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -27,8 +27,8 @@ test "it returns empty result if user or status search return undefined error", capture_log(fn -> results = conn - |> get("/api/v2/search", %{"q" => "2hu"}) - |> json_response(200) + |> get("/api/v2/search?q=2hu") + |> json_response_and_validate_schema(200) assert results["accounts"] == [] assert results["statuses"] == [] @@ -54,8 +54,8 @@ test "search", %{conn: conn} do results = conn - |> get("/api/v2/search", %{"q" => "2hu #private"}) - |> json_response(200) + |> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}") + |> json_response_and_validate_schema(200) [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) @@ -68,8 +68,8 @@ test "search", %{conn: conn} do assert status["id"] == to_string(activity.id) results = - get(conn, "/api/v2/search", %{"q" => "天子"}) - |> json_response(200) + get(conn, "/api/v2/search?q=天子") + |> json_response_and_validate_schema(200) [status] = results["statuses"] assert status["id"] == to_string(activity.id) @@ -89,8 +89,8 @@ test "excludes a blocked users from search results", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) - |> get("/api/v2/search", %{"q" => "Agent"}) - |> json_response(200) + |> get("/api/v2/search?q=Agent") + |> json_response_and_validate_schema(200) status_ids = Enum.map(results["statuses"], fn g -> g["id"] end) @@ -107,8 +107,8 @@ test "account search", %{conn: conn} do results = conn - |> get("/api/v1/accounts/search", %{"q" => "shp"}) - |> json_response(200) + |> get("/api/v1/accounts/search?q=shp") + |> json_response_and_validate_schema(200) result_ids = for result <- results, do: result["acct"] @@ -117,8 +117,8 @@ test "account search", %{conn: conn} do results = conn - |> get("/api/v1/accounts/search", %{"q" => "2hu"}) - |> json_response(200) + |> get("/api/v1/accounts/search?q=2hu") + |> json_response_and_validate_schema(200) result_ids = for result <- results, do: result["acct"] @@ -130,8 +130,8 @@ test "returns account if query contains a space", %{conn: conn} do results = conn - |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) - |> json_response(200) + |> get("/api/v1/accounts/search?q=shp@shitposter.club xxx") + |> json_response_and_validate_schema(200) assert length(results) == 1 end @@ -146,8 +146,8 @@ test "it returns empty result if user or status search return undefined error", capture_log(fn -> results = conn - |> get("/api/v1/search", %{"q" => "2hu"}) - |> json_response(200) + |> get("/api/v1/search?q=2hu") + |> json_response_and_validate_schema(200) assert results["accounts"] == [] assert results["statuses"] == [] @@ -173,8 +173,8 @@ test "search", %{conn: conn} do results = conn - |> get("/api/v1/search", %{"q" => "2hu"}) - |> json_response(200) + |> get("/api/v1/search?q=2hu") + |> json_response_and_validate_schema(200) [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) @@ -194,8 +194,8 @@ test "search fetches remote statuses and prefers them over other results", %{con results = conn - |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) - |> json_response(200) + |> get("/api/v1/search?q=https://shitposter.club/notice/2827873") + |> json_response_and_validate_schema(200) [status, %{"id" => ^activity_id}] = results["statuses"] @@ -212,10 +212,12 @@ test "search doesn't show statuses that it shouldn't", %{conn: conn} do }) capture_log(fn -> + q = Object.normalize(activity).data["id"] + results = conn - |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) - |> json_response(200) + |> get("/api/v1/search?q=#{q}") + |> json_response_and_validate_schema(200) [] = results["statuses"] end) @@ -228,8 +230,8 @@ test "search fetches remote accounts", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) - |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) - |> json_response(200) + |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=true") + |> json_response_and_validate_schema(200) [account] = results["accounts"] assert account["acct"] == "mike@osada.macgirvin.com" @@ -238,8 +240,8 @@ test "search fetches remote accounts", %{conn: conn} do test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do results = conn - |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"}) - |> json_response(200) + |> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false") + |> json_response_and_validate_schema(200) assert [] == results["accounts"] end @@ -254,16 +256,16 @@ test "search with limit and offset", %{conn: conn} do result = conn - |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1}) + |> get("/api/v1/search?q=2hu&limit=1") - assert results = json_response(result, 200) + assert results = json_response_and_validate_schema(result, 200) assert [%{"id" => activity_id1}] = results["statuses"] assert [_] = results["accounts"] results = conn - |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1, "offset" => 1}) - |> json_response(200) + |> get("/api/v1/search?q=2hu&limit=1&offset=1") + |> json_response_and_validate_schema(200) assert [%{"id" => activity_id2}] = results["statuses"] assert [] = results["accounts"] @@ -279,13 +281,13 @@ test "search returns results only for the given type", %{conn: conn} do assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} = conn - |> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"}) - |> json_response(200) + |> get("/api/v1/search?q=2hu&type=statuses") + |> json_response_and_validate_schema(200) assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} = conn - |> get("/api/v1/search", %{"q" => "2hu", "type" => "accounts"}) - |> json_response(200) + |> get("/api/v1/search?q=2hu&type=accounts") + |> json_response_and_validate_schema(200) end test "search uses account_id to filter statuses by the author", %{conn: conn} do @@ -297,8 +299,8 @@ test "search uses account_id to filter statuses by the author", %{conn: conn} do results = conn - |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id}) - |> json_response(200) + |> get("/api/v1/search?q=2hu&account_id=#{user.id}") + |> json_response_and_validate_schema(200) assert [%{"id" => activity_id1}] = results["statuses"] assert activity_id1 == activity1.id @@ -306,8 +308,8 @@ test "search uses account_id to filter statuses by the author", %{conn: conn} do results = conn - |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id}) - |> json_response(200) + |> get("/api/v1/search?q=2hu&account_id=#{user_two.id}") + |> json_response_and_validate_schema(200) assert [%{"id" => activity_id2}] = results["statuses"] assert activity_id2 == activity2.id From f57fa2a00df2d93eba53c1ff3ab5c7d5fabb8308 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 7 May 2020 12:43:30 +0200 Subject: [PATCH 101/337] Notifications: Simplify recipient calculation for some Activities. Fixes the 'getting notfications for other people's posts' bug. --- lib/pleroma/notification.ex | 29 ++++++++++++++++++++++------- test/notification_test.exs | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 98289af08..b14e7c843 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -339,13 +339,7 @@ def get_notified_from_activity(activity, local_only \\ true) def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only) when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do - potential_receiver_ap_ids = - [] - |> Utils.maybe_notify_to_recipients(activity) - |> Utils.maybe_notify_mentioned_recipients(activity) - |> Utils.maybe_notify_subscribers(activity) - |> Utils.maybe_notify_followers(activity) - |> Enum.uniq() + potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity) potential_receivers = User.get_users_from_set(potential_receiver_ap_ids, local_only) @@ -363,6 +357,27 @@ def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, lo def get_notified_from_activity(_, _local_only), do: {[], []} + # For some actitivies, only notifity the author of the object + def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}}) + when type in ~w{Like Announce EmojiReact} do + case Object.get_cached_by_ap_id(object_id) do + %Object{data: %{"actor" => actor}} -> + [actor] + + _ -> + [] + end + end + + def get_potential_receiver_ap_ids(activity) do + [] + |> Utils.maybe_notify_to_recipients(activity) + |> Utils.maybe_notify_mentioned_recipients(activity) + |> Utils.maybe_notify_subscribers(activity) + |> Utils.maybe_notify_followers(activity) + |> Enum.uniq() + end + @doc "Filters out AP IDs domain-blocking and not following the activity's actor" def exclude_domain_blocker_ap_ids(ap_ids, activity, preloaded_users \\ []) diff --git a/test/notification_test.exs b/test/notification_test.exs index 5c85f3368..509ca0b0b 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -12,6 +12,8 @@ defmodule Pleroma.NotificationTest do alias Pleroma.Notification alias Pleroma.Tests.ObanHelpers alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.NotificationView @@ -601,6 +603,28 @@ test "it does not send notification to mentioned users in likes" do assert other_user not in enabled_receivers end + test "it only notifies the post's author in likes" do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + {:ok, activity_one} = + CommonAPI.post(user, %{ + "status" => "hey @#{other_user.nickname}!" + }) + + {:ok, like_data, _} = Builder.like(third_user, activity_one.object) + + {:ok, like, _} = + like_data + |> Map.put("to", [other_user.ap_id | like_data["to"]]) + |> ActivityPub.persist(local: true) + + {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(like) + + assert other_user not in enabled_receivers + end + test "it does not send notification to mentioned users in announces" do user = insert(:user) other_user = insert(:user) From 3f867d8e9bf970e180153b411d5924f15c490046 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 7 May 2020 10:48:09 +0000 Subject: [PATCH 102/337] Apply suggestion to lib/pleroma/web/api_spec/operations/search_operation.ex --- lib/pleroma/web/api_spec/operations/search_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/search_operation.ex b/lib/pleroma/web/api_spec/operations/search_operation.ex index ec1ae5dcf..6ea00a9a8 100644 --- a/lib/pleroma/web/api_spec/operations/search_operation.ex +++ b/lib/pleroma/web/api_spec/operations/search_operation.ex @@ -44,7 +44,7 @@ def account_search_operation do :following, :query, %Schema{allOf: [BooleanLike], default: false}, - "Only who the user is following." + "Only include accounts that the user is following" ) ], responses: %{ From 8ae4d64d475405f8ff98868b80fc71fbe74b45bc Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 7 May 2020 11:01:52 +0000 Subject: [PATCH 103/337] Apply suggestion to lib/pleroma/notification.ex --- lib/pleroma/notification.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b14e7c843..af49fd713 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -357,7 +357,7 @@ def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, lo def get_notified_from_activity(_, _local_only), do: {[], []} - # For some actitivies, only notifity the author of the object + # For some activities, only notify the author of the object def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}}) when type in ~w{Like Announce EmojiReact} do case Object.get_cached_by_ap_id(object_id) do From 9c3c142c32b027addd7b729229820f8b2bf76994 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 7 May 2020 14:35:29 +0300 Subject: [PATCH 104/337] Restore mix.lock after 2FA merge It downgraded a bunch of deps, including plug. Which resulted in errors since pleroma was using a feature plug didn't support at the time. --- mix.lock | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/mix.lock b/mix.lock index 4792249d7..c400202b7 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,8 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, - "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, + "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, @@ -18,33 +19,38 @@ "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, + "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "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.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, + "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"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, + "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, + "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, - "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, + "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, + "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, + "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, + "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, "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"}, @@ -53,34 +59,38 @@ "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"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "e6d886252f1a41f4ba06ecf2b4c8d38760b34b1c08a11c28f7397b2e03995964"}, "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, + "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, "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": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]}, "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.12", "b86fa85a2ba336f5de068549de5ccceec356fd413264a9637e7733395d6cc4ea", [: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", "58331ade6d77e1312a3d976f0fa41803b8f004b2b5f489193425bc46aea3ed30"}, + "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"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, - "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "164baaeb382d19beee0ec484492aa82a9c8685770aee33b24ec727a0971b34d0"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.1", "a196e4f428d7f5d6dba5ded314cc55cd0fbddf1110af620f75c0190e77844b33", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "15a3c34ffaccef8a0b575b8d39ab1b9044586d7dab917292cdc44cf2737df7f2"}, - "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, + "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, "pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"}, - "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, + "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, From 788b7e7bbd2732e2af72adad1a660cf363486c6b Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 7 May 2020 14:52:37 +0200 Subject: [PATCH 105/337] Merge fixes. --- lib/pleroma/user.ex | 13 +-- .../web/activity_pub/object_validator.ex | 6 +- .../object_validators/common_validations.ex | 2 +- .../activity_pub/object_validator_test.exs | 1 - test/web/activity_pub/transmogrifier_test.exs | 107 ------------------ 5 files changed, 10 insertions(+), 119 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 921bdd93a..2a6a23fec 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1557,16 +1557,13 @@ def delete_user_activities(%User{ap_id: ap_id} = user) do defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do {:ok, delete_data, _} = Builder.delete(user, object) - Pipeline.common_pipeline(delete_data, local: true) + Pipeline.common_pipeline(delete_data, local: user.local) end - defp delete_activity(%{data: %{"type" => type}} = activity) when type in ["Like", "Announce"] do - actor = - activity.actor - |> get_cached_by_ap_id() - - {:ok, undo, _} = Builder.undo(actor, activity) - Pipeline.common_pipeline(undo, local: true) + defp delete_activity(%{data: %{"type" => type}} = activity, user) + when type in ["Like", "Announce"] do + {:ok, undo, _} = Builder.undo(user, activity) + Pipeline.common_pipeline(undo, local: user.local) end defp delete_activity(_activity, _user), do: "Doing nothing" diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 8e043287d..1f0431b36 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -21,8 +21,10 @@ def validate(object, meta) def validate(%{"type" => "Undo"} = object, meta) do with {:ok, object} <- - object |> UndoValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do - object = stringify_keys(object |> Map.from_struct()) + object + |> UndoValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) {:ok, object, meta} 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 index 2ada9f09e..aeef31945 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -48,7 +48,7 @@ def validate_object_presence(cng, options \\ []) do cng |> validate_change(field_name, fn field_name, object_id -> - object = Object.get_cached_by_ap_id(object_id) || Activity.get_by_ap_id(object) + object = Object.get_cached_by_ap_id(object_id) || Activity.get_by_ap_id(object_id) cond do !object -> diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 4d90a0cf3..174be5ec6 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -107,7 +107,6 @@ test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post {:error, cng} = ObjectValidator.validate(missing_object, []) assert {:object, {"can't find object", []}} in cng.errors - assert length(cng.errors) == 1 end test "it's invalid if the actor of the object and the actor of delete are from different domains", diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index ae5d3bf92..4fd6c8b00 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -685,113 +685,6 @@ test "it works for incoming update activities which lock the account" do assert user.locked == true end - test "it works for incoming deletes" do - activity = insert(:note_activity) - deleting_user = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - |> Map.put("actor", deleting_user.ap_id) - - {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = - Transmogrifier.handle_incoming(data) - - assert id == data["id"] - refute Activity.get_by_id(activity.id) - assert actor == deleting_user.ap_id - end - - test "it fails for incoming deletes with spoofed origin" do - activity = insert(:note_activity) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - - assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) - end) =~ - "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" - - assert Activity.get_by_id(activity.id) - end - - @tag capture_log: true - test "it works for incoming user deletes" do - %{ap_id: ap_id} = - insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false) - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - - {:ok, _} = Transmogrifier.handle_incoming(data) - ObanHelpers.perform_all() - - refute User.get_cached_by_ap_id(ap_id) - end - - test "it fails for incoming user deletes with spoofed origin" do - %{ap_id: ap_id} = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - |> Map.put("actor", ap_id) - - assert capture_log(fn -> - assert :error == Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" - - assert User.get_cached_by_ap_id(ap_id) - end - - test "it works for incoming unannounces with an existing notice" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) - - announce_data = - File.read!("test/fixtures/mastodon-announce.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: announce_data, local: false}} = - Transmogrifier.handle_incoming(announce_data) - - data = - File.read!("test/fixtures/mastodon-undo-announce.json") - |> Poison.decode!() - |> Map.put("object", announce_data) - |> Map.put("actor", announce_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["type"] == "Undo" - assert object_data = data["object"] - assert object_data["type"] == "Announce" - assert object_data["object"] == activity.data["object"] - - assert object_data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - end - test "it works for incomming unfollows with an existing follow" do user = insert(:user) From d11eea62b139ce16d7dffbd574947b2550df238f Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 7 May 2020 15:09:37 +0200 Subject: [PATCH 106/337] Credo fixes --- lib/pleroma/web/activity_pub/object_validator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 1f0431b36..4782cd8f3 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -13,8 +13,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) From eb1f2fcbc62735a6e1a24c7c5591061d9391e808 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 7 May 2020 16:13:24 +0300 Subject: [PATCH 107/337] Streamer: Fix wrong argument order when rendering activities to authenticated user Closes #1747 --- lib/pleroma/web/mastodon_api/websocket_handler.ex | 2 +- lib/pleroma/web/views/streamer_view.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 6ef3fe2dd..e2ffd02d0 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -78,7 +78,7 @@ def websocket_info({:render_with_user, view, template, item}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, user, item)}, %{state | user: user}) + websocket_info({:text, view.render(template, item, user)}, %{state | user: user}) else {:ok, state} end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 443868878..237b29ded 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -25,7 +25,7 @@ def render("update.json", %Activity{} = activity, %User{} = user) do |> Jason.encode!() end - def render("notification.json", %User{} = user, %Notification{} = notify) do + def render("notification.json", %Notification{} = notify, %User{} = user) do %{ event: "notification", payload: From ea01e647df4466975b9382f123f0a2aa35ebfe76 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 7 May 2020 09:13:43 -0500 Subject: [PATCH 108/337] Test Direct, Public, and Favorite notifications with privacy option --- test/web/push/impl_test.exs | 60 +++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 3de911810..b855d72ba 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -193,7 +193,7 @@ test "renders title for create activity with direct visibility" do end describe "build_content/3" do - test "returns info content for direct message with enabled privacy option" do + test "hides details for notifications when privacy option enabled" do user = insert(:user, nickname: "Bob") user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true}) @@ -211,9 +211,35 @@ test "returns info content for direct message with enabled privacy option" do assert Impl.build_content(notif, actor, object) == %{ body: "New Direct Message" } + + {:ok, activity} = + CommonAPI.post(user, %{ + "visibility" => "public", + "status" => " "public", + "status" => + "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." + }) + + notif = insert(:notification, user: user2, activity: activity) + + actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) + object = Object.normalize(activity) + + assert Impl.build_content(notif, actor, object) == %{ + body: + "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini...", + title: "New Mention" + } + + {:ok, activity} = CommonAPI.favorite(user, activity.id) + + notif = insert(:notification, user: user2, activity: activity) + + actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) + object = Object.normalize(activity) + + assert Impl.build_content(notif, actor, object) == %{ + body: "@Bob has favorited your post", + title: "New Favorite" + } end end end From a081135365c2b9d7bc81ee84baffbc3c2be68e8c Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 8 May 2020 12:06:24 +0300 Subject: [PATCH 109/337] revert mix.lock --- mix.lock | 54 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/mix.lock b/mix.lock index 11234ae14..c400202b7 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,8 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, - "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, + "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, @@ -19,47 +19,47 @@ "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, - "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"}, + "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "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.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, - "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, + "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, + "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.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"}, + "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, + "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, - "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "f3b4b184dcdd5f356b7c26c6cd72ab0918ba9dfb4061ccfaf519e562942af87b"}, + "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, - "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, + "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, + "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"}, - "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm", "f7d97341e536f95b96eef2988d6d4230f7262cf239cda0e2e63123ee0b717222"}, + "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, - "httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "89149056039084024a284cd703b2d1900d584958dba432132cb21ef35aed7487"}, + "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.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"}, - "joken": {:hex, :joken, "2.1.0", "bf21a73105d82649f617c5e59a7f8919aa47013d2519ebcc39d998d8d12adda9", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "eb02df7d5526df13063397e051b926b7006d5986d66f399eefc474f560cdad6a"}, - "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"}, + "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"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm", "1feaf05ee886815ad047cad7ede17d6910710986148ae09cf73eee2989717b81"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, @@ -71,41 +71,39 @@ "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, + "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, "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": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]}, "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.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [: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", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fe15d9fee5b82f5e64800502011ffe530650d42e1710ae9b14bc4c9be38bf303"}, - "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b01b3d6d39731ab18aa548d928b5796166d2500755f553725cfe967bafba7d9"}, + "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"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, + "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6cd8ddd1bd1fbfa54d3fc61d4719c2057dae67615395d58d40437a919a46f132"}, - "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, "pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"}, - "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, + "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "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"}, - "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm", "6de553ba9ac0668d3728b699d5065543f3e40c854154017461ee8c09038752da"}, "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", "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"}, - "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, - "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm", "769ddfabd0d2a16f3f9c17eb7509951e0ca4f68363fb26f2ee51a8ec4a49881a"}, + "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, "tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]}, "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, From 6acbe45eb211286e747143f6bd6edaa5c2126657 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 8 May 2020 11:30:31 +0200 Subject: [PATCH 110/337] Builder: Extract common features of likes and reactions. --- lib/pleroma/web/activity_pub/builder.ex | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index f6544d3f5..922a444a9 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()} def emoji_react(actor, object, emoji) do - with {:ok, data, meta} <- like(actor, object) do + with {:ok, data, meta} <- object_action(actor, object) do data = data |> Map.put("content", emoji) @@ -64,6 +64,17 @@ def delete(actor, object_id) do @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()} def like(actor, object) do + with {:ok, data, meta} <- object_action(actor, object) do + data = + data + |> Map.put("type", "Like") + + {:ok, data, meta} + end + end + + @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()} + defp object_action(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. @@ -85,7 +96,6 @@ def like(actor, object) do %{ "id" => Utils.generate_activity_id(), "actor" => actor.ap_id, - "type" => "Like", "object" => object.data["id"], "to" => to, "cc" => cc, From 4d71c4b8051d5cf54f37903091aed7f4d5c1ddd9 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 8 May 2020 12:33:01 +0300 Subject: [PATCH 111/337] fixed 'source' object in verify_credentials --- lib/pleroma/web/mastodon_api/views/account_view.ex | 5 ++++- test/web/mastodon_api/views/account_view_test.exs | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 420bd586f..b7cdb52b1 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -261,7 +261,10 @@ defp do_render("show.json", %{user: user} = opts) do defp prepare_user_bio(%User{bio: ""}), do: "" defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do - bio |> String.replace(~r(
), "\n") |> Pleroma.HTML.strip_tags() + bio + |> String.replace(~r(
), "\n") + |> Pleroma.HTML.strip_tags() + |> HtmlEntities.decode() end defp prepare_user_bio(_), do: "" diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index 5fb162141..375f0103a 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -31,7 +31,7 @@ test "Represent a user account" do nickname: "shp@shitposter.club", name: ":karjalanpiirakka: shp", bio: - "valid html. a
b
c
d
f", + "valid html. a
b
c
d
f '&<>\"", inserted_at: ~N[2017-08-15 15:47:06.597036], emoji: %{"karjalanpiirakka" => "/file.png"} }) @@ -46,7 +46,7 @@ test "Represent a user account" do followers_count: 3, following_count: 0, statuses_count: 5, - note: "valid html. a
b
c
d
f", + note: "valid html. a
b
c
d
f '&<>"", url: user.ap_id, avatar: "http://localhost:4001/images/avi.png", avatar_static: "http://localhost:4001/images/avi.png", @@ -63,7 +63,7 @@ test "Represent a user account" do fields: [], bot: false, source: %{ - note: "valid html. a\nb\nc\nd\nf", + note: "valid html. a\nb\nc\nd\nf '&<>\"", sensitive: false, pleroma: %{ actor_type: "Person", From f1274c3326207ecba5086ee28f721b43a29eb14c Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 8 May 2020 11:41:13 +0200 Subject: [PATCH 112/337] Transmogrifier tests: Remove double tests. --- test/web/activity_pub/transmogrifier_test.exs | 81 ------------------- 1 file changed, 81 deletions(-) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 14c0f57ae..d783f57d2 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -325,87 +325,6 @@ test "it cleans up incoming notices which are not really DMs" do assert object_data["cc"] == to end - test "it works for incoming emoji reaction undos" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌") - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", reaction_activity.data["id"]) - |> Map.put("actor", user.ap_id) - - {:ok, activity} = Transmogrifier.handle_incoming(data) - - assert activity.actor == user.ap_id - assert activity.data["id"] == data["id"] - assert activity.data["type"] == "Undo" - end - - test "it returns an error for incoming unlikes wihout a like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert Transmogrifier.handle_incoming(data) == :error - end - - test "it works for incoming unlikes with an existing like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" - end - - test "it works for incoming unlikes with an existing like activity and a compact object" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data["id"]) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" - end - test "it works for incoming emoji reactions" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) From 7e9aaa0d0221311d831161d977c8b0e2a55b3439 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 8 May 2020 11:43:07 +0200 Subject: [PATCH 113/337] Transmogrifier tests: Remove more double tests. --- test/web/activity_pub/transmogrifier_test.exs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index d783f57d2..2914c90ea 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -325,43 +325,6 @@ test "it cleans up incoming notices which are not really DMs" do assert object_data["cc"] == to end - test "it works for incoming emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "EmojiReact" - assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" - assert data["object"] == activity.data["object"] - assert data["content"] == "👌" - end - - test "it reject invalid emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction-too-long.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert {:error, _} = Transmogrifier.handle_incoming(data) - - data = - File.read!("test/fixtures/emoji-reaction-no-emoji.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert {:error, _} = Transmogrifier.handle_incoming(data) - end - test "it works for incoming announces" do data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() From 287f781808c88f43f5689508b5aa21f6639b9d16 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 8 May 2020 16:54:53 +0300 Subject: [PATCH 114/337] user deletion --- lib/pleroma/user.ex | 28 +++++++++++++++------ test/user_test.exs | 27 ++++++++++++++++++++ test/web/activity_pub/side_effects_test.exs | 25 ++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 2a6a23fec..278129ad2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1431,6 +1431,25 @@ def delete(%User{} = user) do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end + defp delete_and_invalidate_cache(%User{} = user) do + invalidate_cache(user) + Repo.delete(user) + end + + defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) + + defp delete_or_deactivate(%User{local: true} = user) do + status = account_status(user) + + if status == :confirmation_pending do + delete_and_invalidate_cache(user) + else + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() + end + end + def perform(:force_password_reset, user), do: force_password_reset(user) @spec perform(atom(), User.t()) :: {:ok, User.t()} @@ -1452,14 +1471,7 @@ def perform(:delete, %User{} = user) do delete_user_activities(user) - if user.local do - user - |> change(%{deactivated: true, email: nil}) - |> update_and_set_cache() - else - invalidate_cache(user) - Repo.delete(user) - end + delete_or_deactivate(user) end def perform(:deactivate_async, user, status), do: deactivate(user, status) diff --git a/test/user_test.exs b/test/user_test.exs index a3c75aa9b..96116fca6 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1171,6 +1171,33 @@ test "it deactivates a user, all follow relationships and all activities", %{use end end + describe "delete/1 when confirmation is pending" do + setup do + user = insert(:user, confirmation_pending: true) + {:ok, user: user} + end + + test "deletes user from database when activation required", %{user: user} do + clear_config([:instance, :account_activation_required], true) + + {:ok, job} = User.delete(user) + {:ok, _} = ObanHelpers.perform(job) + + refute User.get_cached_by_id(user.id) + refute User.get_by_id(user.id) + end + + test "deactivates user when activation is not required", %{user: user} do + clear_config([:instance, :account_activation_required], false) + + {:ok, job} = User.delete(user) + {:ok, _} = ObanHelpers.perform(job) + + assert %{deactivated: true} = User.get_cached_by_id(user.id) + assert %{deactivated: true} = User.get_by_id(user.id) + end + end + test "get_public_key_for_ap_id fetches a user that's not in the db" do assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index b29a7a7be..5c06dc864 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -99,6 +99,31 @@ test "creates a notification", %{emoji_react: emoji_react, poster: poster} do end end + describe "delete users with confirmation pending" do + setup do + user = insert(:user, confirmation_pending: true) + {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) + {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) + {:ok, delete: delete_user, user: user} + end + + test "when activation is not required", %{delete: delete, user: user} do + clear_config([:instance, :account_activation_required], false) + {:ok, _, _} = SideEffects.handle(delete) + ObanHelpers.perform_all() + + assert User.get_cached_by_id(user.id).deactivated + end + + test "when activation is required", %{delete: delete, user: user} do + clear_config([:instance, :account_activation_required], true) + {:ok, _, _} = SideEffects.handle(delete) + ObanHelpers.perform_all() + + refute User.get_cached_by_id(user.id) + end + end + describe "Undo objects" do setup do poster = insert(:user) From 0e1bda55e856cd97b82ed393b719653872c93e34 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 8 May 2020 21:33:56 +0300 Subject: [PATCH 115/337] PleromaFE bundle dropping requirement for embedded account relationships in statuses / notifications. https://git.pleroma.social/pleroma/pleroma-fe/-/commit/7a0e554daf843fe9e98053e79ec0114c380ededb --- priv/static/index.html | 2 +- .../static/font/fontello.1588419330867.woff | Bin 13836 -> 0 bytes .../static/font/fontello.1588419330867.woff2 | Bin 11712 -> 0 bytes ...9330867.eot => fontello.1588947937982.eot} | Bin 22752 -> 22976 bytes ...9330867.svg => fontello.1588947937982.svg} | 2 ++ ...9330867.ttf => fontello.1588947937982.ttf} | Bin 22584 -> 22808 bytes .../static/font/fontello.1588947937982.woff | Bin 0 -> 13988 bytes .../static/font/fontello.1588947937982.woff2 | Bin 0 -> 11816 bytes ...9330867.css => fontello.1588947937982.css} | Bin 3378 -> 3421 bytes priv/static/static/fontello.json | 6 ++++++ ...9fca99e19.js => 2.18e4adec273c4ce867a8.js} | Bin 2190 -> 2190 bytes ...9.js.map => 2.18e4adec273c4ce867a8.js.map} | Bin 7763 -> 7763 bytes .../static/js/app.996428ccaaaa7f28cb8d.js | Bin 0 -> 1079195 bytes .../static/js/app.996428ccaaaa7f28cb8d.js.map | Bin 0 -> 1643581 bytes .../static/js/app.fa89b90e606f4facd209.js | Bin 1075836 -> 0 bytes .../static/js/app.fa89b90e606f4facd209.js.map | Bin 1635217 -> 0 bytes ...js => vendors~app.561a1c605d1dfb0e6f74.js} | Bin 411233 -> 411235 bytes .../vendors~app.561a1c605d1dfb0e6f74.js.map | Bin 0 -> 1737881 bytes .../vendors~app.8aa781e6dd81307f544b.js.map | Bin 1737947 -> 0 bytes priv/static/sw-pleroma.js | Bin 31752 -> 31752 bytes 20 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 priv/static/static/font/fontello.1588419330867.woff delete mode 100644 priv/static/static/font/fontello.1588419330867.woff2 rename priv/static/static/font/{fontello.1588419330867.eot => fontello.1588947937982.eot} (90%) rename priv/static/static/font/{fontello.1588419330867.svg => fontello.1588947937982.svg} (98%) rename priv/static/static/font/{fontello.1588419330867.ttf => fontello.1588947937982.ttf} (90%) create mode 100644 priv/static/static/font/fontello.1588947937982.woff create mode 100644 priv/static/static/font/fontello.1588947937982.woff2 rename priv/static/static/{fontello.1588419330867.css => fontello.1588947937982.css} (88%) rename priv/static/static/js/{2.1c407059cd79fca99e19.js => 2.18e4adec273c4ce867a8.js} (80%) rename priv/static/static/js/{2.1c407059cd79fca99e19.js.map => 2.18e4adec273c4ce867a8.js.map} (99%) create mode 100644 priv/static/static/js/app.996428ccaaaa7f28cb8d.js create mode 100644 priv/static/static/js/app.996428ccaaaa7f28cb8d.js.map delete mode 100644 priv/static/static/js/app.fa89b90e606f4facd209.js delete mode 100644 priv/static/static/js/app.fa89b90e606f4facd209.js.map rename priv/static/static/js/{vendors~app.8aa781e6dd81307f544b.js => vendors~app.561a1c605d1dfb0e6f74.js} (87%) create mode 100644 priv/static/static/js/vendors~app.561a1c605d1dfb0e6f74.js.map delete mode 100644 priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map diff --git a/priv/static/index.html b/priv/static/index.html index 4fac5c100..b37cbaa67 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/font/fontello.1588419330867.woff b/priv/static/static/font/fontello.1588419330867.woff deleted file mode 100644 index 2bf4cbc16bfaf89c8e663c3ecdfdea1728fe781d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13836 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 adN{Y<|J>^Vp>Qot4UGdp^rZL;0Q?_|L~vFB diff --git a/priv/static/static/font/fontello.1588419330867.woff2 b/priv/static/static/font/fontello.1588419330867.woff2 deleted file mode 100644 index a31bf3f29df33e61b0472ae87e44d05efe32d05a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11712 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 delta 477 zcmX@Gneo9!Mz#kL3=9?#6WPpIisc>LCOUN2S1~X!1~4!%geB)D7WCBLUkBu`U|?YR zm0VV$!1n*&7X}6&1)#h_T0wel*Te~tK)wS519w4sVsQabTmZQbuxrzYg%`$)j>?c`(Y*!y3UnL{Aq~dH}*#DbA0gypYa`KZC z_e?!y%)sCl0_2zECRP+M_VF$Q@=pNy3VDgSsb}Yk0}XV0!N9iqgb62WF&A&1&&u~GcbHIfnWv;28PWE?48z|mpZ>^l5um8FOKK8`O3h}4D=8K z!>!9)TVeF%4Q}R)mXlw&-JN{Wy@Inx-W=?B%gufs+{{3UDDR@p=e##FZf^Ir + + diff --git a/priv/static/static/font/fontello.1588419330867.ttf b/priv/static/static/font/fontello.1588947937982.ttf similarity index 90% rename from priv/static/static/font/fontello.1588419330867.ttf rename to priv/static/static/font/fontello.1588947937982.ttf index 7dc4f108b7be4e5c132da254f00e70a6463d8102..443801c4fc0b48e45cf0ae16472319b6b95eb52a 100644 GIT binary patch delta 720 zcmZuuT}V@57=FI*?3|gKHPm#%m}~ybv_L8p#R4;6Gz@kY!ah4eSWnkypksCldSHVNb>+^oqwFlZDyx-3rC2FdepdFh zM$N2^Xk*%h7S@V+&X4PhT4~2st>!iEa4?!x{#F{b%KoqN%RD1`X#+{sIAUT=V-VPJ9W=b^6Y0kA3caPeE?f|r(1nT7vGyh|3Z3KT1rcIOV&F)DGoAutQ3k7}&D>~eFo-&Iu`a#X+{c_P&62&_YEhbf0vMFe{TBc89*DS>NjB&w{diC(edgVZb*f%TvCA0+a7Wc|71JMlQ3)Z^z>S zy)oQRq(5xb2W6hfB^c4eWJ2v7-{F=wZvTr*M^62QK`7UrzqM_N{=@v>q=yaVmC4U+ sE+6z=Yp`2?wBMz)_JKp{2cJQpcq@?RY*?KRs!S3yawr{9t5aU%9~x-Cj{pDw delta 495 zcmbQSiE+mU#tF*xRSXP_0SpWbVad6P1wHlm*8%w}7#LW7C6|>bu>Jq{g@M6G0VuDK zR*;_CHE}{Dknh02z+I4@SX=-U7Xb1lfHX&XPGy?Ok43Hw3~Wmn80_*gQWH}ojUs_^ zt|CBrvkagB`$-lc+tmliSINjNsW{sg_WveO0A$dUoc!d(JyTB^GcdS?0Qn`ki4_Hm zeZ0$n{1ZUFLSABS>e;#CKm*-gFfcHkEyyn}S$Y1K76XIl4j@0SpeVKAsiXED28Qqr z6MsZA&Ym2?nC|YyzzEdL!05uj3})#7X)d60F9sGykh|3w6c|_-*j}i-P=8_c!sJ8$ zhm#*3e0cid^M`*Qg+5ArwEO7z(dA>xWELjP$@)xMs*FG#c~Dru0D?uBaB>qLF^Yeh*z_bBG0)fS59+qw<@uv(7AAy*`oPpt!2?R4(FfeT1!PaTL`LEM^ zCK)#e`Qmtfo39Mq%s?+QFxxcR=1B@Y0vQ;sA6 diff --git a/priv/static/static/font/fontello.1588947937982.woff b/priv/static/static/font/fontello.1588947937982.woff new file mode 100644 index 0000000000000000000000000000000000000000..e96fea75766b8087fd2e064329a1e21500d2e522 GIT binary patch literal 13988 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 X|4Xk6sLZuE1q3D#oGa-A5YYbt*N?ji literal 0 HcmV?d00001 diff --git a/priv/static/static/font/fontello.1588947937982.woff2 b/priv/static/static/font/fontello.1588947937982.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..50318a6703694b2888e6be5f00b39e91435e3105 GIT binary patch literal 11816 zcmV+@F4xg_Pew8T0RR9104^v14*&oF09hCS04>k}0RR9100000000000000000000 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 Wp?_x}4h)=p4)p!;5VXmQ10De4ugL!Z literal 0 HcmV?d00001 diff --git a/priv/static/static/fontello.1588419330867.css b/priv/static/static/fontello.1588947937982.css similarity index 88% rename from priv/static/static/fontello.1588419330867.css rename to priv/static/static/fontello.1588947937982.css index 198eff184daf831f2b1b626163da90578b705891..d3d77a8b5d9234e05454b33fb7e8008f94b312d2 100644 GIT binary patch delta 104 zcmdlabysSFGpD7AxuvnWrG?Q%A9W;7jXe_Qh$(_InTgRDiD$=XhTv_kWz6DcEzU?R QN}c?XMUv5A@@<}Q0C>V1RR910 delta 92 zcmcaBwMlA%GpC86rLnPrg_-$8A9W;7jXe_Qh$(_InTgRDiD$=XhTv_kWz6E9EX3;# E05l&KF#rGn diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json index 5963b68b4..7f0e7cdd5 100755 --- a/priv/static/static/fontello.json +++ b/priv/static/static/fontello.json @@ -346,6 +346,12 @@ "code": 59427, "src": "fontawesome" }, + { + "uid": "4aad6bb50b02c18508aae9cbe14e784e", + "css": "share", + "code": 61920, + "src": "fontawesome" + }, { "uid": "8b80d36d4ef43889db10bc1f0dc9a862", "css": "user", diff --git a/priv/static/static/js/2.1c407059cd79fca99e19.js b/priv/static/static/js/2.18e4adec273c4ce867a8.js similarity index 80% rename from priv/static/static/js/2.1c407059cd79fca99e19.js rename to priv/static/static/js/2.18e4adec273c4ce867a8.js index 14018d92ab84186c7fe80b765c529fcb50ebce38..d191aa852fe1c32896605718d9019b33b3b3ba46 100644 GIT binary patch delta 73 zcmeAZ>=T?|&1gQ+Mw-!LVuUxN@x&!cK-O_NMx%|-T$vaxH|sDjU}7}ie3o@NlaxiO cNn%QBvXQxQvPp8Pg_(Jxg=T?|&1gE&Mw-!VVuUxN$;2f}K-O_NM&pgoT$vcnH|sDjU}7}ge3o@NlT@;a cfw_UHWpawSWmV!Z diff --git a/priv/static/static/js/2.1c407059cd79fca99e19.js.map b/priv/static/static/js/2.18e4adec273c4ce867a8.js.map similarity index 99% rename from priv/static/static/js/2.1c407059cd79fca99e19.js.map rename to priv/static/static/js/2.18e4adec273c4ce867a8.js.map index cfee79ea8cd351017ddd2c6f8fbf6f4899e66322..a7f98bfef87cae26f6c1ef7e006013a98feabe3a 100644 GIT binary patch delta 32 ncmca?bJ=FYT~1+(RFlM%)MO)b<7AWMR0}inM2pQIIFm&H%w!9K delta 32 ncmca?bJ=FYT~6U-69aPtQ_JKObIY{kL`%z5L(9z{IFm&H#8C?= diff --git a/priv/static/static/js/app.996428ccaaaa7f28cb8d.js b/priv/static/static/js/app.996428ccaaaa7f28cb8d.js new file mode 100644 index 0000000000000000000000000000000000000000..00f3a28e008b2676b0ef891c50f761c696c9c4c1 GIT binary patch literal 1079195 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%0Ai*ep*<(VCmH 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≈Y8YK&DqLS;^Y~N@?Z=#3w;` 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

1cL4 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%0D-7uz&H?e;pkx=i40T9VSF4=X*Y8307XUu!T^o~ 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� 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

|l&P6_bPr2kA)b`&ue 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%0DZii|>Qt>>@Whiqa)^;O=^ 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