From 8ba7a151adf77c5cc47d6e1364a6078cc4bdef98 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Mon, 22 Jul 2019 16:45:54 +0200 Subject: [PATCH 01/16] Cleanup: fix a comment --- test/web/mastodon_api/mastodon_api_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index ce2e44499..b5279412f 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -405,7 +405,7 @@ test "direct timeline", %{conn: conn} do assert %{"visibility" => "direct"} = status assert status["url"] != direct.data["id"] - # User should be able to see his own direct message + # User should be able to see their own direct message res_conn = build_conn() |> assign(:user, user_one) From b72940277470c67802b979e4cab44f277e8fffb3 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Mon, 22 Jul 2019 09:10:30 +0200 Subject: [PATCH 02/16] Make test.exs read config in the same way as dev.exs This way, if your test.secret.exs has an error, you'll actually see it. --- config/test.exs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/test.exs b/config/test.exs index 92dca18bc..3f606aa81 100644 --- a/config/test.exs +++ b/config/test.exs @@ -82,11 +82,10 @@ config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock -try do +if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" -rescue - _ -> - IO.puts( - "You may want to create test.secret.exs to declare custom database connection parameters." - ) +else + IO.puts( + "You may want to create test.secret.exs to declare custom database connection parameters." + ) end From 666514194a325e2463c05bae516b89d7c5f59316 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Mon, 22 Jul 2019 14:16:20 +0200 Subject: [PATCH 03/16] Add activity expirations table Add a table to store activity expirations. An activity can have zero or one expirations. The expiration has a scheduled_at field which stores the time at which the activity should expire and be deleted. --- lib/pleroma/activity.ex | 3 ++ lib/pleroma/activity_expiration.ex | 31 +++++++++++++++++++ .../20190716100804_add_expirations_table.exs | 10 ++++++ test/activity_expiration_test.exs | 21 +++++++++++++ test/activity_test.exs | 9 ++++++ test/support/factory.ex | 19 +++++++++++- 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 lib/pleroma/activity_expiration.ex create mode 100644 priv/repo/migrations/20190716100804_add_expirations_table.exs create mode 100644 test/activity_expiration_test.exs diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 46552c7be..be4850560 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Activity do use Ecto.Schema alias Pleroma.Activity + alias Pleroma.ActivityExpiration alias Pleroma.Bookmark alias Pleroma.Notification alias Pleroma.Object @@ -59,6 +60,8 @@ defmodule Pleroma.Activity do # typical case. has_one(:object, Object, on_delete: :nothing, foreign_key: :id) + has_one(:expiration, ActivityExpiration, on_delete: :delete_all) + timestamps() end diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex new file mode 100644 index 000000000..d3d95f9e9 --- /dev/null +++ b/lib/pleroma/activity_expiration.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ActivityExpiration do + use Ecto.Schema + + alias Pleroma.Activity + alias Pleroma.ActivityExpiration + alias Pleroma.FlakeId + alias Pleroma.Repo + + import Ecto.Query + + @type t :: %__MODULE__{} + + schema "activity_expirations" do + belongs_to(:activity, Activity, type: FlakeId) + field(:scheduled_at, :naive_datetime) + end + + def due_expirations(offset \\ 0) do + naive_datetime = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(offset, :millisecond) + + ActivityExpiration + |> where([exp], exp.scheduled_at < ^naive_datetime) + |> Repo.all() + end +end diff --git a/priv/repo/migrations/20190716100804_add_expirations_table.exs b/priv/repo/migrations/20190716100804_add_expirations_table.exs new file mode 100644 index 000000000..fbde8f9d6 --- /dev/null +++ b/priv/repo/migrations/20190716100804_add_expirations_table.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.AddExpirationsTable do + use Ecto.Migration + + def change do + create_if_not_exists table(:activity_expirations) do + add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all)) + add(:scheduled_at, :naive_datetime, null: false) + end + end +end diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs new file mode 100644 index 000000000..20566a186 --- /dev/null +++ b/test/activity_expiration_test.exs @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ActivityExpirationTest do + use Pleroma.DataCase + alias Pleroma.ActivityExpiration + import Pleroma.Factory + + test "finds activities due to be deleted only" do + activity = insert(:note_activity) + expiration_due = insert(:expiration_in_the_past, %{activity_id: activity.id}) + activity2 = insert(:note_activity) + insert(:expiration_in_the_future, %{activity_id: activity2.id}) + + expirations = ActivityExpiration.due_expirations() + + assert length(expirations) == 1 + assert hd(expirations) == expiration_due + end +end diff --git a/test/activity_test.exs b/test/activity_test.exs index b27f6fd36..785c4b3cf 100644 --- a/test/activity_test.exs +++ b/test/activity_test.exs @@ -164,4 +164,13 @@ test "find all statuses for unauthenticated users when `limit_to_local_content` Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) end end + + test "add an activity with an expiration" do + activity = insert(:note_activity) + insert(:expiration_in_the_future, %{activity_id: activity.id}) + + Pleroma.ActivityExpiration + |> where([a], a.activity_id == ^activity.id) + |> Repo.one!() + end end diff --git a/test/support/factory.ex b/test/support/factory.ex index c751546ce..7b52b1328 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Factory do @@ -142,6 +142,23 @@ def note_activity_factory(attrs \\ %{}) do |> Map.merge(attrs) end + defp expiration_offset_by_minutes(attrs, minutes) do + %Pleroma.ActivityExpiration{} + |> Map.merge(attrs) + |> Map.put( + :scheduled_at, + NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(minutes), :millisecond) + ) + end + + def expiration_in_the_past_factory(attrs \\ %{}) do + expiration_offset_by_minutes(attrs, -60) + end + + def expiration_in_the_future_factory(attrs \\ %{}) do + expiration_offset_by_minutes(attrs, 60) + end + def article_activity_factory do article = insert(:article) From 378f5f0fbe21c2533719fed9afe8313586fda5d5 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Mon, 22 Jul 2019 14:18:58 +0200 Subject: [PATCH 04/16] Add activity expiration worker This is a worker that runs every minute and deletes expired activities. It's based heavily on the scheduled activities worker. --- config/config.exs | 3 ++ docs/config.md | 4 ++ lib/pleroma/activity_expiration_worker.ex | 62 +++++++++++++++++++++++ lib/pleroma/application.ex | 4 ++ test/activity_expiration_worker_test.exs | 17 +++++++ 5 files changed, 90 insertions(+) create mode 100644 lib/pleroma/activity_expiration_worker.ex create mode 100644 test/activity_expiration_worker_test.exs diff --git a/config/config.exs b/config/config.exs index 569411866..2887353fb 100644 --- a/config/config.exs +++ b/config/config.exs @@ -447,6 +447,7 @@ max_retries: 5 config :pleroma_job_queue, :queues, + activity_expiration: 10, federator_incoming: 50, federator_outgoing: 50, web_push: 50, @@ -536,6 +537,8 @@ status_id_action: {60_000, 3}, password_reset: {1_800_000, 5} +config :pleroma, Pleroma.ActivityExpiration, enabled: true + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/docs/config.md b/docs/config.md index 02f86dc16..a20ed704f 100644 --- a/docs/config.md +++ b/docs/config.md @@ -484,6 +484,10 @@ config :auto_linker, * `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`) * `enabled`: whether scheduled activities are sent to the job queue to be executed +## Pleroma.ActivityExpiration + +# `enabled`: whether expired activities will be sent to the job queue to be deleted + ## Pleroma.Web.Auth.Authenticator * `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator diff --git a/lib/pleroma/activity_expiration_worker.ex b/lib/pleroma/activity_expiration_worker.ex new file mode 100644 index 000000000..a341f58df --- /dev/null +++ b/lib/pleroma/activity_expiration_worker.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ActivityExpirationWorker do + alias Pleroma.Activity + alias Pleroma.ActivityExpiration + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + require Logger + use GenServer + import Ecto.Query + + @schedule_interval :timer.minutes(1) + + def start_link do + GenServer.start_link(__MODULE__, nil) + end + + @impl true + def init(_) do + if Config.get([ActivityExpiration, :enabled]) do + schedule_next() + {:ok, nil} + else + :ignore + end + end + + def perform(:execute, expiration_id) do + try do + expiration = + ActivityExpiration + |> where([e], e.id == ^expiration_id) + |> Repo.one!() + + activity = Activity.get_by_id_with_object(expiration.activity_id) + user = User.get_by_ap_id(activity.object.data["actor"]) + CommonAPI.delete(activity.id, user) + rescue + error -> + Logger.error("#{__MODULE__} Couldn't delete expired activity: #{inspect(error)}") + end + end + + @impl true + def handle_info(:perform, state) do + ActivityExpiration.due_expirations(@schedule_interval) + |> Enum.each(fn expiration -> + PleromaJobQueue.enqueue(:activity_expiration, __MODULE__, [:execute, expiration.id]) + end) + + schedule_next() + {:noreply, state} + end + + defp schedule_next do + Process.send_after(self(), :perform, @schedule_interval) + end +end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 035331491..42e4a1dfa 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -115,6 +115,10 @@ def start(_type, _args) do %{ id: Pleroma.ScheduledActivityWorker, start: {Pleroma.ScheduledActivityWorker, :start_link, []} + }, + %{ + id: Pleroma.ActivityExpirationWorker, + start: {Pleroma.ActivityExpirationWorker, :start_link, []} } ] ++ hackney_pool_children() ++ diff --git a/test/activity_expiration_worker_test.exs b/test/activity_expiration_worker_test.exs new file mode 100644 index 000000000..939d912f1 --- /dev/null +++ b/test/activity_expiration_worker_test.exs @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ActivityExpirationWorkerTest do + use Pleroma.DataCase + alias Pleroma.Activity + import Pleroma.Factory + + test "deletes an activity" do + activity = insert(:note_activity) + expiration = insert(:expiration_in_the_past, %{activity_id: activity.id}) + Pleroma.ActivityExpirationWorker.perform(:execute, expiration.id) + + refute Repo.get(Activity, activity.id) + end +end From 704960b3c135d2e050308c68f5ccf5d7b7df40f8 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Mon, 22 Jul 2019 16:46:20 +0200 Subject: [PATCH 05/16] Add support for activity expiration to common and Masto API The "expires_at" parameter accepts an ISO8601-formatted date which defines when the activity will expire. At this point the API will not give you any feedback about if your post will expire or not. --- docs/api/differences_in_mastoapi_responses.md | 1 + lib/pleroma/activity_expiration.ex | 19 ++++++++++++ lib/pleroma/web/common_api/common_api.ex | 29 +++++++++++++------ test/support/factory.ex | 10 ++++--- test/web/common_api/common_api_test.exs | 17 +++++++++++ .../mastodon_api_controller_test.exs | 19 ++++++++++++ 6 files changed, 82 insertions(+), 13 deletions(-) diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 1907d70c8..7d5be4713 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -79,6 +79,7 @@ Additional parameters can be added to the JSON body/Form data: - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. +- `expires_on`: datetime (iso8601), sets when the posted activity should expire. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. ## PATCH `/api/v1/update_credentials` diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex index d3d95f9e9..a0af5255b 100644 --- a/lib/pleroma/activity_expiration.ex +++ b/lib/pleroma/activity_expiration.ex @@ -10,6 +10,7 @@ defmodule Pleroma.ActivityExpiration do alias Pleroma.FlakeId alias Pleroma.Repo + import Ecto.Changeset import Ecto.Query @type t :: %__MODULE__{} @@ -19,6 +20,24 @@ defmodule Pleroma.ActivityExpiration do field(:scheduled_at, :naive_datetime) end + def changeset(%ActivityExpiration{} = expiration, attrs) do + expiration + |> cast(attrs, [:scheduled_at]) + |> validate_required([:scheduled_at]) + end + + def get_by_activity_id(activity_id) do + ActivityExpiration + |> where([exp], exp.activity_id == ^activity_id) + |> Repo.one() + end + + def create(%Activity{} = activity, scheduled_at) do + %ActivityExpiration{activity_id: activity.id} + |> changeset(%{scheduled_at: scheduled_at}) + |> Repo.insert() + end + def due_expirations(offset \\ 0) do naive_datetime = NaiveDateTime.utc_now() diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 44af6a773..0f287af4e 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity + alias Pleroma.ActivityExpiration alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.ThreadMute @@ -218,6 +219,7 @@ def post(user, %{"status" => status} = data) do context <- make_context(in_reply_to), cw <- data["spoiler_text"] || "", sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}), + {:ok, expires_at} <- Ecto.Type.cast(:naive_datetime, data["expires_at"]), full_payload <- String.trim(status <> cw), :ok <- validate_character_limit(full_payload, attachments, limit), object <- @@ -243,15 +245,24 @@ def post(user, %{"status" => status} = data) do preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false direct? = visibility == "direct" - %{ - to: to, - actor: user, - context: context, - object: object, - additional: %{"cc" => cc, "directMessage" => direct?} - } - |> maybe_add_list_data(user, visibility) - |> ActivityPub.create(preview?) + result = + %{ + to: to, + actor: user, + context: context, + object: object, + additional: %{"cc" => cc, "directMessage" => direct?} + } + |> maybe_add_list_data(user, visibility) + |> ActivityPub.create(preview?) + + if expires_at do + with {:ok, activity} <- result do + ActivityExpiration.create(activity, expires_at) + end + end + + result else {:private_to_public, true} -> {:error, dgettext("errors", "The message visibility must be direct")} diff --git a/test/support/factory.ex b/test/support/factory.ex index 7b52b1328..63fe3a66d 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -143,12 +143,14 @@ def note_activity_factory(attrs \\ %{}) do end defp expiration_offset_by_minutes(attrs, minutes) do + scheduled_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(minutes), :millisecond) + |> NaiveDateTime.truncate(:second) + %Pleroma.ActivityExpiration{} |> Map.merge(attrs) - |> Map.put( - :scheduled_at, - NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(minutes), :millisecond) - ) + |> Map.put(:scheduled_at, scheduled_at) end def expiration_in_the_past_factory(attrs \\ %{}) do diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 16b3f121d..210314a4a 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -160,6 +160,23 @@ test "it returns error when character limit is exceeded" do Pleroma.Config.put([:instance, :limit], limit) end + + test "it can handle activities that expire" do + user = insert(:user) + + expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + |> NaiveDateTime.add(1_000_000, :second) + + expires_at_iso8601 = expires_at |> NaiveDateTime.to_iso8601() + + assert {:ok, activity} = + CommonAPI.post(user, %{"status" => "chai", "expires_at" => expires_at_iso8601}) + + assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) + assert expiration.scheduled_at == expires_at + end end describe "reactions" do diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index b5279412f..24482a4a2 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Ecto.Changeset alias Pleroma.Activity + alias Pleroma.ActivityExpiration alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo @@ -151,6 +152,24 @@ test "posting a status", %{conn: conn} do assert %{"id" => third_id} = json_response(conn_three, 200) refute id == third_id + + # An activity that will expire: + expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(120), :millisecond) + |> NaiveDateTime.truncate(:second) + + conn_four = + conn + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_at" => expires_at + }) + + assert %{"id" => fourth_id} = json_response(conn_four, 200) + assert activity = Activity.get_by_id(fourth_id) + assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + assert expiration.scheduled_at == expires_at end test "replying to a status", %{conn: conn} do From 36012ef6c1dfea2489e61063e14783fa3fb52700 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Tue, 23 Jul 2019 16:33:45 +0200 Subject: [PATCH 06/16] Require that ephemeral posts live for at least one hour If we didn't put some kind of lifetime requirement on these, I guess you could annoy people by sending large numbers of ephemeral posts that provoke notifications but then disappear before anyone can read them. --- lib/pleroma/activity_expiration.ex | 18 ++++++++++++++++++ lib/pleroma/web/common_api/common_api.ex | 14 ++++++++++++-- test/activity_expiration_test.exs | 6 ++++++ test/support/factory.ex | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex index a0af5255b..bf57abca4 100644 --- a/lib/pleroma/activity_expiration.ex +++ b/lib/pleroma/activity_expiration.ex @@ -14,6 +14,7 @@ defmodule Pleroma.ActivityExpiration do import Ecto.Query @type t :: %__MODULE__{} + @min_activity_lifetime :timer.hours(1) schema "activity_expirations" do belongs_to(:activity, Activity, type: FlakeId) @@ -24,6 +25,7 @@ def changeset(%ActivityExpiration{} = expiration, attrs) do expiration |> cast(attrs, [:scheduled_at]) |> validate_required([:scheduled_at]) + |> validate_scheduled_at() end def get_by_activity_id(activity_id) do @@ -47,4 +49,20 @@ def due_expirations(offset \\ 0) do |> where([exp], exp.scheduled_at < ^naive_datetime) |> Repo.all() end + + def validate_scheduled_at(changeset) do + validate_change(changeset, :scheduled_at, fn _, scheduled_at -> + if not expires_late_enough?(scheduled_at) do + [scheduled_at: "an ephemeral activity must live for at least one hour"] + else + [] + end + end) + end + + def expires_late_enough?(scheduled_at) do + now = NaiveDateTime.utc_now() + diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) + diff >= @min_activity_lifetime + end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 0f287af4e..261d60392 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -196,6 +196,16 @@ def get_replied_to_visibility(activity) do end end + defp check_expiry_date(expiry_str) do + {:ok, expiry} = Ecto.Type.cast(:naive_datetime, expiry_str) + + if is_nil(expiry) || ActivityExpiration.expires_late_enough?(expiry) do + {:ok, expiry} + else + {:error, "Expiry date is too soon"} + end + end + def post(user, %{"status" => status} = data) do limit = Pleroma.Config.get([:instance, :limit]) @@ -219,7 +229,7 @@ def post(user, %{"status" => status} = data) do context <- make_context(in_reply_to), cw <- data["spoiler_text"] || "", sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}), - {:ok, expires_at} <- Ecto.Type.cast(:naive_datetime, data["expires_at"]), + {:ok, expires_at} <- check_expiry_date(data["expires_at"]), full_payload <- String.trim(status <> cw), :ok <- validate_character_limit(full_payload, attachments, limit), object <- @@ -258,7 +268,7 @@ def post(user, %{"status" => status} = data) do if expires_at do with {:ok, activity} <- result do - ActivityExpiration.create(activity, expires_at) + {:ok, _} = ActivityExpiration.create(activity, expires_at) end end diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs index 20566a186..4948fae16 100644 --- a/test/activity_expiration_test.exs +++ b/test/activity_expiration_test.exs @@ -18,4 +18,10 @@ test "finds activities due to be deleted only" do assert length(expirations) == 1 assert hd(expirations) == expiration_due end + + test "denies expirations that don't live long enough" do + activity = insert(:note_activity) + now = NaiveDateTime.utc_now() + assert {:error, _} = ActivityExpiration.create(activity, now) + end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 63fe3a66d..7a2ddcada 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -158,7 +158,7 @@ def expiration_in_the_past_factory(attrs \\ %{}) do end def expiration_in_the_future_factory(attrs \\ %{}) do - expiration_offset_by_minutes(attrs, 60) + expiration_offset_by_minutes(attrs, 61) end def article_activity_factory do From 3cb471ec0688b81c8ef37dd27f2b82e6c858431f Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 12:43:20 +0200 Subject: [PATCH 07/16] Expose expires_at datetime in mastoAPI only for the activity actor In the "pleroma" section of the MastoAPI for status activities you can see an expires_at item that states when the activity will expire, or nothing if the activity will not expire. The expires_at date is only visible to the person who posted the activity. This is the conservative approach in case some attacker decides to write a logger for expiring posts. However, in the future of OCAP, signed requests, and all that stuff, this attack might not be that likely. Some other pleroma dev should remove the restriction in the code at that time, if they're satisfied with the security implications of doing so. --- docs/api/differences_in_mastoapi_responses.md | 1 + lib/pleroma/web/mastodon_api/views/status_view.ex | 13 ++++++++++++- .../mastodon_api/mastodon_api_controller_test.exs | 3 ++- test/web/mastodon_api/status_view_test.exs | 3 ++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 7d5be4713..168a13f4e 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -25,6 +25,7 @@ Has these additional fields under the `pleroma` object: - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` +- `expires_on`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire ## Attachments diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index de9425959..7264dcafb 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do use Pleroma.Web, :view alias Pleroma.Activity + alias Pleroma.ActivityExpiration alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -165,6 +166,15 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil + client_posted_this_activity = opts[:for] && user.id == opts[:for].id + + expires_at = + with true <- client_posted_this_activity, + expiration when not is_nil(expiration) <- + ActivityExpiration.get_by_activity_id(activity.id) do + expiration.scheduled_at + end + thread_muted? = case activity.thread_muted? do thread_muted? when is_boolean(thread_muted?) -> thread_muted? @@ -262,7 +272,8 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity conversation_id: get_context_id(activity), in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, - spoiler_text: %{"text/plain" => summary_plaintext} + spoiler_text: %{"text/plain" => summary_plaintext}, + expires_at: expires_at } } end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 24482a4a2..e59908979 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -166,10 +166,11 @@ test "posting a status", %{conn: conn} do "expires_at" => expires_at }) - assert %{"id" => fourth_id} = json_response(conn_four, 200) + assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) assert activity = Activity.get_by_id(fourth_id) assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) assert expiration.scheduled_at == expires_at + assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expires_at) end test "replying to a status", %{conn: conn} do diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index 3447c5b1f..073c69659 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -133,7 +133,8 @@ test "a note activity" do conversation_id: convo_id, in_reply_to_account_acct: nil, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, - spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])} + spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, + expires_at: nil } } From 91d9fdc7decc664483625c11e44d4e053dd9c585 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 13:02:28 +0200 Subject: [PATCH 08/16] Update changelog to document expiring posts feature --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a5a6c21..75d236af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Expiring/ephemeral activites. All activities can have expires_on value set, which controls when they should be deleted automatically. +- Mastodon API: in post_status, expires_at datetime parameter lets you set when an activity should expire +- Mastodon API: all status JSON responses contain a `pleroma.expires_in` item which states the number of minutes until an activity expires. The value is only shown to the user who created the activity. To everyone else it's empty. +- Configuration: `ActivityExpiration.enabled` controls whether expired activites will get deleted at the appropriate time. Enabled by default. + ### Changed - **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config - Configuration: OpenGraph and TwitterCard providers enabled by default From 2981821db834448bf9b2ba26590314e36201664c Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 16:51:09 +0200 Subject: [PATCH 09/16] squash! Expose expires_at datetime in mastoAPI only for the activity actor NOTE: rewrite the commit msg --- docs/api/differences_in_mastoapi_responses.md | 2 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 10 +++++++--- test/web/mastodon_api/mastodon_api_controller_test.exs | 2 +- test/web/mastodon_api/status_view_test.exs | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 168a13f4e..829468b13 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -25,7 +25,7 @@ Has these additional fields under the `pleroma` object: - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` -- `expires_on`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire +- `expires_in`: the number of minutes until a post will expire (be deleted automatically), or empty if the post won't expire ## Attachments diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 7264dcafb..4a3686d72 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -168,11 +168,15 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity client_posted_this_activity = opts[:for] && user.id == opts[:for].id - expires_at = + expires_in = with true <- client_posted_this_activity, expiration when not is_nil(expiration) <- ActivityExpiration.get_by_activity_id(activity.id) do - expiration.scheduled_at + expires_in_seconds = + expiration.scheduled_at + |> NaiveDateTime.diff(NaiveDateTime.utc_now(), :second) + + round(expires_in_seconds / 60) end thread_muted? = @@ -273,7 +277,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext}, - expires_at: expires_at + expires_in: expires_in } } end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index e59908979..a9d38c06e 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -170,7 +170,7 @@ test "posting a status", %{conn: conn} do assert activity = Activity.get_by_id(fourth_id) assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) assert expiration.scheduled_at == expires_at - assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expires_at) + assert fourth_response["pleroma"]["expires_in"] > 0 end test "replying to a status", %{conn: conn} do diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index 073c69659..eb0874ab2 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -134,7 +134,7 @@ test "a note activity" do in_reply_to_account_acct: nil, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, - expires_at: nil + expires_in: nil } } From 877575d0da830724e822eac2de243391aaea7ec8 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 17:07:51 +0200 Subject: [PATCH 10/16] fixup! Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d236af5..f64506637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - Expiring/ephemeral activites. All activities can have expires_on value set, which controls when they should be deleted automatically. -- Mastodon API: in post_status, expires_at datetime parameter lets you set when an activity should expire -- Mastodon API: all status JSON responses contain a `pleroma.expires_in` item which states the number of minutes until an activity expires. The value is only shown to the user who created the activity. To everyone else it's empty. +- Mastodon API: in post_status, the expires_in parameter lets you set the number of minutes until an activity expires. It must be at least one hour. +- Mastodon API: all status JSON responses contain a `pleroma.expires_on` item which states when an activity will expire. The value is only shown to the user who created the activity. To everyone else it's empty. - Configuration: `ActivityExpiration.enabled` controls whether expired activites will get deleted at the appropriate time. Enabled by default. ### Changed From 2c83eb0b157b2f574f55341e9171f0b5ab7bd3b2 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 17:09:59 +0200 Subject: [PATCH 11/16] Revert "squash! Expose expires_at datetime in mastoAPI only for the activity actor" This reverts commit 2981821db834448bf9b2ba26590314e36201664c. --- docs/api/differences_in_mastoapi_responses.md | 2 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 10 +++------- test/web/mastodon_api/mastodon_api_controller_test.exs | 2 +- test/web/mastodon_api/status_view_test.exs | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 829468b13..168a13f4e 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -25,7 +25,7 @@ Has these additional fields under the `pleroma` object: - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` -- `expires_in`: the number of minutes until a post will expire (be deleted automatically), or empty if the post won't expire +- `expires_on`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire ## Attachments diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 4a3686d72..7264dcafb 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -168,15 +168,11 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity client_posted_this_activity = opts[:for] && user.id == opts[:for].id - expires_in = + expires_at = with true <- client_posted_this_activity, expiration when not is_nil(expiration) <- ActivityExpiration.get_by_activity_id(activity.id) do - expires_in_seconds = - expiration.scheduled_at - |> NaiveDateTime.diff(NaiveDateTime.utc_now(), :second) - - round(expires_in_seconds / 60) + expiration.scheduled_at end thread_muted? = @@ -277,7 +273,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext}, - expires_in: expires_in + expires_at: expires_at } } end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index a9d38c06e..e59908979 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -170,7 +170,7 @@ test "posting a status", %{conn: conn} do assert activity = Activity.get_by_id(fourth_id) assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) assert expiration.scheduled_at == expires_at - assert fourth_response["pleroma"]["expires_in"] > 0 + assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expires_at) end test "replying to a status", %{conn: conn} do diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index eb0874ab2..073c69659 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -134,7 +134,7 @@ test "a note activity" do in_reply_to_account_acct: nil, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, - expires_in: nil + expires_at: nil } } From 0e2b5a3e6aed7947909c2a1ff1618403546f1572 Mon Sep 17 00:00:00 2001 From: Mike Verdone Date: Wed, 24 Jul 2019 17:25:11 +0200 Subject: [PATCH 12/16] WIP --- .../mastodon_api_controller_test.exs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index e59908979..fbe0ab375 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -154,23 +154,27 @@ test "posting a status", %{conn: conn} do refute id == third_id # An activity that will expire: - expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(120), :millisecond) - |> NaiveDateTime.truncate(:second) + expires_in = 120 conn_four = conn |> post("api/v1/statuses", %{ "status" => "oolong", - "expires_at" => expires_at + "expires_in" => expires_in }) assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) assert activity = Activity.get_by_id(fourth_id) assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) - assert expiration.scheduled_at == expires_at - assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expires_at) + + estimated_expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(expires_in), :millisecond) + |> NaiveDateTime.truncate(:second) + + # This assert will fail if the test takes longer than a minute. I sure hope it never does: + assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 + assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expiration.scheduled_at) end test "replying to a status", %{conn: conn} do From 1692fa89458f0f83f69ffa2f85a998869b8fe454 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 24 Aug 2019 17:22:26 +0200 Subject: [PATCH 13/16] ActivityExpirationWorker: Fix merge issues. --- lib/pleroma/activity_expiration_worker.ex | 2 +- lib/pleroma/application.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/activity_expiration_worker.ex b/lib/pleroma/activity_expiration_worker.ex index a341f58df..0f9e715f8 100644 --- a/lib/pleroma/activity_expiration_worker.ex +++ b/lib/pleroma/activity_expiration_worker.ex @@ -15,7 +15,7 @@ defmodule Pleroma.ActivityExpirationWorker do @schedule_interval :timer.minutes(1) - def start_link do + def start_link(_) do GenServer.start_link(__MODULE__, nil) end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 1e4de272c..483ac1f39 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -36,7 +36,7 @@ def start(_type, _args) do Pleroma.Captcha, Pleroma.FlakeId, Pleroma.ScheduledActivityWorker, - Pleroma.ActiviyExpirationWorker + Pleroma.ActivityExpirationWorker ] ++ cachex_children() ++ hackney_pool_children() ++ From efb8818e9ee280b53eac17699e8114e8af82b03b Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 24 Aug 2019 17:22:48 +0200 Subject: [PATCH 14/16] Activity Expiration: Switch to 'expires_in' system. --- lib/pleroma/web/common_api/common_api.ex | 15 +++++++++++---- test/web/common_api/common_api_test.exs | 4 +--- .../mastodon_api/mastodon_api_controller_test.exs | 9 ++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 69120cc19..5faddc9f4 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -201,16 +201,23 @@ def get_replied_to_visibility(activity) do end end - defp check_expiry_date(expiry_str) do - {:ok, expiry} = Ecto.Type.cast(:naive_datetime, expiry_str) + defp check_expiry_date({:ok, nil} = res), do: res - if is_nil(expiry) || ActivityExpiration.expires_late_enough?(expiry) do + defp check_expiry_date({:ok, in_seconds}) do + expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds) + + if ActivityExpiration.expires_late_enough?(expiry) do {:ok, expiry} else {:error, "Expiry date is too soon"} end end + defp check_expiry_date(expiry_str) do + Ecto.Type.cast(:integer, expiry_str) + |> check_expiry_date() + end + def post(user, %{"status" => status} = data) do limit = Pleroma.Config.get([:instance, :limit]) @@ -237,7 +244,7 @@ def post(user, %{"status" => status} = data) do context <- make_context(in_reply_to, in_reply_to_conversation), cw <- data["spoiler_text"] || "", sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}), - {:ok, expires_at} <- check_expiry_date(data["expires_at"]), + {:ok, expires_at} <- check_expiry_date(data["expires_in"]), full_payload <- String.trim(status <> cw), :ok <- validate_character_limit(full_payload, attachments, limit), object <- diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 5fda91438..f28a66090 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -213,10 +213,8 @@ test "it can handle activities that expire" do |> NaiveDateTime.truncate(:second) |> NaiveDateTime.add(1_000_000, :second) - expires_at_iso8601 = expires_at |> NaiveDateTime.to_iso8601() - assert {:ok, activity} = - CommonAPI.post(user, %{"status" => "chai", "expires_at" => expires_at_iso8601}) + CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000}) assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) assert expiration.scheduled_at == expires_at diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index c05c39db6..6fcdc19aa 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -153,7 +153,8 @@ test "posting a status", %{conn: conn} do refute id == third_id # An activity that will expire: - expires_in = 120 + # 2 hours + expires_in = 120 * 60 conn_four = conn @@ -168,12 +169,14 @@ test "posting a status", %{conn: conn} do estimated_expires_at = NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(expires_in), :millisecond) + |> NaiveDateTime.add(expires_in) |> NaiveDateTime.truncate(:second) # This assert will fail if the test takes longer than a minute. I sure hope it never does: assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 - assert fourth_response["pleroma"]["expires_at"] == NaiveDateTime.to_iso8601(expiration.scheduled_at) + + assert fourth_response["pleroma"]["expires_at"] == + NaiveDateTime.to_iso8601(expiration.scheduled_at) end test "replying to a status", %{conn: conn} do From 24994f3e0c643abe4d74bec3edec53fa89f4ed72 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 24 Aug 2019 17:28:19 +0200 Subject: [PATCH 15/16] Activity expiration: Fix docs. --- docs/api/differences_in_mastoapi_responses.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md index 197c465d8..f34e3dd72 100644 --- a/docs/api/differences_in_mastoapi_responses.md +++ b/docs/api/differences_in_mastoapi_responses.md @@ -25,7 +25,7 @@ Has these additional fields under the `pleroma` object: - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` -- `expires_on`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire +- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire ## Attachments @@ -87,7 +87,7 @@ Additional parameters can be added to the JSON body/Form data: - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. -- `expires_on`: datetime (iso8601), sets when the posted activity should expire. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. +- `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour. - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`. ## PATCH `/api/v1/update_credentials` From 1d7033d96289edf0adf2ca61a725f93b345305ec Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 24 Aug 2019 15:33:17 +0000 Subject: [PATCH 16/16] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 949577842..b1ec21818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,9 +50,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - ActivityPub: Deactivated user deletion ### Added -- Expiring/ephemeral activites. All activities can have expires_on value set, which controls when they should be deleted automatically. -- Mastodon API: in post_status, the expires_in parameter lets you set the number of minutes until an activity expires. It must be at least one hour. -- Mastodon API: all status JSON responses contain a `pleroma.expires_on` item which states when an activity will expire. The value is only shown to the user who created the activity. To everyone else it's empty. +- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically. +- Mastodon API: in post_status, the expires_in parameter lets you set the number of seconds until an activity expires. It must be at least one hour. +- Mastodon API: all status JSON responses contain a `pleroma.expires_at` item which states when an activity will expire. The value is only shown to the user who created the activity. To everyone else it's empty. - Configuration: `ActivityExpiration.enabled` controls whether expired activites will get deleted at the appropriate time. Enabled by default. - Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data. - **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.