From 55d086b52077a220aef60c8d9071aea990431d74 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Wed, 27 Mar 2019 22:09:39 +0300 Subject: [PATCH 01/36] Notification controls Allow users to configure whether they want to receive notifications from people they follow / who follow them, people from remote / local instances --- lib/pleroma/notification.ex | 65 ++++++++++++++++++++++++++++++---- lib/pleroma/user/info.ex | 4 +++ test/notification_test.exs | 69 +++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index cac10f24a..caa6b755e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -122,13 +122,7 @@ def create_notifications(_), do: {:ok, []} # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user) do - unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or - CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or - (activity.data["type"] == "Follow" and - Enum.any?(Notification.for_user(user), fn notif -> - notif.activity.data["type"] == "Follow" and - notif.activity.data["actor"] == activity.data["actor"] - end)) do + unless skip?(activity, user) do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) Pleroma.Web.Streamer.stream("user", notification) @@ -154,4 +148,61 @@ def get_notified_from_activity( end def get_notified_from_activity(_, _local_only), do: [] + + def skip?(activity, user) do + [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed] + |> Enum.any?(&skip?(&1, activity, user)) + end + + def skip?(:self, activity, user) do + activity.data["actor"] == user.ap_id + end + + def skip?(:blocked, activity, user) do + actor = activity.data["actor"] + User.blocks?(user, %{ap_id: actor}) + end + + def skip?(:local, %{local: true}, user) do + user.info.notification_settings["local"] == false + end + + def skip?(:local, %{local: false}, user) do + user.info.notification_settings["remote"] == false + end + + def skip?(:muted, activity, user) do + actor = activity.data["actor"] + + User.mutes?(user, %{ap_id: actor}) or + CommonAPI.thread_muted?(user, activity) + end + + def skip?( + :followers, + activity, + %{info: %{notification_settings: %{"followers" => false}}} = user + ) do + actor = activity.data["actor"] + follower = User.get_cached_by_ap_id(actor) + User.following?(follower, user) + end + + def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do + actor = activity.data["actor"] + followed = User.get_by_ap_id(actor) + User.following?(user, followed) + end + + def skip?(:recently_followed, activity, user) do + actor = activity.data["actor"] + + Notification.for_user(user) + |> Enum.any?(fn + %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true + _ -> false + end) + end + + def skip?(_, _, _), do: false end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 740a46727..c36efa126 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -40,6 +40,10 @@ defmodule Pleroma.User.Info do field(:pinned_activities, {:array, :string}, default: []) field(:flavour, :string, default: nil) + field(:notification_settings, :map, + default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true} + ) + # Found in the wild # ap_id -> Where is this used? # bio -> Where is this used? diff --git a/test/notification_test.exs b/test/notification_test.exs index 12b4292aa..89d06b3a2 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -41,6 +41,75 @@ test "it doesn't create a notification for user if the user blocks the activity assert nil == Notification.create_notification(activity, user) end + test "it doesn't create a notificatin for the user if the user mutes the activity author" do + muter = insert(:user) + muted = insert(:user) + {:ok, _} = User.mute(muter, muted) + muter = Repo.get(User, muter.id) + {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"}) + + assert nil == Notification.create_notification(activity, muter) + end + + test "it doesn't create a notification for an activity from a muted thread" do + muter = insert(:user) + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"}) + CommonAPI.add_mute(muter, activity) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "Hi @#{muter.nickname}", + "in_reply_to_status_id" => activity.id + }) + + assert nil == Notification.create_notification(activity, muter) + end + + test "it disables notifications from people on remote instances" do + user = insert(:user, info: %{notification_settings: %{"remote" => false}}) + other_user = insert(:user) + + create_activity = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "actor" => other_user.ap_id, + "object" => %{ + "type" => "Note", + "content" => "Hi @#{user.nickname}", + "attributedTo" => other_user.ap_id + } + } + + {:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity) + assert nil == Notification.create_notification(activity, user) + end + + test "it disables notifications from people on the local instance" do + user = insert(:user, info: %{notification_settings: %{"local" => false}}) + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) + assert nil == Notification.create_notification(activity, user) + end + + test "it disables notifications from followers" do + follower = insert(:user) + followed = insert(:user, info: %{notification_settings: %{"followers" => false}}) + User.follow(follower, followed) + {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"}) + assert nil == Notification.create_notification(activity, followed) + end + + test "it disables notifications from people the user follows" do + follower = insert(:user, info: %{notification_settings: %{"follows" => false}}) + followed = insert(:user) + User.follow(follower, followed) + follower = Repo.get(User, follower.id) + {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"}) + assert nil == Notification.create_notification(activity, follower) + end + test "it doesn't create a notification for user if he is the activity author" do activity = insert(:note_activity) author = User.get_by_ap_id(activity.data["actor"]) From cd90695a349f33b84f287794bae6070e9eec446a Mon Sep 17 00:00:00 2001 From: eugenijm Date: Thu, 28 Mar 2019 14:52:09 +0300 Subject: [PATCH 02/36] Add PUT /api/pleroma/notification_settings endpoint --- docs/Pleroma-API.md | 14 ++++++++++-- lib/pleroma/notification.ex | 12 +++++----- lib/pleroma/user.ex | 8 +++++++ lib/pleroma/user/info.ex | 13 +++++++++++ .../web/mastodon_api/views/account_view.ex | 22 +++++++++++++------ lib/pleroma/web/router.ex | 1 + .../controllers/util_controller.ex | 6 +++++ test/web/mastodon_api/account_view_test.exs | 14 ++++++++++++ test/web/twitter_api/util_controller_test.exs | 21 ++++++++++++++++++ 9 files changed, 95 insertions(+), 16 deletions(-) diff --git a/docs/Pleroma-API.md b/docs/Pleroma-API.md index 478c9d874..bb7ed3744 100644 --- a/docs/Pleroma-API.md +++ b/docs/Pleroma-API.md @@ -27,14 +27,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Method: `GET` * Authentication: not required * Params: none -* Response: Provider specific JSON, the only guaranteed parameter is `type` +* Response: Provider specific JSON, the only guaranteed parameter is `type` * Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}` ## `/api/pleroma/delete_account` ### Delete an account * Method `POST` * Authentication: required -* Params: +* Params: * `password`: user's password * Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise * Example response: `{"error": "Invalid password."}` @@ -116,3 +116,13 @@ See [Admin-API](Admin-API.md) * Params: * `id`: notifications's id * Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` +## `/api/pleroma/notification_settings` +### Updates user notification settings +* Method `PUT` +* Authentication: required +* Params: + * `followers`: BOOLEAN field, receives notifications from followers + * `follows`: BOOLEAN field, receives notifications from people the user follows + * `remote`: BOOLEAN field, receives notifications from people on remote instances + * `local`: BOOLEAN field, receives notifications from people on the local instance +* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index caa6b755e..14de1a097 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -163,13 +163,11 @@ def skip?(:blocked, activity, user) do User.blocks?(user, %{ap_id: actor}) end - def skip?(:local, %{local: true}, user) do - user.info.notification_settings["local"] == false - end + def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}), + do: true - def skip?(:local, %{local: false}, user) do - user.info.notification_settings["remote"] == false - end + def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}), + do: true def skip?(:muted, activity, user) do actor = activity.data["actor"] @@ -194,7 +192,7 @@ def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => fa User.following?(user, followed) end - def skip?(:recently_followed, activity, user) do + def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do actor = activity.data["actor"] Notification.for_user(user) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 728b00a56..73c2a82a7 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1082,6 +1082,14 @@ def deactivate(%User{} = user, status \\ true) do update_and_set_cache(cng) end + def update_notification_settings(%User{} = user, settings \\ %{}) do + info_changeset = User.Info.update_notification_settings(user.info, settings) + + change(user) + |> put_embed(:info, info_changeset) + |> update_and_set_cache() + end + def delete(%User{} = user) do {:ok, user} = User.deactivate(user) diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index c36efa126..33fd77b02 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -61,6 +61,19 @@ def set_activation_status(info, deactivated) do |> validate_required([:deactivated]) end + def update_notification_settings(info, settings) do + notification_settings = + info.notification_settings + |> Map.merge(settings) + |> Map.take(["remote", "local", "followers", "follows"]) + + params = %{notification_settings: notification_settings} + + info + |> cast(params, [:notification_settings]) + |> validate_required([:notification_settings]) + end + def add_to_note_count(info, number) do set_note_count(info, info.note_count + number) end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b5f3bbb9d..25899e491 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -117,13 +117,15 @@ defp do_render("account.json", %{user: user} = opts) do }, # Pleroma extension - pleroma: %{ - confirmation_pending: user_info.confirmation_pending, - tags: user.tags, - is_moderator: user.info.is_moderator, - is_admin: user.info.is_admin, - relationship: relationship - } + pleroma: + %{ + confirmation_pending: user_info.confirmation_pending, + tags: user.tags, + is_moderator: user.info.is_moderator, + is_admin: user.info.is_admin, + relationship: relationship + } + |> with_notification_settings(user, opts[:for]) } end @@ -132,4 +134,10 @@ defp username_from_nickname(string) when is_binary(string) do end defp username_from_nickname(_), do: nil + + defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do + Map.put(data, :notification_settings, user.info.notification_settings) + end + + defp with_notification_settings(data, _, _), do: data end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 32e5f7644..36cbf0f57 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -182,6 +182,7 @@ defmodule Pleroma.Web.Router do post("/change_password", UtilController, :change_password) post("/delete_account", UtilController, :delete_account) + put("/notification_settings", UtilController, :update_notificaton_settings) end scope [] do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index faa733fec..2708299cb 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -269,6 +269,12 @@ def emoji(conn, _params) do json(conn, Enum.into(Emoji.get_all(), %{})) end + def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do + with {:ok, _} <- User.update_notification_settings(user, params) do + json(conn, %{status: "success"}) + end + end + def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do follow_import(conn, %{"list" => File.read!(listfile.path)}) end diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index 6dc60afe9..aa6a1e960 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -71,6 +71,20 @@ test "Represent a user account" do assert expected == AccountView.render("account.json", %{user: user}) end + test "Represent the user account for the account owner" do + user = insert(:user) + + notification_settings = %{ + "remote" => true, + "local" => true, + "followers" => true, + "follows" => true + } + + assert %{pleroma: %{notification_settings: ^notification_settings}} = + AccountView.render("account.json", %{user: user, for: user}) + end + test "Represent a Service(bot) account" do user = insert(:user, %{ diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 832fdc096..426508353 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -3,6 +3,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do alias Pleroma.Notification alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -74,6 +75,26 @@ test "it marks a single notification as read", %{conn: conn} do end end + describe "PUT /api/pleroma/notification_settings" do + test "it updates notification settings", %{conn: conn} do + user = insert(:user) + + conn + |> assign(:user, user) + |> put("/api/pleroma/notification_settings", %{ + "remote" => false, + "followers" => false, + "bar" => 1 + }) + |> json_response(:ok) + + user = Repo.get(User, user.id) + + assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} == + user.info.notification_settings + end + end + describe "GET /api/statusnet/config.json" do test "returns the state of safe_dm_mentions flag", %{conn: conn} do option = Pleroma.Config.get([:instance, :safe_dm_mentions]) From 79910ce5cc85f83e58c5f49e53b4b6263925d110 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 13:49:33 +0100 Subject: [PATCH 03/36] Add API endpoints and user column for subscription --- lib/pleroma/user.ex | 20 ++++++++++++++++ lib/pleroma/user/info.ex | 17 +++++++++++++ lib/pleroma/web/router.ex | 3 +++ lib/pleroma/web/twitter_api/twitter_api.ex | 12 ++++++++++ .../web/twitter_api/twitter_api_controller.ex | 24 +++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 5012aef77..b7cab8642 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -923,6 +923,26 @@ def unmute(muter, %{ap_id: ap_id}) do update_and_set_cache(cng) end + def subscribe(subscriber, %{ap_id: ap_id}) do + info_cng = + subscriber.info + |> User.Info.add_to_subscriptions(ap_id) + + change(subscriber) + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end + + def unsubscribe(unsubscriber, %{ap_id: ap_id}) do + info_cng = + subscriber.info + |> User.Info.remove_from_subscriptions(ap_id) + + change(subscriber) + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end + def block(blocker, %User{ap_id: ap_id} = blocked) do # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) blocker = diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 740a46727..30c594f64 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -22,6 +22,7 @@ defmodule Pleroma.User.Info do field(:domain_blocks, {:array, :string}, default: []) field(:mutes, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: []) + field(:subscribed_to, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) @@ -93,6 +94,14 @@ def set_blocks(info, blocks) do |> validate_required([:blocks]) end + def set_subscriptions(info, subscriptions) do + params = %{subscriptions: subscriptions} + + info + |> cast(params, [:subscribed_to]) + |> validate_required([:subscribed_to]) + end + def add_to_mutes(info, muted) do set_mutes(info, Enum.uniq([muted | info.mutes])) end @@ -109,6 +118,14 @@ def remove_from_block(info, blocked) do set_blocks(info, List.delete(info.blocks, blocked)) end + def add_to_subscriptions(info, subscribed) do + set_subscriptions(info, Enum.uniq([subscribed | info.subscribed_to])) + end + + def remove_from_subscriptions(info, subscribed) do + set_subscriptions(info, List.delete(info.subscribed_to, subscribed)) + end + def set_domain_blocks(info, domain_blocks) do params = %{domain_blocks: domain_blocks} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 605a327fc..10037b24f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -500,6 +500,9 @@ defmodule Pleroma.Web.Router do post("/blocks/create", TwitterAPI.Controller, :block) post("/blocks/destroy", TwitterAPI.Controller, :unblock) + + post("/subscriptions/create", TwitterAPI.Controller, :subscribe) + post("/subscriptions/destroy", TwitterAPI.Controller, :unsubscribe) end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 9b081a316..c0a0a500f 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -59,6 +59,18 @@ def unblock(%User{} = blocker, params) do end end + def subscribe(%User{} = subscriber, params) do + with {:ok, %User{} = subscribed} <- get_user(params) do + User.subscribe(subscriber, subscribed) + end + end + + def unsubscribe(%User{} = unsubscriber, params) do + with {:ok, %User{} = unsubscribed} <- get_user(params) do + User.unsubscribe(unsubscriber, unsubscribed) + end + end + def repeat(%User{} = user, ap_id_or_id) do with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index a7ec9949c..af4c0a60e 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -269,6 +269,30 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do end end + def subscribe(%{assigns: %{user: user}} = conn, params) do + case TwitterAPI.subscribe(user, params) do + {:ok, user, subscribed} -> + conn + |> put_view(UserView) + |> render("show.json", %{user: subscribed, for: user}) + + {:error, msg} -> + forbidden_json_reply(conn, msg) + end + end + + def unsubscribe(%{assigns: %{user: user}} = conn, params) do + case TwitterAPI.unsubscribe(user, params) do + {:ok, user, unsubscribed} -> + conn + |> put_view(UserView) + |> render("show.json", %{user: unsubscribed, for: user}) + + {:error, msg} -> + forbidden_json_reply(conn, msg) + end + end + def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do From 007762e767fcd734bacd9700f5268591854a446a Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 14:20:13 +0100 Subject: [PATCH 04/36] Add notification checks --- lib/pleroma/notification.ex | 1 + lib/pleroma/user.ex | 7 +++++-- lib/pleroma/user/info.ex | 10 +++++----- lib/pleroma/web/common_api/utils.ex | 16 ++++++++++++++++ lib/pleroma/web/twitter_api/twitter_api.ex | 1 + 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index cac10f24a..7c5856438 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -148,6 +148,7 @@ def get_notified_from_activity( [] |> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity) + |> Utils.maybe_notify_subscribers(activity) |> Enum.uniq() User.get_users_from_set(recipients, local_only) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b7cab8642..70db520ca 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -935,10 +935,10 @@ def subscribe(subscriber, %{ap_id: ap_id}) do def unsubscribe(unsubscriber, %{ap_id: ap_id}) do info_cng = - subscriber.info + unsubscriber.info |> User.Info.remove_from_subscriptions(ap_id) - change(subscriber) + change(unsubscriber) |> put_embed(:info, info_cng) |> update_and_set_cache() end @@ -1005,6 +1005,9 @@ def muted_users(user), def blocked_users(user), do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) + def subscribed_users(user), + do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscriptions)) + def block_domain(user, domain) do info_cng = user.info diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 30c594f64..1746da576 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -22,7 +22,7 @@ defmodule Pleroma.User.Info do field(:domain_blocks, {:array, :string}, default: []) field(:mutes, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: []) - field(:subscribed_to, {:array, :string}, default: []) + field(:subscriptions, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) @@ -98,8 +98,8 @@ def set_subscriptions(info, subscriptions) do params = %{subscriptions: subscriptions} info - |> cast(params, [:subscribed_to]) - |> validate_required([:subscribed_to]) + |> cast(params, [:subscriptions]) + |> validate_required([:subscriptions]) end def add_to_mutes(info, muted) do @@ -119,11 +119,11 @@ def remove_from_block(info, blocked) do end def add_to_subscriptions(info, subscribed) do - set_subscriptions(info, Enum.uniq([subscribed | info.subscribed_to])) + set_subscriptions(info, Enum.uniq([subscribed | info.subscriptions])) end def remove_from_subscriptions(info, subscribed) do - set_subscriptions(info, List.delete(info.subscribed_to, subscribed)) + set_subscriptions(info, List.delete(info.subscriptions, subscribed)) end def set_domain_blocks(info, domain_blocks) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9cd8b3758..effc49a01 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -335,6 +335,22 @@ def maybe_notify_mentioned_recipients( def maybe_notify_mentioned_recipients(recipients, _), do: recipients + def maybe_notify_subscribers( + recipients, + %Activity{data: %{"actor" => actor, "type" => type}} + ) when type == "Create" do + with %User{} = user <- User.get_by_ap_id(actor) do + subscriber_ids = + user + |> User.subscribed_users() + |> Enum.map(& &1.ap_id) + + recipients ++ subscriber_ids + end + end + + def maybe_notify_subscribers(recipients, _), do: recipients + def maybe_extract_mentions(%{"tag" => tag}) do tag |> Enum.filter(fn x -> is_map(x) end) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index c0a0a500f..7720367ad 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -62,6 +62,7 @@ def unblock(%User{} = blocker, params) do def subscribe(%User{} = subscriber, params) do with {:ok, %User{} = subscribed} <- get_user(params) do User.subscribe(subscriber, subscribed) + |> IO.inspect end end From 23c4f4949436003f6ccd7fd283492a6598f88482 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 14:27:26 +0100 Subject: [PATCH 05/36] Fix subscription endpoint return formats --- lib/pleroma/web/twitter_api/twitter_api.ex | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 7720367ad..5537680ad 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -60,15 +60,16 @@ def unblock(%User{} = blocker, params) do end def subscribe(%User{} = subscriber, params) do - with {:ok, %User{} = subscribed} <- get_user(params) do - User.subscribe(subscriber, subscribed) - |> IO.inspect + with {:ok, %User{} = subscribed} <- get_user(params), + {:ok, subscriber} <- User.subscribe(subscriber, subscribed) do + {:ok, subscriber, subscribed} end end def unsubscribe(%User{} = unsubscriber, params) do - with {:ok, %User{} = unsubscribed} <- get_user(params) do - User.unsubscribe(unsubscriber, unsubscribed) + with {:ok, %User{} = unsubscribed} <- get_user(params), + {:ok, unsubscriber} <- User.unsubscribe(unsubscriber, unsubscribed) do + {:ok, unsubscriber, unsubscribed} end end From 316fe20d864711c2854401eaa2cb08f627d5e536 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 14:59:34 +0100 Subject: [PATCH 06/36] Make subscriptions the same direction as blocks That being, user - subscribes to -> users, rather than user - has subscribers -> users --- lib/pleroma/user.ex | 7 ++++++- lib/pleroma/web/common_api/utils.ex | 9 +++++---- lib/pleroma/web/twitter_api/twitter_api_controller.ex | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 70db520ca..6b213df58 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1006,7 +1006,12 @@ def blocked_users(user), do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) def subscribed_users(user), - do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscriptions)) + do: + Repo.all( + from(u in User, + where: fragment("?->'subscriptions' @> ?", u.info, ^user.ap_id) + ) + ) def block_domain(user, domain) do info_cng = diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index effc49a01..087778dfe 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -336,10 +336,11 @@ def maybe_notify_mentioned_recipients( def maybe_notify_mentioned_recipients(recipients, _), do: recipients def maybe_notify_subscribers( - recipients, - %Activity{data: %{"actor" => actor, "type" => type}} - ) when type == "Create" do - with %User{} = user <- User.get_by_ap_id(actor) do + recipients, + %Activity{data: %{"actor" => actor, "type" => type}} + ) + when type == "Create" do + with %User{} = user <- User.get_by_ap_id(actor) do subscriber_ids = user |> User.subscribed_users() diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index af4c0a60e..0732705e6 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -279,7 +279,7 @@ def subscribe(%{assigns: %{user: user}} = conn, params) do {:error, msg} -> forbidden_json_reply(conn, msg) end - end + end def unsubscribe(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.unsubscribe(user, params) do @@ -287,7 +287,7 @@ def unsubscribe(%{assigns: %{user: user}} = conn, params) do conn |> put_view(UserView) |> render("show.json", %{user: unsubscribed, for: user}) - + {:error, msg} -> forbidden_json_reply(conn, msg) end From 75c4ceb4a2e5b59607a4688a2519e2f784aaa8f6 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 15:14:19 +0100 Subject: [PATCH 07/36] Add tests for subscriptions --- test/notification_test.exs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/notification_test.exs b/test/notification_test.exs index 12b4292aa..3a11d493b 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -29,6 +29,18 @@ test "notifies someone when they are directly addressed" do assert notification.activity_id == activity.id assert other_notification.activity_id == activity.id end + + + test "it creates a notification for subscribed users" do + user = insert(:user) + subscriber = insert(:user) + + {:ok, _, _} = TwitterAPI.subscribe(subscriber, %{"user_id" => user.id}) + {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) + {:ok, [notification]} = Notification.create_notifications(status) + + assert notification.user_id == subscriber.id + end end describe "create_notification" do @@ -84,6 +96,16 @@ test "it doesn't create a notification for repeat-unrepeat-repeat chains" do {:ok, dupe} = TwitterAPI.repeat(user, status.id) assert nil == Notification.create_notification(dupe, retweeted_user) end + + test "it doesn't create duplicate notifications for follow+subscribed users" do + user = insert(:user) + subscriber = insert(:user) + + {:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id}) + {:ok, _, _} = TwitterAPI.subscribe(subscriber, %{"user_id" => user.id}) + {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) + {:ok, [_notif]} = Notification.create_notifications(status) + end end describe "get notification" do From d56866c824d8023c9ca162ffef4c2eff150de399 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 15:21:33 +0100 Subject: [PATCH 08/36] Add subscribed status to user view Added in pleroma extensions, but can be moved whenever --- lib/pleroma/user.ex | 5 +++++ lib/pleroma/web/twitter_api/views/user_view.ex | 10 ++++++---- test/web/twitter_api/views/user_view_test.exs | 17 ++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6b213df58..de7ed6e72 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -999,6 +999,11 @@ def blocks?(user, %{ap_id: ap_id}) do end) end + def subscribed_to?(user, %{ap_id: ap_id}) do + subs = user.info.subscriptions + Enum.member?(subs, ap_id) + end + def muted_users(user), do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes)) diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 0791ed760..c59570d3e 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -47,15 +47,16 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do for_user = assigns[:for] image = User.avatar_url(user) |> MediaProxy.url() - {following, follows_you, statusnet_blocking} = + {following, follows_you, statusnet_blocking, subscribed} = if for_user do { User.following?(for_user, user), User.following?(user, for_user), - User.blocks?(for_user, user) + User.blocks?(for_user, user), + User.subscribed_to?(for_user, user) } else - {false, false, false} + {false, false, false, false} end user_info = User.get_cached_user_info(user) @@ -116,7 +117,8 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do "pleroma" => %{ "confirmation_pending" => user_info.confirmation_pending, - "tags" => user.tags + "tags" => user.tags, + "subscribed" => subscribed } |> maybe_with_activation_status(user, for_user) } diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 0feaf4b64..f9ae5888c 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -105,7 +105,8 @@ test "A user" do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [] + "tags" => [], + "subscribed" => false } } @@ -153,7 +154,8 @@ test "A user for a given other follower", %{user: user} do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [] + "tags" => [], + "subscribed" => false, } } @@ -202,13 +204,22 @@ test "A user that follows you", %{user: user} do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [] + "tags" => [], + "subscribed" => false } } assert represented == UserView.render("show.json", %{user: follower, for: user}) end + test "a user that you are subscribed to" do + user = insert(:user) + subscriber = insert(:user) + {:ok, subscriber} = User.subscribe(subscriber, user) + represented = UserView.render("show.json", %{user: user, for: subscriber}) + assert represented["pleroma"]["subscribed"] == true + end + test "a user that is a moderator" do user = insert(:user, %{info: %{is_moderator: true}}) represented = UserView.render("show.json", %{user: user, for: user}) From 06cd547fc6562b00d505fcc8e78ba7c734b1adf0 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 15:22:48 +0100 Subject: [PATCH 09/36] Format subscriptions related stuff --- test/notification_test.exs | 3 +-- test/web/twitter_api/views/user_view_test.exs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/notification_test.exs b/test/notification_test.exs index 3a11d493b..96a8d372a 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -30,7 +30,6 @@ test "notifies someone when they are directly addressed" do assert other_notification.activity_id == activity.id end - test "it creates a notification for subscribed users" do user = insert(:user) subscriber = insert(:user) @@ -38,7 +37,7 @@ test "it creates a notification for subscribed users" do {:ok, _, _} = TwitterAPI.subscribe(subscriber, %{"user_id" => user.id}) {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) {:ok, [notification]} = Notification.create_notifications(status) - + assert notification.user_id == subscriber.id end end diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index f9ae5888c..8986112cc 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -155,7 +155,7 @@ test "A user for a given other follower", %{user: user} do "pleroma" => %{ "confirmation_pending" => false, "tags" => [], - "subscribed" => false, + "subscribed" => false } } From 73afd13c2114dde0f9939d2f8c8516d2693b7e95 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 15:37:00 +0100 Subject: [PATCH 10/36] Add subscription key to one test I forgot about --- test/web/twitter_api/views/user_view_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 8986112cc..69d9c5da5 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -299,7 +299,8 @@ test "A blocked user for the blocker" do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [] + "tags" => [], + "subscribed" => false } } From d35f6551c1e9b11dec81b622c9ed2d9cdd6ac389 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 15:58:44 +0100 Subject: [PATCH 11/36] Move subscriptions endpoint to be under /pleroma --- lib/pleroma/web/router.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 10037b24f..0b571fc0b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -495,14 +495,14 @@ defmodule Pleroma.Web.Router do post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request) + post("/pleroma/subscriptions/create", TwitterAPI.Controller, :subscribe) + post("/pleroma/subscriptions/destroy", TwitterAPI.Controller, :unsubscribe) + post("/friendships/create", TwitterAPI.Controller, :follow) post("/friendships/destroy", TwitterAPI.Controller, :unfollow) post("/blocks/create", TwitterAPI.Controller, :block) post("/blocks/destroy", TwitterAPI.Controller, :unblock) - - post("/subscriptions/create", TwitterAPI.Controller, :subscribe) - post("/subscriptions/destroy", TwitterAPI.Controller, :unsubscribe) end end From 9ca91cbb8764ef4f8fe5303705dd98984e4e90c0 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 16:51:45 +0100 Subject: [PATCH 12/36] Change relationship direction of subscriptions --- lib/pleroma/user.ex | 42 +++++++++---------- lib/pleroma/user/info.ex | 18 ++++---- lib/pleroma/web/common_api/utils.ex | 2 +- .../mastodon_api/mastodon_api_controller.ex | 20 +++++++++ .../web/mastodon_api/views/account_view.ex | 1 + lib/pleroma/web/router.ex | 6 +-- lib/pleroma/web/twitter_api/twitter_api.ex | 14 ------- .../web/twitter_api/twitter_api_controller.ex | 24 ----------- .../web/twitter_api/views/user_view.ex | 10 ++--- test/web/mastodon_api/account_view_test.exs | 2 + .../mastodon_api_controller_test.exs | 19 +++++++++ test/web/twitter_api/views/user_view_test.exs | 20 ++------- 12 files changed, 84 insertions(+), 94 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index de7ed6e72..f1565ade7 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -924,23 +924,27 @@ def unmute(muter, %{ap_id: ap_id}) do end def subscribe(subscriber, %{ap_id: ap_id}) do - info_cng = - subscriber.info - |> User.Info.add_to_subscriptions(ap_id) + with %User{} = user <- get_or_fetch_by_ap_id(ap_id) do + info_cng = + user.info + |> User.Info.add_to_subscribers(subscriber.ap_id) - change(subscriber) - |> put_embed(:info, info_cng) - |> update_and_set_cache() + change(user) + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end end def unsubscribe(unsubscriber, %{ap_id: ap_id}) do - info_cng = - unsubscriber.info - |> User.Info.remove_from_subscriptions(ap_id) + with %User{} = user <- get_or_fetch_by_ap_id(ap_id) do + info_cng = + user.info + |> User.Info.remove_from_subscribers(unsubscriber.ap_id) - change(unsubscriber) - |> put_embed(:info, info_cng) - |> update_and_set_cache() + change(user) + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end end def block(blocker, %User{ap_id: ap_id} = blocked) do @@ -1000,8 +1004,9 @@ def blocks?(user, %{ap_id: ap_id}) do end def subscribed_to?(user, %{ap_id: ap_id}) do - subs = user.info.subscriptions - Enum.member?(subs, ap_id) + with %User{} = target <- User.get_by_ap_id(ap_id) do + Enum.member?(target.info.subscribers, user.ap_id) + end end def muted_users(user), @@ -1010,13 +1015,8 @@ def muted_users(user), def blocked_users(user), do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) - def subscribed_users(user), - do: - Repo.all( - from(u in User, - where: fragment("?->'subscriptions' @> ?", u.info, ^user.ap_id) - ) - ) + def subscribers(user), + do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers)) def block_domain(user, domain) do info_cng = diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 1746da576..1cf46feb3 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -22,7 +22,7 @@ defmodule Pleroma.User.Info do field(:domain_blocks, {:array, :string}, default: []) field(:mutes, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: []) - field(:subscriptions, {:array, :string}, default: []) + field(:subscribers, {:array, :string}, default: []) field(:deactivated, :boolean, default: false) field(:no_rich_text, :boolean, default: false) field(:ap_enabled, :boolean, default: false) @@ -94,12 +94,12 @@ def set_blocks(info, blocks) do |> validate_required([:blocks]) end - def set_subscriptions(info, subscriptions) do - params = %{subscriptions: subscriptions} + def set_subscribers(info, subscribers) do + params = %{subscribers: subscribers} info - |> cast(params, [:subscriptions]) - |> validate_required([:subscriptions]) + |> cast(params, [:subscribers]) + |> validate_required([:subscribers]) end def add_to_mutes(info, muted) do @@ -118,12 +118,12 @@ def remove_from_block(info, blocked) do set_blocks(info, List.delete(info.blocks, blocked)) end - def add_to_subscriptions(info, subscribed) do - set_subscriptions(info, Enum.uniq([subscribed | info.subscriptions])) + def add_to_subscribers(info, subscribed) do + set_subscribers(info, Enum.uniq([subscribed | info.subscribers])) end - def remove_from_subscriptions(info, subscribed) do - set_subscriptions(info, List.delete(info.subscriptions, subscribed)) + def remove_from_subscribers(info, subscribed) do + set_subscribers(info, List.delete(info.subscribers, subscribed)) end def set_domain_blocks(info, domain_blocks) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 087778dfe..4e0a6b2d9 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -343,7 +343,7 @@ def maybe_notify_subscribers( with %User{} = user <- User.get_by_ap_id(actor) do subscriber_ids = user - |> User.subscribed_users() + |> User.subscribers() |> Enum.map(& &1.ap_id) recipients ++ subscriber_ids diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 89fd7629a..e848895f1 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -863,6 +863,26 @@ def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) d json(conn, %{}) end + def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %User{} = subscription_target <- User.get_by_id(id) do + {:ok, subscription_target} = User.subscribe(user, subscription_target) + + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: user, target: subscription_target}) + end + end + + def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %User{} = subscription_target <- User.get_by_id(id) do + {:ok, subscription_target} = User.unsubscribe(user, subscription_target) + + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: user, target: subscription_target}) + end + end + def status_search(user, query) do fetched = if Regex.match?(~r/https?:/, query) do diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b5f3bbb9d..42595b0b5 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -53,6 +53,7 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target blocking: User.blocks?(user, target), muting: User.mutes?(user, target), muting_notifications: false, + subscribing: User.subscribed_to?(user, target), requested: requested, domain_blocking: false, showing_reblogs: User.showing_reblogs?(user, target), diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 0b571fc0b..da988e5f2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -311,6 +311,9 @@ defmodule Pleroma.Web.Router do post("/domain_blocks", MastodonAPIController, :block_domain) delete("/domain_blocks", MastodonAPIController, :unblock_domain) + + post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe) + post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe) end scope [] do @@ -495,9 +498,6 @@ defmodule Pleroma.Web.Router do post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request) - post("/pleroma/subscriptions/create", TwitterAPI.Controller, :subscribe) - post("/pleroma/subscriptions/destroy", TwitterAPI.Controller, :unsubscribe) - post("/friendships/create", TwitterAPI.Controller, :follow) post("/friendships/destroy", TwitterAPI.Controller, :unfollow) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 5537680ad..9b081a316 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -59,20 +59,6 @@ def unblock(%User{} = blocker, params) do end end - def subscribe(%User{} = subscriber, params) do - with {:ok, %User{} = subscribed} <- get_user(params), - {:ok, subscriber} <- User.subscribe(subscriber, subscribed) do - {:ok, subscriber, subscribed} - end - end - - def unsubscribe(%User{} = unsubscriber, params) do - with {:ok, %User{} = unsubscribed} <- get_user(params), - {:ok, unsubscriber} <- User.unsubscribe(unsubscriber, unsubscribed) do - {:ok, unsubscriber, unsubscribed} - end - end - def repeat(%User{} = user, ap_id_or_id) do with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 0732705e6..a7ec9949c 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -269,30 +269,6 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do end end - def subscribe(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.subscribe(user, params) do - {:ok, user, subscribed} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: subscribed, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - - def unsubscribe(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.unsubscribe(user, params) do - {:ok, user, unsubscribed} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: unsubscribed, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index c59570d3e..0791ed760 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -47,16 +47,15 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do for_user = assigns[:for] image = User.avatar_url(user) |> MediaProxy.url() - {following, follows_you, statusnet_blocking, subscribed} = + {following, follows_you, statusnet_blocking} = if for_user do { User.following?(for_user, user), User.following?(user, for_user), - User.blocks?(for_user, user), - User.subscribed_to?(for_user, user) + User.blocks?(for_user, user) } else - {false, false, false, false} + {false, false, false} end user_info = User.get_cached_user_info(user) @@ -117,8 +116,7 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do "pleroma" => %{ "confirmation_pending" => user_info.confirmation_pending, - "tags" => user.tags, - "subscribed" => subscribed + "tags" => user.tags } |> maybe_with_activation_status(user, for_user) } diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index 6dc60afe9..48580ff1e 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -142,6 +142,7 @@ test "represent a relationship" do blocking: true, muting: false, muting_notifications: false, + subscribing: false, requested: false, domain_blocking: false, showing_reblogs: true, @@ -198,6 +199,7 @@ test "represent an embedded relationship" do following: false, followed_by: false, blocking: true, + subscribing: false, muting: false, muting_notifications: false, requested: false, diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 6060cc97f..811a2dd7b 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1555,6 +1555,25 @@ test "muting / unmuting a user", %{conn: conn} do assert %{"id" => _id, "muting" => false} = json_response(conn, 200) end + test "subscribing / unsubscribing to a user", %{conn: conn} do + user = insert(:user) + subscription_target = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + + assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + + assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) + end + test "getting a list of mutes", %{conn: conn} do user = insert(:user) other_user = insert(:user) diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 69d9c5da5..0feaf4b64 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -105,8 +105,7 @@ test "A user" do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [], - "subscribed" => false + "tags" => [] } } @@ -154,8 +153,7 @@ test "A user for a given other follower", %{user: user} do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [], - "subscribed" => false + "tags" => [] } } @@ -204,22 +202,13 @@ test "A user that follows you", %{user: user} do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [], - "subscribed" => false + "tags" => [] } } assert represented == UserView.render("show.json", %{user: follower, for: user}) end - test "a user that you are subscribed to" do - user = insert(:user) - subscriber = insert(:user) - {:ok, subscriber} = User.subscribe(subscriber, user) - represented = UserView.render("show.json", %{user: user, for: subscriber}) - assert represented["pleroma"]["subscribed"] == true - end - test "a user that is a moderator" do user = insert(:user, %{info: %{is_moderator: true}}) represented = UserView.render("show.json", %{user: user, for: user}) @@ -299,8 +288,7 @@ test "A blocked user for the blocker" do "fields" => [], "pleroma" => %{ "confirmation_pending" => false, - "tags" => [], - "subscribed" => false + "tags" => [] } } From f6e131eeb9b9e75e66c5e64084f46e57b9b1358f Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 17:00:41 +0100 Subject: [PATCH 13/36] Don't use twitterAPI for subscription tests --- test/notification_test.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/notification_test.exs b/test/notification_test.exs index 96a8d372a..2163c95c0 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -33,8 +33,9 @@ test "notifies someone when they are directly addressed" do test "it creates a notification for subscribed users" do user = insert(:user) subscriber = insert(:user) + + User.subscribe(subscriber, user) - {:ok, _, _} = TwitterAPI.subscribe(subscriber, %{"user_id" => user.id}) {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) {:ok, [notification]} = Notification.create_notifications(status) @@ -101,7 +102,7 @@ test "it doesn't create duplicate notifications for follow+subscribed users" do subscriber = insert(:user) {:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id}) - {:ok, _, _} = TwitterAPI.subscribe(subscriber, %{"user_id" => user.id}) + User.subscribe(subscriber, user) {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) {:ok, [_notif]} = Notification.create_notifications(status) end From a270e7a1915c0d36004e16fc9a166a42ea3eff8a Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 17:07:58 +0100 Subject: [PATCH 14/36] Mix format everything --- test/notification_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/notification_test.exs b/test/notification_test.exs index 2163c95c0..8109623af 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -33,7 +33,7 @@ test "notifies someone when they are directly addressed" do test "it creates a notification for subscribed users" do user = insert(:user) subscriber = insert(:user) - + User.subscribe(subscriber, user) {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) From ffac2593eaefeb3000a69f44566dfe5b2a574ae9 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Fri, 5 Apr 2019 17:30:54 +0100 Subject: [PATCH 15/36] Add index on subscribers --- .../20190405160700_add_index_on_subscribers.exs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 priv/repo/migrations/20190405160700_add_index_on_subscribers.exs diff --git a/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs b/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs new file mode 100644 index 000000000..232f75c92 --- /dev/null +++ b/priv/repo/migrations/20190405160700_add_index_on_subscribers.exs @@ -0,0 +1,8 @@ +defmodule Pleroma.Repo.Migrations.AddIndexOnSubscribers do + use Ecto.Migration + + @disable_ddl_transaction true + def change do + create index(:users, ["(info->'subscribers')"], name: :users_subscribers_index, using: :gin, concurrently: true) + end +end From 0484f3a8b1ae2103d1d756e5c09f2bdb218a7207 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 16:58:22 +0700 Subject: [PATCH 16/36] generating tokens with mix --- lib/mix/tasks/pleroma/user.ex | 75 +++++++++++++++- lib/pleroma/user_invite_token.ex | 89 +++++++++++++++++-- ...50946_add_fields_to_user_invite_tokens.exs | 12 +++ test/tasks/user_test.exs | 81 ++++++++++++++++- 4 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 0d0bea8c0..00a933292 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -7,6 +7,7 @@ defmodule Mix.Tasks.Pleroma.User do import Ecto.Changeset alias Mix.Tasks.Pleroma.Common alias Pleroma.User + alias Pleroma.UserInviteToken @shortdoc "Manages Pleroma users" @moduledoc """ @@ -26,7 +27,19 @@ defmodule Mix.Tasks.Pleroma.User do ## Generate an invite link. - mix pleroma.user invite + mix pleroma.user invite [OPTION...] + + Options: + - `--expire_date DATE` - last day on which token is active (e.g. "2019-04-05") + - `--max_use NUMBER` - maximum numbers of token use + + ## Generated invites list + + mix pleroma.user invites_list + + ## Revoke invite + + mix pleroma.user invite_revoke TOKEN OR TOKEN_ID ## Delete the user's account. @@ -287,11 +300,28 @@ def run(["untag", nickname | tags]) do end end - def run(["invite"]) do + def run(["invite" | rest]) do + {options, [], []} = + OptionParser.parse(rest, + strict: [ + expire_date: :string, + max_use: :integer + ] + ) + + expire_at = + with expire_date when expire_date != nil <- Keyword.get(options, :expire_date) do + Date.from_iso8601!(expire_date) + end + + options = Keyword.put(options, :expire_at, expire_at) + Common.start_pleroma() - with {:ok, token} <- Pleroma.UserInviteToken.create_token() do - Mix.shell().info("Generated user invite token") + with {:ok, token} <- UserInviteToken.create_token(options) do + Mix.shell().info( + "Generated user invite token " <> String.replace(token.token_type, "_", " ") + ) url = Pleroma.Web.Router.Helpers.redirect_url( @@ -307,6 +337,43 @@ def run(["invite"]) do end end + def run(["invites_list"]) do + Common.start_pleroma() + + Mix.shell().info("Invites list:") + + UserInviteToken.list_invites() + |> Enum.each(fn invite -> + expire_date = + case invite.expire_at do + nil -> nil + date -> " | Expire date: #{Date.to_string(date)}" + end + + using_info = + case invite.max_use do + nil -> nil + max_use -> " | Max use: #{max_use} Left use: #{max_use - invite.uses}" + end + + Mix.shell().info( + "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.token_type} | Used: #{ + invite.used + }#{expire_date}#{using_info}" + ) + end) + end + + def run(["invite_revoke", token]) do + Common.start_pleroma() + + with {:ok, _} <- UserInviteToken.mark_as_used(token) do + Mix.shell().info("Invite for token #{token} was revoked.") + else + _ -> Mix.shell().error("No invite found with token #{token}") + end + end + def run(["delete_activities", nickname]) do Common.start_pleroma() diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 9c5579934..3ed39ddd3 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -6,34 +6,54 @@ defmodule Pleroma.UserInviteToken do use Ecto.Schema import Ecto.Changeset - + import Ecto.Query alias Pleroma.Repo alias Pleroma.UserInviteToken + @type token :: String.t() + schema "user_invite_tokens" do field(:token, :string) field(:used, :boolean, default: false) + field(:max_use, :integer) + field(:expire_at, :date) + field(:uses, :integer) + field(:token_type) timestamps() end - def create_token do + def create_token(options \\ []) do token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() - token = %UserInviteToken{ - used: false, - token: token - } + max_use = options[:max_use] + expire_at = options[:expire_at] + + token = + %UserInviteToken{ + used: false, + token: token, + max_use: max_use, + expire_at: expire_at, + uses: 0 + } + |> token_type() Repo.insert(token) end + def list_invites do + query = from(u in UserInviteToken, order_by: u.id) + Repo.all(query) + end + def used_changeset(struct) do struct |> cast(%{}, []) |> put_change(:used, true) end + @spec mark_as_used(token()) :: {:ok, UserInviteToken.t()} | {:error, token()} def mark_as_used(token) do with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}), {:ok, token} <- Repo.update(used_changeset(token)) do @@ -42,4 +62,61 @@ def mark_as_used(token) do _e -> {:error, token} end end + + defp token_type(%{expire_at: nil, max_use: nil} = token), do: %{token | token_type: "one_time"} + + defp token_type(%{expire_at: _expire_at, max_use: nil} = token), + do: %{token | token_type: "date_limited"} + + defp token_type(%{expire_at: nil, max_use: _max_use} = token), + do: %{token | token_type: "reusable"} + + defp token_type(%{expire_at: _expire_at, max_use: _max_use} = token), + do: %{token | token_type: "reusable_date_limited"} + + @spec valid_token?(UserInviteToken.t()) :: boolean() + def valid_token?(%{token_type: "one_time"} = token) do + not token.used + end + + def valid_token?(%{token_type: "date_limited"} = token) do + not_overdue_date?(token) and not token.used + end + + def valid_token?(%{token_type: "reusable"} = token) do + token.uses < token.max_use and not token.used + end + + def valid_token?(%{token_type: "reusable_date_limited"} = token) do + not_overdue_date?(token) and token.uses < token.max_use and not token.used + end + + defp not_overdue_date?(%{expire_at: expire_at} = token) do + Date.compare(Date.utc_today(), expire_at) in [:lt, :eq] || + (Repo.update!(change(token, used: true)) && false) + end + + def update_usage(%{token_type: "date_limited"}), do: nil + + def update_usage(%{token_type: "one_time"} = token) do + UserInviteToken.mark_as_used(token.token) + end + + def update_usage(%{token_type: token_type} = token) + when token_type == "reusable" or token_type == "reusable_date_limited" do + new_uses = token.uses + 1 + + changes = %{ + uses: new_uses + } + + changes = + if new_uses >= token.max_use do + Map.put(changes, :used, true) + else + changes + end + + change(token, changes) |> Repo.update!() + end end diff --git a/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs new file mode 100644 index 000000000..abdd5e277 --- /dev/null +++ b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs @@ -0,0 +1,12 @@ +defmodule Pleroma.Repo.Migrations.AddFieldsToUserInviteTokens do + use Ecto.Migration + + def change do + alter table(:user_invite_tokens) do + add(:expire_at, :date) + add(:uses, :integer, default: 0) + add(:max_use, :integer) + add(:token_type, :string, default: "one_time") + end + end +end diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 1030bd555..c55711b04 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -245,7 +245,86 @@ test "invite token is generated" do end) =~ "http" assert_received {:mix_shell, :info, [message]} - assert message =~ "Generated" + assert message =~ "Generated user invite token one time" + end + + test "token is generated with expire_at" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invite", + "--expire-date", + Date.to_string(Date.utc_today()) + ]) + end) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Generated user invite token date limited" + end + + test "token is generated with max use" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invite", + "--max-use", + "5" + ]) + end) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Generated user invite token reusable" + end + + test "token is generated with max use and expire date" do + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invite", + "--max-use", + "5", + "--expire-date", + Date.to_string(Date.utc_today()) + ]) + end) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Generated user invite token reusable date limited" + end + end + + describe "running invites_list" do + test "invites are listed" do + {:ok, invite} = Pleroma.UserInviteToken.create_token() + + {:ok, invite2} = + Pleroma.UserInviteToken.create_token(expire_at: Date.utc_today(), max_use: 15) + + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invites_list" + ]) + end) + + assert_received {:mix_shell, :info, [message]} + assert_received {:mix_shell, :info, [message2]} + assert_received {:mix_shell, :info, [message3]} + assert message =~ "Invites list:" + assert message2 =~ invite.token_type + assert message3 =~ invite2.token_type + end + end + + describe "running invite revoke" do + test "invite is revoked" do + {:ok, invite} = Pleroma.UserInviteToken.create_token(expire_at: Date.utc_today()) + + assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invite_revoke", + invite.token + ]) + end) + + assert_received {:mix_shell, :info, [message]} + assert message =~ "Invite for token #{invite.token} was revoked." end end From be54e40890432d2cd8e592e6d4acfa9f1e98586c Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 17:18:59 +0700 Subject: [PATCH 17/36] twitter api registration fix for twitter api tests --- lib/pleroma/web/twitter_api/twitter_api.ex | 67 ++-- test/fixtures/lambadalambda.json | 64 ++++ test/support/http_request_mock.ex | 4 + test/web/twitter_api/twitter_api_test.exs | 361 ++++++++++++++++++--- 4 files changed, 420 insertions(+), 76 deletions(-) create mode 100644 test/fixtures/lambadalambda.json diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 9b081a316..a578fbbf4 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -163,36 +163,49 @@ def register_user(params) do {:error, %{error: Jason.encode!(%{captcha: [error]})}} else registrations_open = Pleroma.Config.get([:instance, :registrations_open]) + registration_process(registrations_open, params, token_string) + end + end - # no need to query DB if registration is open - token = - unless registrations_open || is_nil(token_string) do - Repo.get_by(UserInviteToken, %{token: token_string}) - end + defp registration_process(_registration_open = true, params, _token_string) do + create_user(params) + end - cond do - registrations_open || (!is_nil(token) && !token.used) -> - changeset = User.register_changeset(%User{}, params) - - with {:ok, user} <- User.register(changeset) do - !registrations_open && UserInviteToken.mark_as_used(token.token) - - {:ok, user} - else - {:error, changeset} -> - errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Jason.encode!() - - {:error, %{error: errors}} - end - - !registrations_open && is_nil(token) -> - {:error, "Invalid token"} - - !registrations_open && token.used -> - {:error, "Expired token"} + defp registration_process(registration_open, params, token_string) + when registration_open == false or is_nil(registration_open) do + token = + unless is_nil(token_string) do + Repo.get_by(UserInviteToken, %{token: token_string}) end + + valid_token? = token && UserInviteToken.valid_token?(token) + + case token do + nil -> + {:error, "Invalid token"} + + token when valid_token? -> + UserInviteToken.update_usage(token) + create_user(params) + + _ -> + {:error, "Expired token"} + end + end + + defp create_user(params) do + changeset = User.register_changeset(%User{}, params) + + case User.register(changeset) do + {:ok, user} -> + {:ok, user} + + {:error, changeset} -> + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() + + {:error, %{error: errors}} end end diff --git a/test/fixtures/lambadalambda.json b/test/fixtures/lambadalambda.json new file mode 100644 index 000000000..1f09fb591 --- /dev/null +++ b/test/fixtures/lambadalambda.json @@ -0,0 +1,64 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "toot": "http://joinmastodon.org/ns#", + "featured": { + "@id": "toot:featured", + "@type": "@id" + }, + "alsoKnownAs": { + "@id": "as:alsoKnownAs", + "@type": "@id" + }, + "movedTo": { + "@id": "as:movedTo", + "@type": "@id" + }, + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji", + "IdentityProof": "toot:IdentityProof", + "focalPoint": { + "@container": "@list", + "@id": "toot:focalPoint" + } + } + ], + "id": "https://mastodon.social/users/lambadalambda", + "type": "Person", + "following": "https://mastodon.social/users/lambadalambda/following", + "followers": "https://mastodon.social/users/lambadalambda/followers", + "inbox": "https://mastodon.social/users/lambadalambda/inbox", + "outbox": "https://mastodon.social/users/lambadalambda/outbox", + "featured": "https://mastodon.social/users/lambadalambda/collections/featured", + "preferredUsername": "lambadalambda", + "name": "Critical Value", + "summary": "\u003cp\u003e\u003c/p\u003e", + "url": "https://mastodon.social/@lambadalambda", + "manuallyApprovesFollowers": false, + "publicKey": { + "id": "https://mastodon.social/users/lambadalambda#main-key", + "owner": "https://mastodon.social/users/lambadalambda", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0P/Tq4gb4G/QVuMGbJo\nC/AfMNcv+m7NfrlOwkVzcU47jgESuYI4UtJayissCdBycHUnfVUd9qol+eznSODz\nCJhfJloqEIC+aSnuEPGA0POtWad6DU0E6/Ho5zQn5WAWUwbRQqowbrsm/GHo2+3v\neR5jGenwA6sYhINg/c3QQbksyV0uJ20Umyx88w8+TJuv53twOfmyDWuYNoQ3y5cc\nHKOZcLHxYOhvwg3PFaGfFHMFiNmF40dTXt9K96r7sbzc44iLD+VphbMPJEjkMuf8\nPGEFOBzy8pm3wJZw2v32RNW2VESwMYyqDzwHXGSq1a73cS7hEnc79gXlELsK04L9\nQQIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "tag": [], + "attachment": [], + "endpoints": { + "sharedInbox": "https://mastodon.social/inbox" + }, + "icon": { + "type": "Image", + "mediaType": "image/gif", + "url": "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif" + }, + "image": { + "type": "Image", + "mediaType": "image/gif", + "url": "https://files.mastodon.social/accounts/headers/000/000/264/original/28b26104f83747d2.gif" + } +} diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index d3b547d91..5b355bfe6 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -716,6 +716,10 @@ def get("https://mastodon.social/users/lambadalambda.atom", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}} end + def get("https://mastodon.social/users/lambadalambda", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}} + end + def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do {:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)} end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 6c00244de..716fccfb2 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -16,6 +16,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do import Pleroma.Factory + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + test "create a status" do user = insert(:user) mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"}) @@ -299,7 +304,6 @@ test "it registers a new user with empty string in bio and returns the user." do UserView.render("show.json", %{user: fetched_user}) end - @moduletag skip: "needs 'account_activation_required: true' in config" test "it sends confirmation email if :account_activation_required is specified in instance config" do setting = Pleroma.Config.get([:instance, :account_activation_required]) @@ -353,68 +357,327 @@ test "it registers a new user and parses mentions in the bio" do assert user2.bio == expected_text end - @moduletag skip: "needs 'registrations_open: false' in config" - test "it registers a new user via invite token and returns the user." do - {:ok, token} = UserInviteToken.create_token() + describe "register with one time token" do + setup do + setting = Pleroma.Config.get([:instance, :registrations_open]) - data = %{ - "nickname" => "vinny", - "email" => "pasta@pizza.vs", - "fullname" => "Vinny Vinesauce", - "bio" => "streamer", - "password" => "hiptofbees", - "confirm" => "hiptofbees", - "token" => token.token - } + if setting do + Pleroma.Config.put([:instance, :registrations_open], false) + on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) + end - {:ok, user} = TwitterAPI.register_user(data) + :ok + end - fetched_user = User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) + test "returns user on success" do + {:ok, token} = UserInviteToken.create_token() - assert token.used == true + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees", + "token" => token.token + } - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.json", %{user: fetched_user}) + {:ok, user} = TwitterAPI.register_user(data) + + fetched_user = User.get_by_nickname("vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + + assert token.used == true + + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + end + + test "returns error on invalid token" do + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => "DudeLetMeInImAFairy" + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Invalid token" + refute User.get_by_nickname("GrimReaper") + end + + test "returns error on expired token" do + {:ok, token} = UserInviteToken.create_token() + UserInviteToken.mark_as_used(token.token) + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("GrimReaper") + end end - @moduletag skip: "needs 'registrations_open: false' in config" - test "it returns an error if invalid token submitted" do - data = %{ - "nickname" => "GrimReaper", - "email" => "death@reapers.afterlife", - "fullname" => "Reaper Grim", - "bio" => "Your time has come", - "password" => "scythe", - "confirm" => "scythe", - "token" => "DudeLetMeInImAFairy" - } + describe "registers with date limited token" do + setup do + setting = Pleroma.Config.get([:instance, :registrations_open]) - {:error, msg} = TwitterAPI.register_user(data) + if setting do + Pleroma.Config.put([:instance, :registrations_open], false) + on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) + end - assert msg == "Invalid token" - refute User.get_by_nickname("GrimReaper") + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees" + } + + check_fn = fn token -> + data = Map.put(data, "token", token.token) + {:ok, user} = TwitterAPI.register_user(data) + fetched_user = User.get_by_nickname("vinny") + + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + end + + {:ok, data: data, check_fn: check_fn} + end + + test "returns user on success", %{check_fn: check_fn} do + {:ok, token} = UserInviteToken.create_token(expire_at: Date.utc_today()) + + check_fn.(token) + + token = Repo.get_by(UserInviteToken, token: token.token) + + refute token.used + end + + test "returns user on token which expired tomorrow", %{check_fn: check_fn} do + {:ok, token} = UserInviteToken.create_token(expire_at: Date.add(Date.utc_today(), 1)) + + check_fn.(token) + + token = Repo.get_by(UserInviteToken, token: token.token) + + refute token.used + end + + test "returns an error on overdue date", %{data: data} do + {:ok, token} = UserInviteToken.create_token(expire_at: Date.add(Date.utc_today(), -1)) + + data = Map.put(data, "token", token.token) + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + + assert token.used == true + end end - @moduletag skip: "needs 'registrations_open: false' in config" - test "it returns an error if expired token submitted" do - {:ok, token} = UserInviteToken.create_token() - UserInviteToken.mark_as_used(token.token) + describe "registers with reusable token" do + setup do + setting = Pleroma.Config.get([:instance, :registrations_open]) - data = %{ - "nickname" => "GrimReaper", - "email" => "death@reapers.afterlife", - "fullname" => "Reaper Grim", - "bio" => "Your time has come", - "password" => "scythe", - "confirm" => "scythe", - "token" => token.token - } + if setting do + Pleroma.Config.put([:instance, :registrations_open], false) + on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) + end - {:error, msg} = TwitterAPI.register_user(data) + :ok + end - assert msg == "Expired token" - refute User.get_by_nickname("GrimReaper") + test "returns user on success, after him registration fails" do + {:ok, token} = UserInviteToken.create_token(max_use: 100) + + Ecto.Changeset.change(token, uses: 99) |> Repo.update!() + + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees", + "token" => token.token + } + + {:ok, user} = TwitterAPI.register_user(data) + fetched_user = User.get_by_nickname("vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + + assert token.used == true + + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("GrimReaper") + end + end + + describe "registers with reusable date limited token" do + setup do + setting = Pleroma.Config.get([:instance, :registrations_open]) + + if setting do + Pleroma.Config.put([:instance, :registrations_open], false) + on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) + end + + :ok + end + + test "returns user on success" do + {:ok, token} = + UserInviteToken.create_token( + expire_at: Date.utc_today(), + max_use: 100 + ) + + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees", + "token" => token.token + } + + {:ok, user} = TwitterAPI.register_user(data) + fetched_user = User.get_by_nickname("vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + + refute token.used + + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + end + + test "error after max uses" do + {:ok, token} = + UserInviteToken.create_token( + expire_at: Date.utc_today(), + max_use: 100 + ) + + Ecto.Changeset.change(token, uses: 99) |> Repo.update!() + + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees", + "token" => token.token + } + + {:ok, user} = TwitterAPI.register_user(data) + fetched_user = User.get_by_nickname("vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + assert token.used == true + + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("GrimReaper") + end + + test "returns error on overdue date" do + {:ok, token} = + UserInviteToken.create_token( + expire_at: Date.add(Date.utc_today(), -1), + max_use: 100 + ) + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("GrimReaper") + end + + test "returns error on with overdue date and after max" do + {:ok, token} = + UserInviteToken.create_token( + expire_at: Date.add(Date.utc_today(), -1), + max_use: 100 + ) + + Ecto.Changeset.change(token, uses: 100) |> Repo.update!() + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute User.get_by_nickname("GrimReaper") + end end test "it returns the error on registration problems" do From 47b07cec495528ce22f83ca56717cc74aa0096f3 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 20:24:22 +0700 Subject: [PATCH 18/36] token -> invite renaming --- lib/mix/tasks/pleroma/user.ex | 12 +- lib/pleroma/user_invite_token.ex | 121 +++++++++--------- lib/pleroma/web/twitter_api/twitter_api.ex | 22 ++-- ...50946_add_fields_to_user_invite_tokens.exs | 2 +- test/tasks/user_test.exs | 6 +- test/web/twitter_api/twitter_api_test.exs | 102 +++++++-------- 6 files changed, 124 insertions(+), 141 deletions(-) diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 00a933292..887f45029 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -315,19 +315,19 @@ def run(["invite" | rest]) do end options = Keyword.put(options, :expire_at, expire_at) - + options = Enum.into(options, %{}) Common.start_pleroma() - with {:ok, token} <- UserInviteToken.create_token(options) do + with {:ok, invite} <- UserInviteToken.create_invite(options) do Mix.shell().info( - "Generated user invite token " <> String.replace(token.token_type, "_", " ") + "Generated user invite token " <> String.replace(invite.invite_type, "_", " ") ) url = Pleroma.Web.Router.Helpers.redirect_url( Pleroma.Web.Endpoint, :registration_page, - token.token + invite.token ) IO.puts(url) @@ -367,7 +367,9 @@ def run(["invites_list"]) do def run(["invite_revoke", token]) do Common.start_pleroma() - with {:ok, _} <- UserInviteToken.mark_as_used(token) do + invite = UserInviteToken.find_by_token!(token) + + with {:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do Mix.shell().info("Invite for token #{token} was revoked.") else _ -> Mix.shell().error("No invite found with token #{token}") diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 3ed39ddd3..4efdbdc32 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -17,106 +17,101 @@ defmodule Pleroma.UserInviteToken do field(:used, :boolean, default: false) field(:max_use, :integer) field(:expire_at, :date) - field(:uses, :integer) - field(:token_type) + field(:uses, :integer, default: 0) + field(:invite_type, :string) timestamps() end - def create_token(options \\ []) do - token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() - - max_use = options[:max_use] - expire_at = options[:expire_at] - - token = - %UserInviteToken{ - used: false, - token: token, - max_use: max_use, - expire_at: expire_at, - uses: 0 - } - |> token_type() - - Repo.insert(token) + @spec create_invite(map()) :: UserInviteToken.t() + def create_invite(params \\ %{}) do + %UserInviteToken{} + |> cast(params, ~w(max_use expire_at)a) + |> add_token() + |> assign_type() + |> Repo.insert() end + defp add_token(changeset) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() + put_change(changeset, :token, token) + end + + defp assign_type(%{changes: %{max_use: _max_use, expire_at: _expire_at}} = changeset) do + put_change(changeset, :invite_type, "reusable_date_limited") + end + + defp assign_type(%{changes: %{expire_at: _expire_at}} = changeset) do + put_change(changeset, :invite_type, "date_limited") + end + + defp assign_type(%{changes: %{max_use: _max_use}} = changeset) do + put_change(changeset, :invite_type, "reusable") + end + + defp assign_type(changeset), do: put_change(changeset, :invite_type, "one_time") + + @spec list_invites() :: [UserInviteToken.t()] def list_invites do query = from(u in UserInviteToken, order_by: u.id) Repo.all(query) end - def used_changeset(struct) do - struct - |> cast(%{}, []) - |> put_change(:used, true) + @spec update_invite!(UserInviteToken.t(), map()) :: UserInviteToken.t() | no_return() + def update_invite!(invite, changes) do + change(invite, changes) |> Repo.update!() end - @spec mark_as_used(token()) :: {:ok, UserInviteToken.t()} | {:error, token()} - def mark_as_used(token) do - with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}), - {:ok, token} <- Repo.update(used_changeset(token)) do - {:ok, token} - else - _e -> {:error, token} - end + @spec update_invite(UserInviteToken.t(), map()) :: + {:ok, UserInviteToken.t()} | {:error, Changeset.t()} + def update_invite(invite, changes) do + change(invite, changes) |> Repo.update() end - defp token_type(%{expire_at: nil, max_use: nil} = token), do: %{token | token_type: "one_time"} + @spec find_by_token!(token()) :: UserInviteToken.t() | no_return() + def find_by_token!(token), do: Repo.get_by!(UserInviteToken, token: token) - defp token_type(%{expire_at: _expire_at, max_use: nil} = token), - do: %{token | token_type: "date_limited"} - - defp token_type(%{expire_at: nil, max_use: _max_use} = token), - do: %{token | token_type: "reusable"} - - defp token_type(%{expire_at: _expire_at, max_use: _max_use} = token), - do: %{token | token_type: "reusable_date_limited"} - - @spec valid_token?(UserInviteToken.t()) :: boolean() - def valid_token?(%{token_type: "one_time"} = token) do - not token.used + @spec valid_invite?(UserInviteToken.t()) :: boolean() + def valid_invite?(%{invite_type: "one_time"} = invite) do + not invite.used end - def valid_token?(%{token_type: "date_limited"} = token) do - not_overdue_date?(token) and not token.used + def valid_invite?(%{invite_type: "date_limited"} = invite) do + not_overdue_date?(invite) and not invite.used end - def valid_token?(%{token_type: "reusable"} = token) do - token.uses < token.max_use and not token.used + def valid_invite?(%{invite_type: "reusable"} = invite) do + invite.uses < invite.max_use and not invite.used end - def valid_token?(%{token_type: "reusable_date_limited"} = token) do - not_overdue_date?(token) and token.uses < token.max_use and not token.used + def valid_invite?(%{invite_type: "reusable_date_limited"} = invite) do + not_overdue_date?(invite) and invite.uses < invite.max_use and not invite.used end - defp not_overdue_date?(%{expire_at: expire_at} = token) do + defp not_overdue_date?(%{expire_at: expire_at} = invite) do Date.compare(Date.utc_today(), expire_at) in [:lt, :eq] || - (Repo.update!(change(token, used: true)) && false) + (update_invite!(invite, %{used: true}) && false) end - def update_usage(%{token_type: "date_limited"}), do: nil + @spec update_usage!(UserInviteToken.t()) :: nil | UserInviteToken.t() | no_return() + def update_usage!(%{invite_type: "date_limited"}), do: nil - def update_usage(%{token_type: "one_time"} = token) do - UserInviteToken.mark_as_used(token.token) - end - - def update_usage(%{token_type: token_type} = token) - when token_type == "reusable" or token_type == "reusable_date_limited" do - new_uses = token.uses + 1 + def update_usage!(%{invite_type: "one_time"} = invite), + do: update_invite!(invite, %{used: true}) + def update_usage!(%{invite_type: invite_type} = invite) + when invite_type == "reusable" or invite_type == "reusable_date_limited" do changes = %{ - uses: new_uses + uses: invite.uses + 1 } changes = - if new_uses >= token.max_use do + if changes.uses >= invite.max_use do Map.put(changes, :used, true) else changes end - change(token, changes) |> Repo.update!() + update_invite!(invite, changes) end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index a578fbbf4..402fd195f 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -129,7 +129,7 @@ def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do end def register_user(params) do - token_string = params["token"] + token = params["token"] params = %{ nickname: params["nickname"], @@ -163,29 +163,29 @@ def register_user(params) do {:error, %{error: Jason.encode!(%{captcha: [error]})}} else registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - registration_process(registrations_open, params, token_string) + registration_process(registrations_open, params, token) end end - defp registration_process(_registration_open = true, params, _token_string) do + defp registration_process(_registration_open = true, params, _token) do create_user(params) end - defp registration_process(registration_open, params, token_string) + defp registration_process(registration_open, params, token) when registration_open == false or is_nil(registration_open) do - token = - unless is_nil(token_string) do - Repo.get_by(UserInviteToken, %{token: token_string}) + invite = + unless is_nil(token) do + Repo.get_by(UserInviteToken, %{token: token}) end - valid_token? = token && UserInviteToken.valid_token?(token) + valid_invite? = invite && UserInviteToken.valid_invite?(invite) - case token do + case invite do nil -> {:error, "Invalid token"} - token when valid_token? -> - UserInviteToken.update_usage(token) + invite when valid_invite? -> + UserInviteToken.update_usage!(invite) create_user(params) _ -> diff --git a/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs index abdd5e277..46fa1cb32 100644 --- a/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs +++ b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs @@ -6,7 +6,7 @@ def change do add(:expire_at, :date) add(:uses, :integer, default: 0) add(:max_use, :integer) - add(:token_type, :string, default: "one_time") + add(:invite_type, :string, default: "one_time") end end end diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index c55711b04..c9e5dd625 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -292,10 +292,10 @@ test "token is generated with max use and expire date" do describe "running invites_list" do test "invites are listed" do - {:ok, invite} = Pleroma.UserInviteToken.create_token() + {:ok, invite} = Pleroma.UserInviteToken.create_invite() {:ok, invite2} = - Pleroma.UserInviteToken.create_token(expire_at: Date.utc_today(), max_use: 15) + Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 15}) assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ @@ -314,7 +314,7 @@ test "invites are listed" do describe "running invite revoke" do test "invite is revoked" do - {:ok, invite} = Pleroma.UserInviteToken.create_token(expire_at: Date.utc_today()) + {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today()}) assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 716fccfb2..519141c96 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -370,7 +370,7 @@ test "it registers a new user and parses mentions in the bio" do end test "returns user on success" do - {:ok, token} = UserInviteToken.create_token() + {:ok, invite} = UserInviteToken.create_invite() data = %{ "nickname" => "vinny", @@ -379,15 +379,15 @@ test "returns user on success" do "bio" => "streamer", "password" => "hiptofbees", "confirm" => "hiptofbees", - "token" => token.token + "token" => invite.token } {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - assert token.used == true + assert invite.used == true assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -411,8 +411,8 @@ test "returns error on invalid token" do end test "returns error on expired token" do - {:ok, token} = UserInviteToken.create_token() - UserInviteToken.mark_as_used(token.token) + {:ok, invite} = UserInviteToken.create_invite() + UserInviteToken.update_invite!(invite, used: true) data = %{ "nickname" => "GrimReaper", @@ -421,7 +421,7 @@ test "returns error on expired token" do "bio" => "Your time has come", "password" => "scythe", "confirm" => "scythe", - "token" => token.token + "token" => invite.token } {:error, msg} = TwitterAPI.register_user(data) @@ -449,8 +449,8 @@ test "returns error on expired token" do "confirm" => "hiptofbees" } - check_fn = fn token -> - data = Map.put(data, "token", token.token) + check_fn = fn invite -> + data = Map.put(data, "token", invite.token) {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_by_nickname("vinny") @@ -462,37 +462,37 @@ test "returns error on expired token" do end test "returns user on success", %{check_fn: check_fn} do - {:ok, token} = UserInviteToken.create_token(expire_at: Date.utc_today()) + {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today()}) - check_fn.(token) + check_fn.(invite) - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - refute token.used + refute invite.used end test "returns user on token which expired tomorrow", %{check_fn: check_fn} do - {:ok, token} = UserInviteToken.create_token(expire_at: Date.add(Date.utc_today(), 1)) + {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), 1)}) - check_fn.(token) + check_fn.(invite) - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - refute token.used + refute invite.used end test "returns an error on overdue date", %{data: data} do - {:ok, token} = UserInviteToken.create_token(expire_at: Date.add(Date.utc_today(), -1)) + {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1)}) - data = Map.put(data, "token", token.token) + data = Map.put(data, "token", invite.token) {:error, msg} = TwitterAPI.register_user(data) assert msg == "Expired token" refute User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - assert token.used == true + assert invite.used == true end end @@ -509,9 +509,9 @@ test "returns an error on overdue date", %{data: data} do end test "returns user on success, after him registration fails" do - {:ok, token} = UserInviteToken.create_token(max_use: 100) + {:ok, invite} = UserInviteToken.create_invite(%{max_use: 100}) - Ecto.Changeset.change(token, uses: 99) |> Repo.update!() + UserInviteToken.update_invite!(invite, uses: 99) data = %{ "nickname" => "vinny", @@ -520,14 +520,14 @@ test "returns user on success, after him registration fails" do "bio" => "streamer", "password" => "hiptofbees", "confirm" => "hiptofbees", - "token" => token.token + "token" => invite.token } {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - assert token.used == true + assert invite.used == true assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -539,7 +539,7 @@ test "returns user on success, after him registration fails" do "bio" => "Your time has come", "password" => "scythe", "confirm" => "scythe", - "token" => token.token + "token" => invite.token } {:error, msg} = TwitterAPI.register_user(data) @@ -562,11 +562,7 @@ test "returns user on success, after him registration fails" do end test "returns user on success" do - {:ok, token} = - UserInviteToken.create_token( - expire_at: Date.utc_today(), - max_use: 100 - ) + {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 100}) data = %{ "nickname" => "vinny", @@ -575,27 +571,23 @@ test "returns user on success" do "bio" => "streamer", "password" => "hiptofbees", "confirm" => "hiptofbees", - "token" => token.token + "token" => invite.token } {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) + invite = Repo.get_by(UserInviteToken, token: invite.token) - refute token.used + refute invite.used assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) end test "error after max uses" do - {:ok, token} = - UserInviteToken.create_token( - expire_at: Date.utc_today(), - max_use: 100 - ) + {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 100}) - Ecto.Changeset.change(token, uses: 99) |> Repo.update!() + UserInviteToken.update_invite!(invite, uses: 99) data = %{ "nickname" => "vinny", @@ -604,13 +596,13 @@ test "error after max uses" do "bio" => "streamer", "password" => "hiptofbees", "confirm" => "hiptofbees", - "token" => token.token + "token" => invite.token } {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_by_nickname("vinny") - token = Repo.get_by(UserInviteToken, token: token.token) - assert token.used == true + invite = Repo.get_by(UserInviteToken, token: invite.token) + assert invite.used == true assert UserView.render("show.json", %{user: user}) == UserView.render("show.json", %{user: fetched_user}) @@ -622,7 +614,7 @@ test "error after max uses" do "bio" => "Your time has come", "password" => "scythe", "confirm" => "scythe", - "token" => token.token + "token" => invite.token } {:error, msg} = TwitterAPI.register_user(data) @@ -632,11 +624,8 @@ test "error after max uses" do end test "returns error on overdue date" do - {:ok, token} = - UserInviteToken.create_token( - expire_at: Date.add(Date.utc_today(), -1), - max_use: 100 - ) + {:ok, invite} = + UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1), max_use: 100}) data = %{ "nickname" => "GrimReaper", @@ -645,7 +634,7 @@ test "returns error on overdue date" do "bio" => "Your time has come", "password" => "scythe", "confirm" => "scythe", - "token" => token.token + "token" => invite.token } {:error, msg} = TwitterAPI.register_user(data) @@ -655,13 +644,10 @@ test "returns error on overdue date" do end test "returns error on with overdue date and after max" do - {:ok, token} = - UserInviteToken.create_token( - expire_at: Date.add(Date.utc_today(), -1), - max_use: 100 - ) + {:ok, invite} = + UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1), max_use: 100}) - Ecto.Changeset.change(token, uses: 100) |> Repo.update!() + UserInviteToken.update_invite!(invite, uses: 100) data = %{ "nickname" => "GrimReaper", @@ -670,7 +656,7 @@ test "returns error on with overdue date and after max" do "bio" => "Your time has come", "password" => "scythe", "confirm" => "scythe", - "token" => token.token + "token" => invite.token } {:error, msg} = TwitterAPI.register_user(data) From dcc54f8cfab2c3d278ea3f3eb54d866c8436703a Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 20:25:19 +0700 Subject: [PATCH 19/36] admin api endpoints for invites --- .../web/admin_api/admin_api_controller.ex | 28 ++- .../web/admin_api/views/account_view.ex | 18 ++ lib/pleroma/web/router.ex | 2 + .../admin_api/admin_api_controller_test.exs | 163 ++++++++++++++++-- 4 files changed, 191 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 78bf31893..8b74efdd3 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -9,6 +9,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.Search + alias Pleroma.UserInviteToken + import Pleroma.Web.ControllerHelper, only: [json_response: 3] require Logger @@ -235,7 +237,7 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) with true <- Pleroma.Config.get([:instance, :invites_enabled]) && !Pleroma.Config.get([:instance, :registrations_open]), - {:ok, invite_token} <- Pleroma.UserInviteToken.create_token(), + {:ok, invite_token} <- UserInviteToken.create_invite(), email <- Pleroma.UserEmail.user_invitation_email(user, invite_token, email, params["name"]), {:ok, _} <- Pleroma.Mailer.deliver(email) do @@ -244,11 +246,29 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) end @doc "Get a account registeration invite token (base64 string)" - def get_invite_token(conn, _params) do - {:ok, token} = Pleroma.UserInviteToken.create_token() + def get_invite_token(conn, params) do + options = params["invite"] || %{} + {:ok, invite} = UserInviteToken.create_invite(options) conn - |> json(token.token) + |> json(invite.token) + end + + @doc "Get list of created invites" + def invites_list(conn, _params) do + invites = UserInviteToken.list_invites() + + conn + |> json(AccountView.render("invites.json", %{invites: invites})) + end + + @doc "Revokes invite by token" + def invite_revoke(conn, %{"token" => token}) do + invite = UserInviteToken.find_by_token!(token) + {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) + + conn + |> json(AccountView.render("invite.json", %{invite: updated_invite})) end @doc "Get a password reset token (base64 string) for given nickname" diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 4d6f921ef..fd7917500 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -26,4 +26,22 @@ def render("show.json", %{user: user}) do "tags" => user.tags || [] } end + + def render("invite.json", %{invite: invite}) do + %{ + "id" => invite.id, + "token" => invite.token, + "used" => invite.used, + "expire_at" => invite.expire_at, + "uses" => invite.uses, + "max_use" => invite.max_use, + "invite_type" => invite.invite_type + } + end + + def render("invites.json", %{invites: invites}) do + %{ + invites: render_many(invites, AccountView, "invite.json", as: :invite) + } + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1c752e44c..f628baa44 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -163,6 +163,8 @@ defmodule Pleroma.Web.Router do delete("/relay", AdminAPIController, :relay_unfollow) get("/invite_token", AdminAPIController, :get_invite_token) + get("/invites_list", AdminAPIController, :invites_list) + post("/invite_revoke", AdminAPIController, :invite_revoke) post("/email_invite", AdminAPIController, :email_invite) get("/password_reset", AdminAPIController, :get_password_reset) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index dd2fbfb15..ae287a953 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do use Pleroma.Web.ConnCase alias Pleroma.User + alias Pleroma.UserInviteToken import Pleroma.Factory describe "/api/pleroma/admin/user" do @@ -80,14 +81,13 @@ test "allows to force-follow another user" do user = insert(:user) follower = insert(:user) - conn = - build_conn() - |> assign(:user, admin) - |> put_req_header("accept", "application/json") - |> post("/api/pleroma/admin/user/follow", %{ - "follower" => follower.nickname, - "followed" => user.nickname - }) + build_conn() + |> assign(:user, admin) + |> put_req_header("accept", "application/json") + |> post("/api/pleroma/admin/user/follow", %{ + "follower" => follower.nickname, + "followed" => user.nickname + }) user = User.get_by_id(user.id) follower = User.get_by_id(follower.id) @@ -104,14 +104,13 @@ test "allows to force-unfollow another user" do User.follow(follower, user) - conn = - build_conn() - |> assign(:user, admin) - |> put_req_header("accept", "application/json") - |> post("/api/pleroma/admin/user/unfollow", %{ - "follower" => follower.nickname, - "followed" => user.nickname - }) + build_conn() + |> assign(:user, admin) + |> put_req_header("accept", "application/json") + |> post("/api/pleroma/admin/user/unfollow", %{ + "follower" => follower.nickname, + "followed" => user.nickname + }) user = User.get_by_id(user.id) follower = User.get_by_id(follower.id) @@ -642,4 +641,136 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do "tags" => [] } end + + describe "GET /api/pleroma/admin/invite_token" do + test "without options" do + admin = insert(:user, info: %{is_admin: true}) + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invite_token") + + token = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(token) + refute invite.used + refute invite.expire_at + refute invite.max_use + assert invite.invite_type == "one_time" + end + + test "with expire_at" do + admin = insert(:user, info: %{is_admin: true}) + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invite_token", %{ + "invite" => %{"expire_at" => Date.to_string(Date.utc_today())} + }) + + token = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(token) + + refute invite.used + assert invite.expire_at == Date.utc_today() + refute invite.max_use + assert invite.invite_type == "date_limited" + end + + test "with max_use" do + admin = insert(:user, info: %{is_admin: true}) + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invite_token", %{ + "invite" => %{"max_use" => 150} + }) + + token = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(token) + refute invite.used + refute invite.expire_at + assert invite.max_use == 150 + assert invite.invite_type == "reusable" + end + + test "with max use and expire_at" do + admin = insert(:user, info: %{is_admin: true}) + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invite_token", %{ + "invite" => %{"max_use" => 150, "expire_at" => Date.to_string(Date.utc_today())} + }) + + token = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(token) + refute invite.used + assert invite.expire_at == Date.utc_today() + assert invite.max_use == 150 + assert invite.invite_type == "reusable_date_limited" + end + end + + describe "GET /api/pleroma/admin/invites_list" do + test "no invites" do + admin = insert(:user, info: %{is_admin: true}) + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invites_list") + + assert json_response(conn, 200) == %{"invites" => []} + end + + test "with invite" do + admin = insert(:user, info: %{is_admin: true}) + {:ok, invite} = UserInviteToken.create_invite() + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/invites_list") + + assert json_response(conn, 200) == %{ + "invites" => [ + %{ + "expire_at" => nil, + "id" => invite.id, + "invite_type" => "one_time", + "max_use" => nil, + "token" => invite.token, + "used" => false, + "uses" => 0 + } + ] + } + end + end + + describe "POST /api/pleroma/admin/invite_revoke" do + test "with token" do + admin = insert(:user, info: %{is_admin: true}) + {:ok, invite} = UserInviteToken.create_invite() + + conn = + build_conn() + |> assign(:user, admin) + |> post("/api/pleroma/admin/invite_revoke", %{"token" => invite.token}) + + assert json_response(conn, 200) == %{ + "expire_at" => nil, + "id" => invite.id, + "invite_type" => "one_time", + "max_use" => nil, + "token" => invite.token, + "used" => true, + "uses" => 0 + } + end + end end From a53d591ac5bc8f19965f1edc71836e42bab3e393 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 20:47:05 +0700 Subject: [PATCH 20/36] code style --- lib/pleroma/web/twitter_api/twitter_api.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 402fd195f..9e9a46cf1 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -167,10 +167,6 @@ def register_user(params) do end end - defp registration_process(_registration_open = true, params, _token) do - create_user(params) - end - defp registration_process(registration_open, params, token) when registration_open == false or is_nil(registration_open) do invite = @@ -193,6 +189,10 @@ defp registration_process(registration_open, params, token) end end + defp registration_process(true, params, _token) do + create_user(params) + end + defp create_user(params) do changeset = User.register_changeset(%User{}, params) From 76d8928cf1bf9759d4eb24351eedd0d5f46c3df2 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 20:49:43 +0700 Subject: [PATCH 21/36] fix for field name --- test/tasks/user_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index c9e5dd625..bde8f421c 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -307,8 +307,8 @@ test "invites are listed" do assert_received {:mix_shell, :info, [message2]} assert_received {:mix_shell, :info, [message3]} assert message =~ "Invites list:" - assert message2 =~ invite.token_type - assert message3 =~ invite2.token_type + assert message2 =~ invite.invite_type + assert message3 =~ invite2.invite_type end end From c05fe4da0a9ad119891d2fc6cf82ea3beb59fec7 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Sat, 6 Apr 2019 16:20:06 +0100 Subject: [PATCH 22/36] Document subscription endpoints, fix typos Also adds a quick error case on the subscription endpoints to avoid 500s --- docs/api/pleroma_api.md | 52 ++++++++++++++++++- .../mastodon_api/mastodon_api_controller.ex | 20 ++++--- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index 478c9d874..410f2a955 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -52,7 +52,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * `confirm` * `captcha_solution`: optional, contains provider-specific captcha solution, * `captcha_token`: optional, contains provider-specific captcha token - * `token`: invite token required when the registerations aren't public. + * `token`: invite token required when the registrations aren't public. * Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}` * Example response: ``` @@ -114,5 +114,53 @@ See [Admin-API](Admin-API.md) * Method `POST` * Authentication: required * Params: - * `id`: notifications's id + * `id`: notification's id * Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` + +## `/api/v1/pleroma/accounts/:id/subscribe` +### Subscribe to receive notifications for all statuses posted by a user +* Method `POST` +* Authentication: required +* Params: + * `id`: account id to subscribe to +* Response: JSON, returns a mastodon relationship object on success, otherwise returns `{"error": "error_msg"}` +* Example response: +```json +{ + id: "abcdefg", + following: true, + followed_by: false, + blocking: false, + muting: false, + muting_notifications: false, + subscribing: true, + requested: false, + domain_blocking: false, + showing_reblogs: true, + endorsed: false +} +``` + +## `/api/v1/pleroma/accounts/:id/unsubscribe` +### Unsubscribe to stop receiving notifications from user statuses +* Method `POST` +* Authentication: required +* Params: + * `id`: account id to unsubscribe from +* Response: JSON, returns a mastodon relationship object on success, otherwise returns `{"error": "error_msg"}` +* Example response: +```json +{ + id: "abcdefg", + following: true, + followed_by: false, + blocking: false, + muting: false, + muting_notifications: false, + subscribing: false, + requested: false, + domain_blocking: false, + showing_reblogs: true, + endorsed: false +} +``` diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index e848895f1..a7c9c4735 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -864,22 +864,30 @@ def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) d end def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_by_id(id) do - {:ok, subscription_target} = User.subscribe(user, subscription_target) - + with %User{} = subscription_target <- User.get_by_id(id), + {:ok, subscription_target} = User.subscribe(user, subscription_target) do conn |> put_view(AccountView) |> render("relationship.json", %{user: user, target: subscription_target}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) end end def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_by_id(id) do - {:ok, subscription_target} = User.unsubscribe(user, subscription_target) - + with %User{} = subscription_target <- User.get_by_id(id), + {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do conn |> put_view(AccountView) |> render("relationship.json", %{user: user, target: subscription_target}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) end end From e6778003abcccdf35fe098a571023ed5f5a20323 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Sat, 6 Apr 2019 16:24:21 +0100 Subject: [PATCH 23/36] JSON need quotes! --- docs/api/pleroma_api.md | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index 410f2a955..75569e092 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -127,17 +127,17 @@ See [Admin-API](Admin-API.md) * Example response: ```json { - id: "abcdefg", - following: true, - followed_by: false, - blocking: false, - muting: false, - muting_notifications: false, - subscribing: true, - requested: false, - domain_blocking: false, - showing_reblogs: true, - endorsed: false + "id": "abcdefg", + "following": true, + "followed_by": false, + "blocking": false, + "muting": false, + "muting_notifications": false, + "subscribing": true, + "requested": false, + "domain_blocking": false, + "showing_reblogs": true, + "endorsed": false } ``` @@ -151,16 +151,16 @@ See [Admin-API](Admin-API.md) * Example response: ```json { - id: "abcdefg", - following: true, - followed_by: false, - blocking: false, - muting: false, - muting_notifications: false, - subscribing: false, - requested: false, - domain_blocking: false, - showing_reblogs: true, - endorsed: false + "id": "abcdefg", + "following": true, + "followed_by": false, + "blocking": false, + "muting": false, + "muting_notifications": false, + "subscribing": false, + "requested": false, + "domain_blocking": false, + "showing_reblogs": true, + "endorsed": false } ``` From ce8d45713287d8f1c413699385950f295085ee77 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sat, 6 Apr 2019 22:38:35 +0700 Subject: [PATCH 24/36] little channges --- lib/mix/tasks/pleroma/user.ex | 58 ++++++++++++++++---------------- lib/pleroma/user_invite_token.ex | 7 ++++ test/tasks/user_test.exs | 21 ++++++------ 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 887f45029..80b07d1ac 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -30,16 +30,16 @@ defmodule Mix.Tasks.Pleroma.User do mix pleroma.user invite [OPTION...] Options: - - `--expire_date DATE` - last day on which token is active (e.g. "2019-04-05") - - `--max_use NUMBER` - maximum numbers of token use + - `--expire_at DATE` - last day on which token is active (e.g. "2019-04-05") + - `--max_use NUMBER` - maximum numbers of token uses - ## Generated invites list + ## List generated invites - mix pleroma.user invites_list + mix pleroma.user invites ## Revoke invite - mix pleroma.user invite_revoke TOKEN OR TOKEN_ID + mix pleroma.user revoke_invite TOKEN OR TOKEN_ID ## Delete the user's account. @@ -304,21 +304,24 @@ def run(["invite" | rest]) do {options, [], []} = OptionParser.parse(rest, strict: [ - expire_date: :string, + expire_at: :string, max_use: :integer ] ) - expire_at = - with expire_date when expire_date != nil <- Keyword.get(options, :expire_date) do - Date.from_iso8601!(expire_date) - end + options = + options + |> Keyword.update(:expire_at, {:ok, nil}, fn + nil -> {:ok, nil} + val -> Date.from_iso8601(val) + end) + |> Enum.into(%{}) - options = Keyword.put(options, :expire_at, expire_at) - options = Enum.into(options, %{}) Common.start_pleroma() - with {:ok, invite} <- UserInviteToken.create_invite(options) do + with {:ok, val} <- options[:expire_at], + options = Map.put(options, :expire_at, val), + {:ok, invite} <- UserInviteToken.create_invite(options) do Mix.shell().info( "Generated user invite token " <> String.replace(invite.invite_type, "_", " ") ) @@ -332,44 +335,41 @@ def run(["invite" | rest]) do IO.puts(url) else - _ -> - Mix.shell().error("Could not create invite token.") + error -> + Mix.shell().error("Could not create invite token: #{inspect(error)}") end end - def run(["invites_list"]) do + def run(["invites"]) do Common.start_pleroma() Mix.shell().info("Invites list:") UserInviteToken.list_invites() |> Enum.each(fn invite -> - expire_date = - case invite.expire_at do - nil -> nil - date -> " | Expire date: #{Date.to_string(date)}" + expire_info = + with expire_at when not is_nil(expire_at) <- invite.expire_at do + " | Expire at: #{Date.to_string(expire_at)}" end using_info = - case invite.max_use do - nil -> nil - max_use -> " | Max use: #{max_use} Left use: #{max_use - invite.uses}" + with max_use when not is_nil(max_use) <- invite.max_use do + " | Max use: #{max_use} Left use: #{max_use - invite.uses}" end Mix.shell().info( - "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.token_type} | Used: #{ + "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{ invite.used - }#{expire_date}#{using_info}" + }#{expire_info}#{using_info}" ) end) end - def run(["invite_revoke", token]) do + def run(["revoke_invite", token]) do Common.start_pleroma() - invite = UserInviteToken.find_by_token!(token) - - with {:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do + with {:ok, invite} <- UserInviteToken.find_by_token(token), + {:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do Mix.shell().info("Invite for token #{token} was revoked.") else _ -> Mix.shell().error("No invite found with token #{token}") diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 4efdbdc32..f08309485 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -71,6 +71,13 @@ def update_invite(invite, changes) do @spec find_by_token!(token()) :: UserInviteToken.t() | no_return() def find_by_token!(token), do: Repo.get_by!(UserInviteToken, token: token) + @spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil + def find_by_token(token) do + with invite <- Repo.get_by(UserInviteToken, token: token) do + {:ok, invite} + end + end + @spec valid_invite?(UserInviteToken.t()) :: boolean() def valid_invite?(%{invite_type: "one_time"} = invite) do not invite.used diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index bde8f421c..630ac06c1 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -252,7 +252,7 @@ test "token is generated with expire_at" do assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ "invite", - "--expire-date", + "--expire-at", Date.to_string(Date.utc_today()) ]) end) @@ -280,7 +280,7 @@ test "token is generated with max use and expire date" do "invite", "--max-use", "5", - "--expire-date", + "--expire-at", Date.to_string(Date.utc_today()) ]) end) @@ -290,18 +290,19 @@ test "token is generated with max use and expire date" do end end - describe "running invites_list" do + describe "running invites" do test "invites are listed" do {:ok, invite} = Pleroma.UserInviteToken.create_invite() {:ok, invite2} = Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 15}) - assert capture_io(fn -> - Mix.Tasks.Pleroma.User.run([ - "invites_list" - ]) - end) + # assert capture_io(fn -> + Mix.Tasks.Pleroma.User.run([ + "invites" + ]) + + # end) assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message2]} @@ -312,13 +313,13 @@ test "invites are listed" do end end - describe "running invite revoke" do + describe "running revoke_invite" do test "invite is revoked" do {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today()}) assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ - "invite_revoke", + "revoke_invite", invite.token ]) end) From b810aac117563a941b50180f19bca2d96a329a0a Mon Sep 17 00:00:00 2001 From: Alex S Date: Sun, 7 Apr 2019 19:48:52 +0700 Subject: [PATCH 25/36] added docs to docs/api/admin_api.md code style and little renamings --- docs/api/admin_api.md | 59 ++++++++++++++++++- .../web/admin_api/admin_api_controller.ex | 5 +- lib/pleroma/web/router.ex | 4 +- .../admin_api/admin_api_controller_test.exs | 10 ++-- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md index 86cacebb1..638b235b8 100644 --- a/docs/api/admin_api.md +++ b/docs/api/admin_api.md @@ -200,11 +200,64 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret ## `/api/pleroma/admin/invite_token` -### Get a account registeration invite token +### Get an account registration invite token + +- Methods: `GET` +- Params: + - *optional* `invite` => [ + - *optional* `max_use` (integer) + - *optional* `expire_at` (date string e.g. "2019-04-07") + ] +- Response: invite token (base64 string) + +## `/api/pleroma/admin/invites` + +### Get a list of generated invites - Methods: `GET` - Params: none -- Response: invite token (base64 string) +- Response: + +```JSON +{ + + "invites": [ + { + "id": integer, + "token": string, + "used": boolean, + "expire_at": date, + "uses": integer, + "max_use": integer, + "invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`) + }, + ... + ] +} +``` + +## `/api/pleroma/admin/revoke_invite` + +### Revoke invite by token + +- Methods: `POST` +- Params: + - `token` +- Response: + +```JSON +{ + "id": integer, + "token": string, + "used": boolean, + "expire_at": date, + "uses": integer, + "max_use": integer, + "invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`) + +} +``` + ## `/api/pleroma/admin/email_invite` @@ -213,7 +266,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - Methods: `POST` - Params: - `email` - - `name`, optionnal + - `name`, optional ## `/api/pleroma/admin/password_reset` diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 8b74efdd3..df729ee02 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.Search - alias Pleroma.UserInviteToken import Pleroma.Web.ControllerHelper, only: [json_response: 3] @@ -255,7 +254,7 @@ def get_invite_token(conn, params) do end @doc "Get list of created invites" - def invites_list(conn, _params) do + def invites(conn, _params) do invites = UserInviteToken.list_invites() conn @@ -263,7 +262,7 @@ def invites_list(conn, _params) do end @doc "Revokes invite by token" - def invite_revoke(conn, %{"token" => token}) do + def revoke_invite(conn, %{"token" => token}) do invite = UserInviteToken.find_by_token!(token) {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f628baa44..b65eaf357 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -163,8 +163,8 @@ defmodule Pleroma.Web.Router do delete("/relay", AdminAPIController, :relay_unfollow) get("/invite_token", AdminAPIController, :get_invite_token) - get("/invites_list", AdminAPIController, :invites_list) - post("/invite_revoke", AdminAPIController, :invite_revoke) + get("/invites", AdminAPIController, :invites) + post("/revoke_invite", AdminAPIController, :revoke_invite) post("/email_invite", AdminAPIController, :email_invite) get("/password_reset", AdminAPIController, :get_password_reset) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index ae287a953..67ee0f3fa 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -715,14 +715,14 @@ test "with max use and expire_at" do end end - describe "GET /api/pleroma/admin/invites_list" do + describe "GET /api/pleroma/admin/invites" do test "no invites" do admin = insert(:user, info: %{is_admin: true}) conn = build_conn() |> assign(:user, admin) - |> get("/api/pleroma/admin/invites_list") + |> get("/api/pleroma/admin/invites") assert json_response(conn, 200) == %{"invites" => []} end @@ -734,7 +734,7 @@ test "with invite" do conn = build_conn() |> assign(:user, admin) - |> get("/api/pleroma/admin/invites_list") + |> get("/api/pleroma/admin/invites") assert json_response(conn, 200) == %{ "invites" => [ @@ -752,7 +752,7 @@ test "with invite" do end end - describe "POST /api/pleroma/admin/invite_revoke" do + describe "POST /api/pleroma/admin/revoke_invite" do test "with token" do admin = insert(:user, info: %{is_admin: true}) {:ok, invite} = UserInviteToken.create_invite() @@ -760,7 +760,7 @@ test "with token" do conn = build_conn() |> assign(:user, admin) - |> post("/api/pleroma/admin/invite_revoke", %{"token" => invite.token}) + |> post("/api/pleroma/admin/revoke_invite", %{"token" => invite.token}) assert json_response(conn, 200) == %{ "expire_at" => nil, From 56c75aec123bc22bd0be94cef627beec324bff15 Mon Sep 17 00:00:00 2001 From: Alex S Date: Sun, 7 Apr 2019 20:59:53 +0700 Subject: [PATCH 26/36] credo fix --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index df729ee02..70a5b5c5d 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -5,10 +5,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller alias Pleroma.User + alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.Search - alias Pleroma.UserInviteToken import Pleroma.Web.ControllerHelper, only: [json_response: 3] From 76fdfd1c7f10332783ef167b7b996edd4813b1c3 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Sun, 7 Apr 2019 15:11:29 +0100 Subject: [PATCH 27/36] Add check on activity visibility in sub notification --- lib/pleroma/web/common_api/utils.ex | 4 +++- test/notification_test.exs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 4e0a6b2d9..a450a70c3 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy @@ -337,13 +338,14 @@ def maybe_notify_mentioned_recipients(recipients, _), do: recipients def maybe_notify_subscribers( recipients, - %Activity{data: %{"actor" => actor, "type" => type}} + %Activity{data: %{"actor" => actor, "type" => type}} = activity ) when type == "Create" do with %User{} = user <- User.get_by_ap_id(actor) do subscriber_ids = user |> User.subscribers() + |> Enum.filter(&Visibility.visible_for_user?(activity, &1)) |> Enum.map(& &1.ap_id) recipients ++ subscriber_ids diff --git a/test/notification_test.exs b/test/notification_test.exs index 8109623af..5727620c9 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -106,6 +106,18 @@ test "it doesn't create duplicate notifications for follow+subscribed users" do {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) {:ok, [_notif]} = Notification.create_notifications(status) end + + test "it doesn't create subscription notifications if the recipient cannot see the status" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, status} = + TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"}) + + assert {:ok, []} == Notification.create_notifications(status) + end end describe "get notification" do From 012bb5dcc9bfbf6f3ea210ec4e865f3adcea9dfd Mon Sep 17 00:00:00 2001 From: Alex S Date: Mon, 8 Apr 2019 16:01:28 +0700 Subject: [PATCH 28/36] renaming expire_at -> expires_at keyword style change --- docs/api/admin_api.md | 6 +++--- lib/mix/tasks/pleroma/user.ex | 14 ++++++------- lib/pleroma/user_invite_token.ex | 12 +++++------ .../web/admin_api/views/account_view.ex | 2 +- ...50946_add_fields_to_user_invite_tokens.exs | 2 +- test/tasks/user_test.exs | 12 +++++------ .../admin_api/admin_api_controller_test.exs | 20 +++++++++---------- test/web/twitter_api/twitter_api_test.exs | 14 ++++++------- 8 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md index 638b235b8..8befa8ea0 100644 --- a/docs/api/admin_api.md +++ b/docs/api/admin_api.md @@ -206,7 +206,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - Params: - *optional* `invite` => [ - *optional* `max_use` (integer) - - *optional* `expire_at` (date string e.g. "2019-04-07") + - *optional* `expires_at` (date string e.g. "2019-04-07") ] - Response: invite token (base64 string) @@ -226,7 +226,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret "id": integer, "token": string, "used": boolean, - "expire_at": date, + "expires_at": date, "uses": integer, "max_use": integer, "invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`) @@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret "id": integer, "token": string, "used": boolean, - "expire_at": date, + "expires_at": date, "uses": integer, "max_use": integer, "invite_type": string (possible values: `one_time`, `reusable`, `date_limited`, `reusable_date_limited`) diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 80b07d1ac..441168df2 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Pleroma.User do mix pleroma.user invite [OPTION...] Options: - - `--expire_at DATE` - last day on which token is active (e.g. "2019-04-05") + - `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05") - `--max_use NUMBER` - maximum numbers of token uses ## List generated invites @@ -304,14 +304,14 @@ def run(["invite" | rest]) do {options, [], []} = OptionParser.parse(rest, strict: [ - expire_at: :string, + expires_at: :string, max_use: :integer ] ) options = options - |> Keyword.update(:expire_at, {:ok, nil}, fn + |> Keyword.update(:expires_at, {:ok, nil}, fn nil -> {:ok, nil} val -> Date.from_iso8601(val) end) @@ -319,8 +319,8 @@ def run(["invite" | rest]) do Common.start_pleroma() - with {:ok, val} <- options[:expire_at], - options = Map.put(options, :expire_at, val), + with {:ok, val} <- options[:expires_at], + options = Map.put(options, :expires_at, val), {:ok, invite} <- UserInviteToken.create_invite(options) do Mix.shell().info( "Generated user invite token " <> String.replace(invite.invite_type, "_", " ") @@ -348,8 +348,8 @@ def run(["invites"]) do UserInviteToken.list_invites() |> Enum.each(fn invite -> expire_info = - with expire_at when not is_nil(expire_at) <- invite.expire_at do - " | Expire at: #{Date.to_string(expire_at)}" + with expires_at when not is_nil(expires_at) <- invite.expires_at do + " | Expires at: #{Date.to_string(expires_at)}" end using_info = diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index f08309485..11f1dcb16 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -16,7 +16,7 @@ defmodule Pleroma.UserInviteToken do field(:token, :string) field(:used, :boolean, default: false) field(:max_use, :integer) - field(:expire_at, :date) + field(:expires_at, :date) field(:uses, :integer, default: 0) field(:invite_type, :string) @@ -26,7 +26,7 @@ defmodule Pleroma.UserInviteToken do @spec create_invite(map()) :: UserInviteToken.t() def create_invite(params \\ %{}) do %UserInviteToken{} - |> cast(params, ~w(max_use expire_at)a) + |> cast(params, [:max_use, :expires_at]) |> add_token() |> assign_type() |> Repo.insert() @@ -37,11 +37,11 @@ defp add_token(changeset) do put_change(changeset, :token, token) end - defp assign_type(%{changes: %{max_use: _max_use, expire_at: _expire_at}} = changeset) do + defp assign_type(%{changes: %{max_use: _max_use, expires_at: _expires_at}} = changeset) do put_change(changeset, :invite_type, "reusable_date_limited") end - defp assign_type(%{changes: %{expire_at: _expire_at}} = changeset) do + defp assign_type(%{changes: %{expires_at: _expires_at}} = changeset) do put_change(changeset, :invite_type, "date_limited") end @@ -95,8 +95,8 @@ def valid_invite?(%{invite_type: "reusable_date_limited"} = invite) do not_overdue_date?(invite) and invite.uses < invite.max_use and not invite.used end - defp not_overdue_date?(%{expire_at: expire_at} = invite) do - Date.compare(Date.utc_today(), expire_at) in [:lt, :eq] || + defp not_overdue_date?(%{expires_at: expires_at} = invite) do + Date.compare(Date.utc_today(), expires_at) in [:lt, :eq] || (update_invite!(invite, %{used: true}) && false) end diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index fd7917500..28bb667d8 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -32,7 +32,7 @@ def render("invite.json", %{invite: invite}) do "id" => invite.id, "token" => invite.token, "used" => invite.used, - "expire_at" => invite.expire_at, + "expires_at" => invite.expires_at, "uses" => invite.uses, "max_use" => invite.max_use, "invite_type" => invite.invite_type diff --git a/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs index 46fa1cb32..211a14135 100644 --- a/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs +++ b/priv/repo/migrations/20190404050946_add_fields_to_user_invite_tokens.exs @@ -3,7 +3,7 @@ defmodule Pleroma.Repo.Migrations.AddFieldsToUserInviteTokens do def change do alter table(:user_invite_tokens) do - add(:expire_at, :date) + add(:expires_at, :date) add(:uses, :integer, default: 0) add(:max_use, :integer) add(:invite_type, :string, default: "one_time") diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 630ac06c1..242265da5 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -248,11 +248,11 @@ test "invite token is generated" do assert message =~ "Generated user invite token one time" end - test "token is generated with expire_at" do + test "token is generated with expires_at" do assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ "invite", - "--expire-at", + "--expires-at", Date.to_string(Date.utc_today()) ]) end) @@ -274,13 +274,13 @@ test "token is generated with max use" do assert message =~ "Generated user invite token reusable" end - test "token is generated with max use and expire date" do + test "token is generated with max use and expires date" do assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ "invite", "--max-use", "5", - "--expire-at", + "--expires-at", Date.to_string(Date.utc_today()) ]) end) @@ -295,7 +295,7 @@ test "invites are listed" do {:ok, invite} = Pleroma.UserInviteToken.create_invite() {:ok, invite2} = - Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 15}) + Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 15}) # assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ @@ -315,7 +315,7 @@ test "invites are listed" do describe "running revoke_invite" do test "invite is revoked" do - {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expire_at: Date.utc_today()}) + {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today()}) assert capture_io(fn -> Mix.Tasks.Pleroma.User.run([ diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 67ee0f3fa..d44392c9d 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -654,26 +654,26 @@ test "without options" do token = json_response(conn, 200) invite = UserInviteToken.find_by_token!(token) refute invite.used - refute invite.expire_at + refute invite.expires_at refute invite.max_use assert invite.invite_type == "one_time" end - test "with expire_at" do + test "with expires_at" do admin = insert(:user, info: %{is_admin: true}) conn = build_conn() |> assign(:user, admin) |> get("/api/pleroma/admin/invite_token", %{ - "invite" => %{"expire_at" => Date.to_string(Date.utc_today())} + "invite" => %{"expires_at" => Date.to_string(Date.utc_today())} }) token = json_response(conn, 200) invite = UserInviteToken.find_by_token!(token) refute invite.used - assert invite.expire_at == Date.utc_today() + assert invite.expires_at == Date.utc_today() refute invite.max_use assert invite.invite_type == "date_limited" end @@ -691,25 +691,25 @@ test "with max_use" do token = json_response(conn, 200) invite = UserInviteToken.find_by_token!(token) refute invite.used - refute invite.expire_at + refute invite.expires_at assert invite.max_use == 150 assert invite.invite_type == "reusable" end - test "with max use and expire_at" do + test "with max use and expires_at" do admin = insert(:user, info: %{is_admin: true}) conn = build_conn() |> assign(:user, admin) |> get("/api/pleroma/admin/invite_token", %{ - "invite" => %{"max_use" => 150, "expire_at" => Date.to_string(Date.utc_today())} + "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())} }) token = json_response(conn, 200) invite = UserInviteToken.find_by_token!(token) refute invite.used - assert invite.expire_at == Date.utc_today() + assert invite.expires_at == Date.utc_today() assert invite.max_use == 150 assert invite.invite_type == "reusable_date_limited" end @@ -739,7 +739,7 @@ test "with invite" do assert json_response(conn, 200) == %{ "invites" => [ %{ - "expire_at" => nil, + "expires_at" => nil, "id" => invite.id, "invite_type" => "one_time", "max_use" => nil, @@ -763,7 +763,7 @@ test "with token" do |> post("/api/pleroma/admin/revoke_invite", %{"token" => invite.token}) assert json_response(conn, 200) == %{ - "expire_at" => nil, + "expires_at" => nil, "id" => invite.id, "invite_type" => "one_time", "max_use" => nil, diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 519141c96..798a009fe 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -462,7 +462,7 @@ test "returns error on expired token" do end test "returns user on success", %{check_fn: check_fn} do - {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today()}) + {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today()}) check_fn.(invite) @@ -472,7 +472,7 @@ test "returns user on success", %{check_fn: check_fn} do end test "returns user on token which expired tomorrow", %{check_fn: check_fn} do - {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), 1)}) + {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), 1)}) check_fn.(invite) @@ -482,7 +482,7 @@ test "returns user on token which expired tomorrow", %{check_fn: check_fn} do end test "returns an error on overdue date", %{data: data} do - {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1)}) + {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1)}) data = Map.put(data, "token", invite.token) @@ -562,7 +562,7 @@ test "returns user on success, after him registration fails" do end test "returns user on success" do - {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 100}) + {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) data = %{ "nickname" => "vinny", @@ -585,7 +585,7 @@ test "returns user on success" do end test "error after max uses" do - {:ok, invite} = UserInviteToken.create_invite(%{expire_at: Date.utc_today(), max_use: 100}) + {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) UserInviteToken.update_invite!(invite, uses: 99) @@ -625,7 +625,7 @@ test "error after max uses" do test "returns error on overdue date" do {:ok, invite} = - UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1), max_use: 100}) + UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) data = %{ "nickname" => "GrimReaper", @@ -645,7 +645,7 @@ test "returns error on overdue date" do test "returns error on with overdue date and after max" do {:ok, invite} = - UserInviteToken.create_invite(%{expire_at: Date.add(Date.utc_today(), -1), max_use: 100}) + UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) UserInviteToken.update_invite!(invite, uses: 100) From f0ca0696c812e804ddaa840d4cdeab032dbdf1d0 Mon Sep 17 00:00:00 2001 From: Alex S Date: Mon, 8 Apr 2019 17:06:04 +0700 Subject: [PATCH 29/36] test for valid_invite? --- test/user_invite_token_test.exs | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/user_invite_token_test.exs diff --git a/test/user_invite_token_test.exs b/test/user_invite_token_test.exs new file mode 100644 index 000000000..276788254 --- /dev/null +++ b/test/user_invite_token_test.exs @@ -0,0 +1,96 @@ +defmodule Pleroma.UserInviteTokenTest do + use ExUnit.Case, async: true + use Pleroma.DataCase + alias Pleroma.UserInviteToken + + describe "valid_invite?/1 one time invites" do + setup do + invite = %UserInviteToken{invite_type: "one_time"} + + {:ok, invite: invite} + end + + test "not used returns true", %{invite: invite} do + invite = %{invite | used: false} + assert UserInviteToken.valid_invite?(invite) + end + + test "used returns false", %{invite: invite} do + invite = %{invite | used: true} + refute UserInviteToken.valid_invite?(invite) + end + end + + describe "valid_invite?/1 reusable invites" do + setup do + invite = %UserInviteToken{ + invite_type: "reusable", + max_use: 5 + } + + {:ok, invite: invite} + end + + test "with less uses then max use returns true", %{invite: invite} do + invite = %{invite | uses: 4} + assert UserInviteToken.valid_invite?(invite) + end + + test "with equal or more uses then max use returns false", %{invite: invite} do + invite = %{invite | uses: 5} + + refute UserInviteToken.valid_invite?(invite) + + invite = %{invite | uses: 6} + + refute UserInviteToken.valid_invite?(invite) + end + end + + describe "valid_token?/1 date limited invites" do + setup do + invite = %UserInviteToken{invite_type: "date_limited"} + {:ok, invite: invite} + end + + test "expires today returns true", %{invite: invite} do + invite = %{invite | expires_at: Date.utc_today()} + assert UserInviteToken.valid_invite?(invite) + end + + test "expires yesterday returns false", %{invite: invite} do + invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)} + invite = Repo.insert!(invite) + refute UserInviteToken.valid_invite?(invite) + end + end + + describe "valid_token?/1 reusable date limited invites" do + setup do + invite = %UserInviteToken{invite_type: "reusable_date_limited", max_use: 5} + {:ok, invite: invite} + end + + test "not overdue date and less uses returns true", %{invite: invite} do + invite = %{invite | expires_at: Date.utc_today(), uses: 4} + assert UserInviteToken.valid_invite?(invite) + end + + test "overdue date and less uses returns false", %{invite: invite} do + invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)} + invite = Repo.insert!(invite) + refute UserInviteToken.valid_invite?(invite) + end + + test "not overdue date with more uses returns false", %{invite: invite} do + invite = %{invite | expires_at: Date.utc_today(), uses: 5} + refute UserInviteToken.valid_invite?(invite) + end + + test "overdue date with more uses returns false", %{invite: invite} do + invite = %{invite | expires_at: Date.add(Date.utc_today(), -1), uses: 5} + invite = Repo.insert!(invite) + refute UserInviteToken.valid_invite?(invite) + end + end +end From 13ff312c48c338c93ee9aa85ec2e48c2059d5168 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 8 Apr 2019 12:46:12 +0100 Subject: [PATCH 30/36] Sever subscription in case of block --- lib/pleroma/user.ex | 31 ++++++++++++++++++++++++------- test/user_test.exs | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f1565ade7..9a80601c1 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -924,14 +924,23 @@ def unmute(muter, %{ap_id: ap_id}) do end def subscribe(subscriber, %{ap_id: ap_id}) do - with %User{} = user <- get_or_fetch_by_ap_id(ap_id) do - info_cng = - user.info - |> User.Info.add_to_subscribers(subscriber.ap_id) + user_config = Application.get_env(:pleroma, :user) + deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) - change(user) - |> put_embed(:info, info_cng) - |> update_and_set_cache() + with %User{} = subscribed <- get_or_fetch_by_ap_id(ap_id) do + blocked = blocks?(subscribed, subscriber) and deny_follow_blocked + + if blocked do + {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} + else + info_cng = + subscribed.info + |> User.Info.add_to_subscribers(subscriber.ap_id) + + change(subscribed) + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end end end @@ -957,6 +966,14 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do blocker end + blocker = + if subscribed_to?(blocked, blocker) do + {:ok, blocker} = unsubscribe(blocked, blocker) + blocker + else + blocker + end + if following?(blocked, blocker) do unfollow(blocked, blocker) end diff --git a/test/user_test.exs b/test/user_test.exs index 38712cebb..2368f95f5 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -146,6 +146,15 @@ test "can't follow a user who blocked us" do {:error, _} = User.follow(blockee, blocker) end + test "can't subscribe to a user who blocked us" do + blocker = insert(:user) + blocked = insert(:user) + + {:ok, blocker} = User.block(blocker, blocked) + + {:error, _} = User.subscribe(blocked, blocker) + end + test "local users do not automatically follow local locked accounts" do follower = insert(:user, info: %{locked: true}) followed = insert(:user, info: %{locked: true}) @@ -729,6 +738,22 @@ test "blocks tear down blocked->blocker follow relationships" do refute User.following?(blocker, blocked) refute User.following?(blocked, blocker) end + + test "blocks tear down blocked->blocker subscription relationships" do + blocker = insert(:user) + blocked = insert(:user) + + {:ok, blocker} = User.subscribe(blocked, blocker) + + assert User.subscribed_to?(blocked, blocker) + refute User.subscribed_to?(blocker, blocked) + + {:ok, blocker} = User.block(blocker, blocked) + + assert User.blocks?(blocker, blocked) + refute User.subscribed_to?(blocker, blocked) + refute User.subscribed_to?(blocked, blocker) + end end describe "domain blocking" do From 87a01f9ee061d513ede1a18ab494778f4b2eb3a8 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 8 Apr 2019 13:04:30 +0100 Subject: [PATCH 31/36] Use Pleroma.Config instead of Application.get_env --- lib/pleroma/user.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9a80601c1..15f606c5f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -924,8 +924,7 @@ def unmute(muter, %{ap_id: ap_id}) do end def subscribe(subscriber, %{ap_id: ap_id}) do - user_config = Application.get_env(:pleroma, :user) - deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) + deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) with %User{} = subscribed <- get_or_fetch_by_ap_id(ap_id) do blocked = blocks?(subscribed, subscriber) and deny_follow_blocked From d0696fdfd6acaee011f4011d69b8221448bfa89e Mon Sep 17 00:00:00 2001 From: Alex S Date: Mon, 8 Apr 2019 19:16:59 +0700 Subject: [PATCH 32/36] fix for elixir 1.7.4 --- lib/pleroma/user_invite_token.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 11f1dcb16..6247831d5 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -10,6 +10,7 @@ defmodule Pleroma.UserInviteToken do alias Pleroma.Repo alias Pleroma.UserInviteToken + @type t :: %__MODULE__{} @type token :: String.t() schema "user_invite_tokens" do From fee50636d07c54328ececfe8805c658e3bb80cc6 Mon Sep 17 00:00:00 2001 From: Alex S Date: Mon, 8 Apr 2019 20:08:16 +0700 Subject: [PATCH 33/36] removing not needed invite update --- lib/pleroma/user_invite_token.ex | 5 ++--- test/web/twitter_api/twitter_api_test.exs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 6247831d5..86f0a5486 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -96,9 +96,8 @@ def valid_invite?(%{invite_type: "reusable_date_limited"} = invite) do not_overdue_date?(invite) and invite.uses < invite.max_use and not invite.used end - defp not_overdue_date?(%{expires_at: expires_at} = invite) do - Date.compare(Date.utc_today(), expires_at) in [:lt, :eq] || - (update_invite!(invite, %{used: true}) && false) + defp not_overdue_date?(%{expires_at: expires_at}) do + Date.compare(Date.utc_today(), expires_at) in [:lt, :eq] end @spec update_usage!(UserInviteToken.t()) :: nil | UserInviteToken.t() | no_return() diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 798a009fe..a4540e651 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -492,7 +492,7 @@ test "returns an error on overdue date", %{data: data} do refute User.get_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) - assert invite.used == true + refute invite.used end end From 589629cf3e1bee9a4f6e92b8456d8614eff73c89 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 8 Apr 2019 15:56:14 +0100 Subject: [PATCH 34/36] Used cached user resources in subscriptions --- lib/pleroma/user.ex | 4 ++-- lib/pleroma/web/common_api/utils.ex | 2 +- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 15f606c5f..8a0243604 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -926,7 +926,7 @@ def unmute(muter, %{ap_id: ap_id}) do def subscribe(subscriber, %{ap_id: ap_id}) do deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) - with %User{} = subscribed <- get_or_fetch_by_ap_id(ap_id) do + with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do blocked = blocks?(subscribed, subscriber) and deny_follow_blocked if blocked do @@ -944,7 +944,7 @@ def subscribe(subscriber, %{ap_id: ap_id}) do end def unsubscribe(unsubscriber, %{ap_id: ap_id}) do - with %User{} = user <- get_or_fetch_by_ap_id(ap_id) do + with %User{} = user <- get_cached_by_ap_id(ap_id) do info_cng = user.info |> User.Info.remove_from_subscribers(unsubscriber.ap_id) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index a450a70c3..a830ea5d2 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -341,7 +341,7 @@ def maybe_notify_subscribers( %Activity{data: %{"actor" => actor, "type" => type}} = activity ) when type == "Create" do - with %User{} = user <- User.get_by_ap_id(actor) do + with %User{} = user <- User.get_cached_by_ap_id(actor) do subscriber_ids = user |> User.subscribers() diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index a7c9c4735..d9c0cd55e 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -864,7 +864,7 @@ def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) d end def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_by_id(id), + with %User{} = subscription_target <- User.get_cached_by_id(id), {:ok, subscription_target} = User.subscribe(user, subscription_target) do conn |> put_view(AccountView) @@ -878,7 +878,7 @@ def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do end def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_by_id(id), + with %User{} = subscription_target <- User.get_cached_by_id(id), {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do conn |> put_view(AccountView) From b57b43027cf958d3a3a82b95f155ae27b235b543 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 9 Apr 2019 23:20:31 +0300 Subject: [PATCH 35/36] Change response format of /api/pleroma/emoji to the one that actually makes sense --- docs/api/pleroma_api.md | 24 ++++++++++++++++++- .../controllers/util_controller.ex | 3 ++- test/web/twitter_api/util_controller_test.exs | 21 +++++++--------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index 2e8fb04d2..6f2bc61bc 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -10,7 +10,29 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Authentication: not required * Params: none * Response: JSON -* Example response: `[{"kalsarikannit_f":{"tags":["Finmoji"],"image_url":"/finmoji/128px/kalsarikannit_f-128.png"}},{"perkele":{"tags":["Finmoji"],"image_url":"/finmoji/128px/perkele-128.png"}},{"blobdab":{"tags":["SomeTag"],"image_url":"/emoji/blobdab.png"}},"happiness":{"tags":["Finmoji"],"image_url":"/finmoji/128px/happiness-128.png"}}]` +* Example response: +```json +{ + "girlpower": { + "tags": [ + "Finmoji" + ], + "image_url": "/finmoji/128px/girlpower-128.png" + }, + "education": { + "tags": [ + "Finmoji" + ], + "image_url": "/finmoji/128px/education-128.png" + }, + "finnishlove": { + "tags": [ + "Finmoji" + ], + "image_url": "/finmoji/128px/finnishlove-128.png" + } +} +``` * Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format ## `/api/pleroma/follow_import` diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 26407aebd..e6057e072 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -286,8 +286,9 @@ def emoji(conn, _params) do emoji = Emoji.get_all() |> Enum.map(fn {short_code, path, tags} -> - %{short_code => %{image_url: path, tags: String.split(tags, ",")}} + {short_code, %{image_url: path, tags: String.split(tags, ",")}} end) + |> Enum.into(%{}) json(conn, emoji) end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 410f20f87..a1a5e3964 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -172,22 +172,19 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d describe "/api/pleroma/emoji" do test "returns json with custom emoji with tags", %{conn: conn} do - [emoji | _body] = + emoji = conn |> get("/api/pleroma/emoji") |> json_response(200) - [key] = Map.keys(emoji) - - %{ - ^key => %{ - "image_url" => url, - "tags" => tags - } - } = emoji - - assert is_binary(url) - assert is_list(tags) + assert Enum.all?(emoji, fn + {_key, + %{ + "image_url" => url, + "tags" => tags + }} -> + is_binary(url) and is_list(tags) + end) end end From cf9ba8fe1b5a701488889c4bfdde46e7f00b9ebc Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 10 Apr 2019 02:32:04 +0200 Subject: [PATCH 36/36] transmogrifier.ex: Add fix_summary/1 MRF.KeywordPolicy assumes that summary is present in a activity, and this would probably simplify code elsewhere too anyway. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f733ae7e1..9d4cbab0b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -83,6 +83,22 @@ def fix_object(object) do |> fix_content_map |> fix_likes |> fix_addressing + |> fix_summary + end + + def fix_summary(%{"summary" => nil} = object) do + object + |> Map.put("summary", "") + end + + def fix_summary(%{"summary" => _} = object) do + # summary is present, nothing to do + object + end + + def fix_summary(object) do + object + |> Map.put("summary", "") end def fix_addressing_list(map, field) do