From f959bf7aa6b878ee5b669c4caabd5cdc4cc2dc9e Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 17 May 2019 18:21:11 +0200 Subject: [PATCH 01/48] MongooseIM: Add basic integration endpoints. --- .../web/mongooseim/mongoose_im_controller.ex | 41 +++++++++++++ lib/pleroma/web/router.ex | 5 ++ .../mongoose_im_controller_test.exs | 59 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 lib/pleroma/web/mongooseim/mongoose_im_controller.ex create mode 100644 test/web/mongooseim/mongoose_im_controller_test.exs diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex new file mode 100644 index 000000000..f8c634653 --- /dev/null +++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MongooseIM.MongooseIMController do + use Pleroma.Web, :controller + alias Comeonin.Pbkdf2 + alias Pleroma.User + alias Pleroma.Repo + + def user_exists(conn, %{"user" => username}) do + with %User{} <- Repo.get_by(User, nickname: username, local: true) do + conn + |> json(true) + else + _ -> + conn + |> put_status(:not_found) + |> json(false) + end + end + + def check_password(conn, %{"user" => username, "pass" => password}) do + with %User{password_hash: password_hash} <- + Repo.get_by(User, nickname: username, local: true), + true <- Pbkdf2.checkpw(password, password_hash) do + conn + |> json(true) + else + false -> + conn + |> put_status(403) + |> json(false) + + _ -> + conn + |> put_status(:not_found) + |> json(false) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6a4e4a1d4..552778c92 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -704,6 +704,11 @@ defmodule Pleroma.Web.Router do end end + scope "/", Pleroma.Web.MongooseIM do + get("/user_exists", MongooseIMController, :user_exists) + get("/check_password", MongooseIMController, :check_password) + end + scope "/", Fallback do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs new file mode 100644 index 000000000..eb83999bb --- /dev/null +++ b/test/web/mongooseim/mongoose_im_controller_test.exs @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MongooseIMController do + use Pleroma.Web.ConnCase + import Pleroma.Factory + + test "/user_exists", %{conn: conn} do + _user = insert(:user, nickname: "lain") + _remote_user = insert(:user, nickname: "alice", local: false) + + res = + conn + |> get(mongoose_im_path(conn, :user_exists), user: "lain") + |> json_response(200) + + assert res == true + + res = + conn + |> get(mongoose_im_path(conn, :user_exists), user: "alice") + |> json_response(404) + + assert res == false + + res = + conn + |> get(mongoose_im_path(conn, :user_exists), user: "bob") + |> json_response(404) + + assert res == false + end + + test "/check_password", %{conn: conn} do + user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool")) + + res = + conn + |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool") + |> json_response(200) + + assert res == true + + res = + conn + |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool") + |> json_response(403) + + assert res == false + + res = + conn + |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool") + |> json_response(404) + + assert res == false + end +end From 075eecec907b0a623a90eed44a0378a6812d8037 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 17 May 2019 18:32:30 +0200 Subject: [PATCH 02/48] Linting. --- lib/pleroma/web/mongooseim/mongoose_im_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex index f8c634653..489d5d3a5 100644 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do use Pleroma.Web, :controller alias Comeonin.Pbkdf2 - alias Pleroma.User alias Pleroma.Repo + alias Pleroma.User def user_exists(conn, %{"user" => username}) do with %User{} <- Repo.get_by(User, nickname: username, local: true) do From eb02edcad9cb0d65fc216408960aec63713e5d2b Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Tue, 21 May 2019 00:35:46 +0800 Subject: [PATCH 03/48] Add virtual :thread_muted? field that may be set when fetching activities --- lib/pleroma/activity.ex | 12 +++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 8 ++++++ .../web/mastodon_api/views/status_view.ex | 8 +++++- .../web/twitter_api/views/activity_view.ex | 8 +++++- test/activity_test.exs | 26 +++++++++++++++++++ test/user_test.exs | 2 +- 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 4e54b15ba..99589590c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Activity do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.ThreadMute alias Pleroma.User import Ecto.Changeset @@ -37,6 +38,7 @@ defmodule Pleroma.Activity do field(:local, :boolean, default: true) field(:actor, :string) field(:recipients, {:array, :string}, default: []) + field(:thread_muted?, :boolean, virtual: true) # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark has_one(:bookmark, Bookmark) has_many(:notifications, Notification, on_delete: :delete_all) @@ -90,6 +92,16 @@ def with_preloaded_bookmark(query, %User{} = user) do def with_preloaded_bookmark(query, _), do: query + def with_set_thread_muted_field(query, %User{} = user) do + from([a] in query, + left_join: tm in ThreadMute, + on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data), + select: %Activity{a | thread_muted?: not is_nil(tm.id)} + ) + end + + def with_set_thread_muted_field(query, _), do: query + def get_by_ap_id(ap_id) do Repo.one( from( diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5c3156978..3d9679ec0 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -834,6 +834,13 @@ defp maybe_preload_bookmarks(query, opts) do |> Activity.with_preloaded_bookmark(opts["user"]) end + defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query + + defp maybe_set_thread_muted_field(query, opts) do + query + |> Activity.with_set_thread_muted_field(opts["user"]) + end + defp maybe_order(query, %{order: :desc}) do query |> order_by(desc: :id) @@ -852,6 +859,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do base_query |> maybe_preload_objects(opts) |> maybe_preload_bookmarks(opts) + |> maybe_set_thread_muted_field(opts) |> maybe_order(opts) |> restrict_recipients(recipients, opts["user"]) |> restrict_tag(opts) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index c93d915e5..e55f9b96e 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -157,6 +157,12 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil + thread_muted? = + case activity.thread_muted? do + thread_muted? when is_boolean(thread_muted?) -> thread_muted? + nil -> CommonAPI.thread_muted?(user, activity) + end + attachment_data = object.data["attachment"] || [] attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) @@ -228,7 +234,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity reblogged: reblogged?(activity, opts[:for]), favourited: present?(favorited), bookmarked: present?(bookmarked), - muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), + muted: thread_muted? || User.mutes?(opts[:for], user), pinned: pinned?(activity, user), sensitive: sensitive, spoiler_text: summary_html, diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 44bcafe0e..e84af84dc 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -284,6 +284,12 @@ def render( Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) ) + thread_muted? = + case activity.thread_muted? do + thread_muted? when is_boolean(thread_muted?) -> thread_muted? + nil -> CommonAPI.thread_muted?(user, activity) + end + %{ "id" => activity.id, "uri" => object.data["id"], @@ -314,7 +320,7 @@ def render( "summary" => summary, "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), "card" => card, - "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user) + "muted" => thread_muted? || User.mutes?(opts[:for], user) } end diff --git a/test/activity_test.exs b/test/activity_test.exs index 7e91d534b..15c95502a 100644 --- a/test/activity_test.exs +++ b/test/activity_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.ActivityTest do use Pleroma.DataCase alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.ThreadMute import Pleroma.Factory test "returns an activity by it's AP id" do @@ -47,6 +48,31 @@ test "preloading a bookmark" do assert queried_activity.bookmark == bookmark3 end + test "setting thread_muted?" do + activity = insert(:note_activity) + user = insert(:user) + annoyed_user = insert(:user) + {:ok, _} = ThreadMute.add_mute(annoyed_user.id, activity.data["context"]) + + activity_with_unset_thread_muted_field = + Ecto.Query.from(Activity) + |> Repo.one() + + activity_for_user = + Ecto.Query.from(Activity) + |> Activity.with_set_thread_muted_field(user) + |> Repo.one() + + activity_for_annoyed_user = + Ecto.Query.from(Activity) + |> Activity.with_set_thread_muted_field(annoyed_user) + |> Repo.one() + + assert activity_with_unset_thread_muted_field.thread_muted? == nil + assert activity_for_user.thread_muted? == false + assert activity_for_annoyed_user.thread_muted? == true + end + describe "getting a bookmark" do test "when association is loaded" do user = insert(:user) diff --git a/test/user_test.exs b/test/user_test.exs index 10e463ff8..cb6afbe07 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -902,7 +902,7 @@ test "hide a user's statuses from timelines and notifications" do assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark) - assert [activity] == + assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] == ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) {:ok, _user} = User.deactivate(user) From 75c7bb9289066bfaebe7d96aaa474d34ec4d5a5f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 20 May 2019 17:18:59 -0500 Subject: [PATCH 04/48] Additional reserved usernames --- config/config.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.exs b/config/config.exs index 61e2648a9..387c1a5a7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -369,6 +369,7 @@ "activities", "api", "auth", + "check_password", "dev", "friend-requests", "inbox", @@ -389,6 +390,7 @@ "status", "tag", "user-search", + "user_exists", "users", "web" ] From f96e9b28bb5ee241a3f0ca6a622b925ca560c141 Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Tue, 21 May 2019 07:30:18 +0800 Subject: [PATCH 05/48] Fix prometheus-ecto error when not configured --- lib/pleroma/application.ex | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index eeb415084..dab45a0b2 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -131,19 +131,22 @@ def start(_type, _args) do defp setup_instrumenters do require Prometheus.Registry - :ok = - :telemetry.attach( - "prometheus-ecto", - [:pleroma, :repo, :query], - &Pleroma.Repo.Instrumenter.handle_event/4, - %{} - ) + if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do + :ok = + :telemetry.attach( + "prometheus-ecto", + [:pleroma, :repo, :query], + &Pleroma.Repo.Instrumenter.handle_event/4, + %{} + ) + + Pleroma.Repo.Instrumenter.setup() + end Prometheus.Registry.register_collector(:prometheus_process_collector) Pleroma.Web.Endpoint.MetricsExporter.setup() Pleroma.Web.Endpoint.PipelineInstrumenter.setup() Pleroma.Web.Endpoint.Instrumenter.setup() - Pleroma.Repo.Instrumenter.setup() end def enabled_hackney_pools do From c972d0bb14ee5a65f053b8c9629d93fc9b94ca78 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 21 May 2019 04:58:26 +0000 Subject: [PATCH 06/48] http: bump connection timeout to 10 seconds --- CHANGELOG.md | 1 + lib/pleroma/http/connection.ex | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07e6bce31..256df91b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Don't ship finmoji by default, they can be installed as an emoji pack - Hide deactivated users and their statuses - Posts which are marked sensitive or tagged nsfw no longer have link previews. +- HTTP connection timeout is now set to 10 seconds. ### Fixed - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended. diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index c0173465a..558005c19 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -8,7 +8,7 @@ defmodule Pleroma.HTTP.Connection do """ @hackney_options [ - connect_timeout: 2_000, + connect_timeout: 10_000, recv_timeout: 20_000, follow_redirect: true, pool: :federation From d378b342ba0d7f54be3b47f031c602a578191dfd Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 21 May 2019 18:57:36 +0200 Subject: [PATCH 07/48] MongooseIM: Add documentation. --- CHANGELOG.md | 1 + docs/config/howto_mongooseim.md | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 docs/config/howto_mongooseim.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c439135..b50ca895c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added +- [MongooseIM](https://github.com/esl/MongooseIM) http authentication support. - LDAP authentication - External OAuth provider authentication - A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc. diff --git a/docs/config/howto_mongooseim.md b/docs/config/howto_mongooseim.md new file mode 100644 index 000000000..a33e590a1 --- /dev/null +++ b/docs/config/howto_mongooseim.md @@ -0,0 +1,10 @@ +# Configuring MongooseIM (XMPP Server) to use Pleroma for authentication + +If you want to give your Pleroma users an XMPP (chat) account, you can configure [MongooseIM](https://github.com/esl/MongooseIM) to use your Pleroma server for user authentication, automatically giving every local user an XMPP account. + +In general, you just have to follow the configuration described at [https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/](https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/) and do these changes to your mongooseim.cfg. + +1. Set the auth_method to `{auth_method, http}`. +2. Add the http auth pool like this: `{http, global, auth, [{workers, 50}], [{server, "https://yourpleromainstance.com"}]}` + +Restart your MongooseIM server, your users should now be able to connect with their Pleroma credentials. From c2b0b82e6a6d40f945feacc001ad984a17e23336 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 21 May 2019 00:41:40 +0000 Subject: [PATCH 08/48] object: add Object.prune() --- lib/pleroma/object.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 740d687a3..cc6fc9c5d 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -130,6 +130,13 @@ def delete(%Object{data: %{"id" => id}} = object) do end end + def prune(%Object{data: %{"id" => id}} = object) do + with {:ok, object} <- Repo.delete(object), + {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do + {:ok, object} + end + end + def set_cache(%Object{data: %{"id" => ap_id}} = object) do Cachex.put(:object_cache, "object:#{ap_id}", object) {:ok, object} From 73df9d690d5c1a9c11f0f04b8d877c0677022591 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 21 May 2019 00:41:58 +0000 Subject: [PATCH 09/48] object: fetcher: add support for reinjecting pruned objects --- lib/pleroma/object/fetcher.ex | 22 ++++++++++++++++++++-- test/object/fetcher_test.exs | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 8d4bcc95e..bb9388d4f 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -8,6 +8,19 @@ defmodule Pleroma.Object.Fetcher do @httpoison Application.get_env(:pleroma, :httpoison) + defp reinject_object(data) do + Logger.debug("Reinjecting object #{data["id"]}") + + with data <- Transmogrifier.fix_object(data), + {:ok, object} <- Object.create(data) do + {:ok, object} + else + e -> + Logger.error("Error while processing object: #{inspect(e)}") + {:error, e} + end + end + # TODO: # This will create a Create activity, which we need internally at the moment. def fetch_object_from_id(id) do @@ -26,12 +39,17 @@ def fetch_object_from_id(id) do "object" => data }, :ok <- Containment.contain_origin(id, params), - {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, Object.normalize(activity, false)} + {:ok, activity} <- Transmogrifier.handle_incoming(params), + {:object, _data, %Object{} = object} <- + {:object, data, Object.normalize(activity, false)} do + {:ok, object} else {:error, {:reject, nil}} -> {:reject, nil} + {:object, data, nil} -> + reinject_object(data) + object = %Object{} -> {:ok, object} diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs index 72f616782..d604fd5f5 100644 --- a/test/object/fetcher_test.exs +++ b/test/object/fetcher_test.exs @@ -87,4 +87,23 @@ test "all objects with fake directions are rejected by the object fetcher" do ) end end + + describe "pruning" do + test "it can refetch pruned objects" do + object_id = "http://mastodon.example.org/@admin/99541947525187367" + + {:ok, object} = Fetcher.fetch_object_from_id(object_id) + + assert object + + {:ok, _object} = Object.prune(object) + + refute Object.get_by_ap_id(object_id) + + {:ok, %Object{} = object_two} = Fetcher.fetch_object_from_id(object_id) + + assert object.data["id"] == object_two.data["id"] + assert object.id != object_two.id + end + end end From 16b260fb19cca02463766c2e36a41bfcc823af9b Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 21 May 2019 01:21:28 +0000 Subject: [PATCH 10/48] add mix task to prune the object database using a configured retention period --- config/config.exs | 3 ++- docs/config.md | 1 + lib/mix/tasks/pleroma/database.ex | 40 +++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 72908266d..466a6e9b7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -239,7 +239,8 @@ welcome_message: nil, max_report_comment_size: 1000, safe_dm_mentions: false, - healthcheck: false + healthcheck: false, + remote_post_retention_days: 90 config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800 diff --git a/docs/config.md b/docs/config.md index 197326bbd..a050068f4 100644 --- a/docs/config.md +++ b/docs/config.md @@ -104,6 +104,7 @@ config :pleroma, Pleroma.Emails.Mailer, * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`) * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`) * `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``. +* `remote_post_retention_days`: the default amount of days to retain remote posts when pruning the database ## :app_account_creation REST API for creating an account settings diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index f650b447d..fdb216037 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -23,6 +23,10 @@ defmodule Mix.Tasks.Pleroma.Database do Options: - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references + ## Prune old objects from the database + + mix pleroma.database prune_objects + ## Create a conversation for all existing DMs. Can be safely re-run. mix pleroma.database bump_all_conversations @@ -72,4 +76,40 @@ def run(["update_users_following_followers_counts"]) do Enum.each(users, &User.remove_duplicated_following/1) Enum.each(users, &User.update_follower_count/1) end + + def run(["prune_objects" | args]) do + {options, [], []} = + OptionParser.parse( + args, + strict: [ + vacuum: :boolean + ] + ) + + Common.start_pleroma() + + deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + + Logger.info("Pruning objects older than #{deadline} days") + + time_deadline = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(-(deadline * 86_400)) + + Repo.query!( + "DELETE FROM objects WHERE inserted_at < $1 AND split_part(data->>'actor', '/', 3) != $2", + [time_deadline, Pleroma.Web.Endpoint.host()], + timeout: :infinity + ) + + if Keyword.get(options, :vacuum) do + Logger.info("Runnning VACUUM FULL") + + Repo.query!( + "vacuum full;", + [], + timeout: :infinity + ) + end + end end From f446f94290a6cdae531859c2efa55f55dbaeb7e8 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Tue, 21 May 2019 01:22:27 +0000 Subject: [PATCH 11/48] add changelog entry for object pruning --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256df91b7..a21c4bff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Metadata: RelMe provider - OAuth: added support for refresh tokens - Emoji packs and emoji pack manager +- Object pruning (`mix pleroma.database prune_objects`) ### Changed - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer From a13d449b241b6e5fa14fb741694e63cd21569b2c Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Tue, 21 May 2019 09:39:32 +0800 Subject: [PATCH 12/48] Add tests for fallback routes --- test/web/fallback_test.exs | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/web/fallback_test.exs diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs new file mode 100644 index 000000000..514923a20 --- /dev/null +++ b/test/web/fallback_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FallbackTest do + use Pleroma.Web.ConnCase + import Pleroma.Factory + + test "GET /registration/:token", %{conn: conn} do + assert conn + |> get("/registration/foo") + |> html_response(200) =~ "" + end + + test "GET /:maybe_nickname_or_id", %{conn: conn} do + user = insert(:user) + + assert conn + |> get("/foo") + |> html_response(200) =~ "" + + refute conn + |> get("/" <> user.nickname) + |> html_response(200) =~ "" + end + + test "GET /*path", %{conn: conn} do + assert conn + |> get("/foo") + |> html_response(200) =~ "" + + assert conn + |> get("/foo/bar") + |> html_response(200) =~ "" + end + + test "OPTIONS /*path", %{conn: conn} do + assert conn + |> options("/foo") + |> response(204) == "" + + assert conn + |> options("/foo/bar") + |> response(204) == "" + end +end From 3ab9255eda21a5f8a25047375af9608e0c0c7592 Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Tue, 21 May 2019 09:40:29 +0800 Subject: [PATCH 13/48] Respond with a 404 Not implemented JSON error message when requested API is not implemented --- CHANGELOG.md | 1 + lib/pleroma/web/router.ex | 7 +++++++ test/web/fallback_test.exs | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256df91b7..2ed380102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Hide deactivated users and their statuses - Posts which are marked sensitive or tagged nsfw no longer have link previews. - HTTP connection timeout is now set to 10 seconds. +- Respond with a 404 Not implemented JSON error message when requested API is not implemented ### Fixed - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended. diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4c29b24eb..49e28cc2d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -710,6 +710,7 @@ defmodule Pleroma.Web.Router do scope "/", Fallback do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) + get("/api*path", RedirectController, :api_not_implemented) get("/*path", RedirectController, :redirector) options("/*path", RedirectController, :empty) @@ -721,6 +722,12 @@ defmodule Fallback.RedirectController do alias Pleroma.User alias Pleroma.Web.Metadata + def api_not_implemented(conn, _params) do + conn + |> put_status(404) + |> json(%{error: "Not implemented"}) + end + def redirector(conn, _params, code \\ 200) do conn |> put_resp_content_type("text/html") diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index 514923a20..cc78b3ae1 100644 --- a/test/web/fallback_test.exs +++ b/test/web/fallback_test.exs @@ -24,6 +24,12 @@ test "GET /:maybe_nickname_or_id", %{conn: conn} do |> html_response(200) =~ "" end + test "GET /api*path", %{conn: conn} do + assert conn + |> get("/api/foo") + |> json_response(404) == %{"error" => "Not implemented"} + end + test "GET /*path", %{conn: conn} do assert conn |> get("/foo") From f76268135c014c20a482d30a7c9596ec2e7d6a69 Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Wed, 22 May 2019 07:11:09 +0800 Subject: [PATCH 14/48] Fix failing test --- test/web/admin_api/admin_api_controller_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index ca12c7215..c15c67e31 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -397,14 +397,14 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: us end end - test "/api/pleroma/admin/invite_token" do + test "/api/pleroma/admin/users/invite_token" do admin = insert(:user, info: %{is_admin: true}) conn = build_conn() |> assign(:user, admin) |> put_req_header("accept", "application/json") - |> get("/api/pleroma/admin/invite_token") + |> get("/api/pleroma/admin/users/invite_token") assert conn.status == 200 end From a023ca004cbd90e330cab35e4dfda16346d08668 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 03:12:48 +0000 Subject: [PATCH 15/48] prune objects task: use Repo.delete_all() --- lib/mix/tasks/pleroma/database.ex | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index fdb216037..f9bafb277 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -5,6 +5,7 @@ defmodule Mix.Tasks.Pleroma.Database do alias Mix.Tasks.Pleroma.Common alias Pleroma.Conversation + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User require Logger @@ -78,6 +79,8 @@ def run(["update_users_following_followers_counts"]) do end def run(["prune_objects" | args]) do + import Ecto.Query + {options, [], []} = OptionParser.parse( args, @@ -96,11 +99,15 @@ def run(["prune_objects" | args]) do NaiveDateTime.utc_now() |> NaiveDateTime.add(-(deadline * 86_400)) - Repo.query!( - "DELETE FROM objects WHERE inserted_at < $1 AND split_part(data->>'actor', '/', 3) != $2", - [time_deadline, Pleroma.Web.Endpoint.host()], - timeout: :infinity + public = "https://www.w3.org/ns/activitystreams#Public" + + from(o in Object, + where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public), + where: o.inserted_at < ^time_deadline, + where: + fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host()) ) + |> Repo.delete_all() if Keyword.get(options, :vacuum) do Logger.info("Runnning VACUUM FULL") From 045803346d70c1f9c6ea770485904fd7cc52969a Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 03:58:15 +0000 Subject: [PATCH 16/48] move key generation functions into Pleroma.Keys module --- lib/pleroma/keys.ex | 44 +++++++++++++++++++ lib/pleroma/signature.ex | 7 ++- lib/pleroma/user.ex | 21 +++++++++ .../activity_pub/activity_pub_controller.ex | 14 +++--- .../web/activity_pub/views/user_view.ex | 11 +++-- lib/pleroma/web/federator/federator.ex | 6 +-- lib/pleroma/web/salmon/salmon.ex | 44 ++----------------- lib/pleroma/web/web_finger/web_finger.ex | 26 +---------- test/keys_test.exs | 20 +++++++++ test/user_test.exs | 15 +++++++ test/web/activity_pub/activity_pub_test.exs | 2 +- .../web/activity_pub/views/user_view_test.exs | 13 +++--- test/web/salmon/salmon_test.exs | 19 ++------ test/web/web_finger/web_finger_test.exs | 15 ------- 14 files changed, 133 insertions(+), 124 deletions(-) create mode 100644 lib/pleroma/keys.ex create mode 100644 test/keys_test.exs diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex new file mode 100644 index 000000000..b7bc7a4da --- /dev/null +++ b/lib/pleroma/keys.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Keys do + # Native generation of RSA keys is only available since OTP 20+ and in default build conditions + # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. + try do + _ = :public_key.generate_key({:rsa, 2048, 65_537}) + + def generate_rsa_pem do + key = :public_key.generate_key({:rsa, 2048, 65_537}) + entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) + pem = :public_key.pem_encode([entry]) |> String.trim_trailing() + {:ok, pem} + end + rescue + _ -> + def generate_rsa_pem do + port = Port.open({:spawn, "openssl genrsa"}, [:binary]) + + {:ok, pem} = + receive do + {^port, {:data, pem}} -> {:ok, pem} + end + + Port.close(port) + + if Regex.match?(~r/RSA PRIVATE KEY/, pem) do + {:ok, pem} + else + :error + end + end + end + + def keys_from_pem(pem) do + [private_key_code] = :public_key.pem_decode(pem) + private_key = :public_key.pem_entry_decode(private_key_code) + {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key + public_key = {:RSAPublicKey, modulus, exponent} + {:ok, private_key, public_key} + end +end diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index b7ecf00a0..1a4d54c62 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -5,11 +5,10 @@ defmodule Pleroma.Signature do @behaviour HTTPSignatures.Adapter + alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Salmon - alias Pleroma.Web.WebFinger def fetch_public_key(conn) do with actor_id <- Utils.get_ap_id(conn.params["actor"]), @@ -33,8 +32,8 @@ def refetch_public_key(conn) do end def sign(%User{} = user, headers) do - with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user), - {:ok, private_key, _} <- Salmon.keys_from_pem(keys) do + with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user), + {:ok, private_key, _} <- Keys.keys_from_pem(keys) do HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 05fe58f7c..653dec95f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -10,6 +10,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Pleroma.Activity + alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Registration @@ -1422,4 +1423,24 @@ def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do } } end + + def ensure_keys_present(user) do + info = user.info + + if info.keys do + {:ok, user} + else + {:ok, pem} = Keys.generate_rsa_pem() + + info_cng = + info + |> User.Info.set_keys(pem) + + cng = + Ecto.Changeset.change(user) + |> Ecto.Changeset.put_embed(:info, info_cng) + + update_and_set_cache(cng) + end + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c967ab7a9..ad2ca1e54 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -39,7 +39,7 @@ def relay_active?(conn, _) do def user(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("user.json", %{user: user})) @@ -106,7 +106,7 @@ def activity(conn, %{"uuid" => uuid}) do def following(conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do {page, _} = Integer.parse(page) conn @@ -117,7 +117,7 @@ def following(conn, %{"nickname" => nickname, "page" => page}) do def following(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("following.json", %{user: user})) @@ -126,7 +126,7 @@ def following(conn, %{"nickname" => nickname}) do def followers(conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do {page, _} = Integer.parse(page) conn @@ -137,7 +137,7 @@ def followers(conn, %{"nickname" => nickname, "page" => page}) do def followers(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("followers.json", %{user: user})) @@ -146,7 +146,7 @@ def followers(conn, %{"nickname" => nickname}) do def outbox(conn, %{"nickname" => nickname} = params) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]})) @@ -195,7 +195,7 @@ def inbox(conn, params) do def relay(conn, _params) do with %User{} = user <- Relay.get_actor(), - {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("user.json", %{user: user})) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 1254fdf6c..327e0e05b 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do use Pleroma.Web, :view + alias Pleroma.Keys alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -12,8 +13,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Endpoint alias Pleroma.Web.Router.Helpers - alias Pleroma.Web.Salmon - alias Pleroma.Web.WebFinger import Ecto.Query @@ -34,8 +33,8 @@ def render("endpoints.json", _), do: %{} # the instance itself is not a Person, but instead an Application def render("user.json", %{user: %{nickname: nil} = user}) do - {:ok, user} = WebFinger.ensure_keys_present(user) - {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) + {:ok, user} = User.ensure_keys_present(user) + {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) @@ -62,8 +61,8 @@ def render("user.json", %{user: %{nickname: nil} = user}) do end def render("user.json", %{user: user}) do - {:ok, user} = WebFinger.ensure_keys_present(user) - {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) + {:ok, user} = User.ensure_keys_present(user) + {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 169fdf4dc..6b0b75284 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.RetryQueue - alias Pleroma.Web.WebFinger alias Pleroma.Web.Websub require Logger @@ -77,9 +76,8 @@ def perform(:request_subscription, websub) do def perform(:publish, activity) do Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) - with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do - {:ok, actor} = WebFinger.ensure_keys_present(actor) - + with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), + {:ok, actor} <- User.ensure_keys_present(actor) do Publisher.publish(actor, activity) end end diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 42709ab47..fa30f73cd 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.Salmon do use Bitwise alias Pleroma.Activity + alias Pleroma.Keys alias Pleroma.Instances alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility @@ -89,45 +90,6 @@ def encode_key({:RSAPublicKey, modulus, exponent}) do "RSA.#{modulus_enc}.#{exponent_enc}" end - # Native generation of RSA keys is only available since OTP 20+ and in default build conditions - # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. - try do - _ = :public_key.generate_key({:rsa, 2048, 65_537}) - - def generate_rsa_pem do - key = :public_key.generate_key({:rsa, 2048, 65_537}) - entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) - pem = :public_key.pem_encode([entry]) |> String.trim_trailing() - {:ok, pem} - end - rescue - _ -> - def generate_rsa_pem do - port = Port.open({:spawn, "openssl genrsa"}, [:binary]) - - {:ok, pem} = - receive do - {^port, {:data, pem}} -> {:ok, pem} - end - - Port.close(port) - - if Regex.match?(~r/RSA PRIVATE KEY/, pem) do - {:ok, pem} - else - :error - end - end - end - - def keys_from_pem(pem) do - [private_key_code] = :public_key.pem_decode(pem) - private_key = :public_key.pem_entry_decode(private_key_code) - {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key - public_key = {:RSAPublicKey, modulus, exponent} - {:ok, private_key, public_key} - end - def encode(private_key, doc) do type = "application/atom+xml" encoding = "base64url" @@ -227,7 +189,7 @@ def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity |> :xmerl.export_simple(:xmerl_xml) |> to_string - {:ok, private, _} = keys_from_pem(keys) + {:ok, private, _} = Keys.keys_from_pem(keys) {:ok, feed} = encode(private, feed) remote_users = remote_users(activity) @@ -253,7 +215,7 @@ def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end) def gather_webfinger_links(%User{} = user) do - {:ok, _private, public} = keys_from_pem(user.info.keys) + {:ok, _private, public} = Keys.keys_from_pem(user.info.keys) magic_key = encode_key(public) [ diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 1239b962a..c5b7d4acb 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.WebFinger do alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.Salmon alias Pleroma.Web.XML alias Pleroma.XmlBuilder require Jason @@ -61,7 +60,7 @@ defp gather_links(%User{} = user) do end def represent_user(user, "JSON") do - {:ok, user} = ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) %{ "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", @@ -71,7 +70,7 @@ def represent_user(user, "JSON") do end def represent_user(user, "XML") do - {:ok, user} = ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) links = gather_links(user) @@ -88,27 +87,6 @@ def represent_user(user, "XML") do |> XmlBuilder.to_doc() end - # This seems a better fit in Salmon - def ensure_keys_present(user) do - info = user.info - - if info.keys do - {:ok, user} - else - {:ok, pem} = Salmon.generate_rsa_pem() - - info_cng = - info - |> User.Info.set_keys(pem) - - cng = - Ecto.Changeset.change(user) - |> Ecto.Changeset.put_embed(:info, info_cng) - - User.update_and_set_cache(cng) - end - end - defp get_magic_key(magic_key) do "data:application/magic-public-key," <> magic_key = magic_key {:ok, magic_key} diff --git a/test/keys_test.exs b/test/keys_test.exs new file mode 100644 index 000000000..776fdea6f --- /dev/null +++ b/test/keys_test.exs @@ -0,0 +1,20 @@ +defmodule Pleroma.KeysTest do + use Pleroma.DataCase + + alias Pleroma.Keys + + test "generates an RSA private key pem" do + {:ok, key} = Keys.generate_rsa_pem() + + assert is_binary(key) + assert Regex.match?(~r/RSA/, key) + end + + test "returns a public and private key from a pem" do + pem = File.read!("test/fixtures/private_key.pem") + {:ok, private, public} = Keys.keys_from_pem(pem) + + assert elem(private, 0) == :RSAPrivateKey + assert elem(public, 0) == :RSAPublicKey + end +end diff --git a/test/user_test.exs b/test/user_test.exs index cb6afbe07..019f2b56d 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1251,4 +1251,19 @@ test "if user is unconfirmed" do refute user.info.confirmation_token end end + + describe "ensure_keys_present" do + test "it creates keys for a user and stores them in info" do + user = insert(:user) + refute is_binary(user.info.keys) + {:ok, user} = User.ensure_keys_present(user) + assert is_binary(user.info.keys) + end + + test "it doesn't create keys if there already are some" do + user = insert(:user, %{info: %{keys: "xxx"}}) + {:ok, user} = User.ensure_keys_present(user) + assert user.info.keys == "xxx" + end + end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index c18e0ab5f..f743f380b 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1005,7 +1005,7 @@ test "it filters broken threads" do describe "update" do test "it creates an update activity with the new user data" do user = insert(:user) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) {:ok, update} = diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 9fb9455d2..e6483db8b 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -2,11 +2,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do use Pleroma.DataCase import Pleroma.Factory + alias Pleroma.User alias Pleroma.Web.ActivityPub.UserView test "Renders a user, including the public key" do user = insert(:user) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -18,7 +19,7 @@ test "Renders a user, including the public key" do test "Does not add an avatar image if the user hasn't set one" do user = insert(:user) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) refute result["icon"] @@ -32,7 +33,7 @@ test "Does not add an avatar image if the user hasn't set one" do } ) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) assert result["icon"]["url"] == "https://someurl" @@ -42,7 +43,7 @@ test "Does not add an avatar image if the user hasn't set one" do describe "endpoints" do test "local users have a usable endpoints structure" do user = insert(:user) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -58,7 +59,7 @@ test "local users have a usable endpoints structure" do test "remote users have an empty endpoints structure" do user = insert(:user, local: false) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -68,7 +69,7 @@ test "remote users have an empty endpoints structure" do test "instance users do not expose oAuth endpoints" do user = insert(:user, nickname: nil, local: true) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs index 232082779..e86e76fe9 100644 --- a/test/web/salmon/salmon_test.exs +++ b/test/web/salmon/salmon_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Keys alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.Federator.Publisher @@ -34,12 +35,6 @@ test "errors on wrong magic key" do assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error end - test "generates an RSA private key pem" do - {:ok, key} = Salmon.generate_rsa_pem() - assert is_binary(key) - assert Regex.match?(~r/RSA/, key) - end - test "it encodes a magic key from a public key" do key = Salmon.decode_key(@magickey) magic_key = Salmon.encode_key(key) @@ -51,18 +46,10 @@ test "it decodes a friendica public key" do _key = Salmon.decode_key(@magickey_friendica) end - test "returns a public and private key from a pem" do - pem = File.read!("test/fixtures/private_key.pem") - {:ok, private, public} = Salmon.keys_from_pem(pem) - - assert elem(private, 0) == :RSAPrivateKey - assert elem(public, 0) == :RSAPublicKey - end - test "encodes an xml payload with a private key" do doc = File.read!("test/fixtures/incoming_note_activity.xml") pem = File.read!("test/fixtures/private_key.pem") - {:ok, private, public} = Salmon.keys_from_pem(pem) + {:ok, private, public} = Keys.keys_from_pem(pem) # Let's try a roundtrip. {:ok, salmon} = Salmon.encode(private, doc) @@ -105,7 +92,7 @@ test "it gets a magic key" do {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) user = User.get_cached_by_ap_id(activity.data["actor"]) - {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + {:ok, user} = User.ensure_keys_present(user) Salmon.publish(user, activity) diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 6b20d8d56..335c95b18 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -105,19 +105,4 @@ test "it gets the xrd endpoint for statusnet" do assert template == "http://status.alpicola.com/main/xrd?uri={uri}" end end - - describe "ensure_keys_present" do - test "it creates keys for a user and stores them in info" do - user = insert(:user) - refute is_binary(user.info.keys) - {:ok, user} = WebFinger.ensure_keys_present(user) - assert is_binary(user.info.keys) - end - - test "it doesn't create keys if there already are some" do - user = insert(:user, %{info: %{keys: "xxx"}}) - {:ok, user} = WebFinger.ensure_keys_present(user) - assert user.info.keys == "xxx" - end - end end From 913484817076bf5ca4bdbe7c3c1ff34f7debd3e5 Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Wed, 22 May 2019 04:04:20 +0000 Subject: [PATCH 17/48] Do not truncate DM when it contains newlines and safe_dm_mentions is set to true --- lib/pleroma/formatter.ex | 2 +- test/formatter_test.exs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 3d7c36d21..3e3b9fe97 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do alias Pleroma.User alias Pleroma.Web.MediaProxy - @safe_mention_regex ~r/^(\s*(?@.+?\s+)+)(?.*)/ + @safe_mention_regex ~r/^(\s*(?@.+?\s+)+)(?.*)/s @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 5e7011160..47b91b121 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -206,6 +206,15 @@ test "given the 'safe_mention' option, it will still work without any mention" d assert mentions == [] assert expected_text == text end + + test "given the 'safe_mention' option, it will keep text after newlines" do + user = insert(:user) + text = " @#{user.nickname}\n hey dude\n\nhow are you doing?" + + {expected_text, _, _} = Formatter.linkify(text, safe_mention: true) + + assert expected_text =~ "how are you doing?" + end end describe ".parse_tags" do From 0e2c215a006c7ca5756e80a357ff6395a4325946 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 22 May 2019 07:22:19 +0200 Subject: [PATCH 18/48] MastoAPI AccountView: fill source.note with plaintext version of note Closes: https://git.pleroma.social/pleroma/pleroma/issues/926 --- lib/pleroma/web/mastodon_api/views/account_view.ex | 2 +- test/web/mastodon_api/account_view_test.exs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 134c07b7e..b82d3319b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -112,7 +112,7 @@ defp do_render("account.json", %{user: user} = opts) do fields: fields, bot: bot, source: %{ - note: "", + note: HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")), sensitive: false, pleroma: %{} }, diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index a24f2a050..aaf2261bb 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -55,7 +55,7 @@ test "Represent a user account" do fields: [], bot: false, source: %{ - note: "", + note: "valid html", sensitive: false, pleroma: %{} }, @@ -120,7 +120,7 @@ test "Represent a Service(bot) account" do fields: [], bot: true, source: %{ - note: "", + note: user.bio, sensitive: false, pleroma: %{} }, @@ -209,7 +209,7 @@ test "represent an embedded relationship" do fields: [], bot: true, source: %{ - note: "", + note: user.bio, sensitive: false, pleroma: %{} }, From 1344c85e2fe636fd6b9d033eb7add1c3a9701c7f Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 05:58:51 +0000 Subject: [PATCH 19/48] salmon: fix credo --- lib/pleroma/web/salmon/salmon.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index fa30f73cd..f25d92fad 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.Salmon do use Bitwise alias Pleroma.Activity - alias Pleroma.Keys alias Pleroma.Instances + alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator.Publisher From f9e0d09ec0082a096dcd4980bc5ffebe8e3139ae Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 10:17:32 +0200 Subject: [PATCH 20/48] Changelog: Add SSH mode. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff70e6e5..3d1e7640d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added +- Optional SSH access mode. - [MongooseIM](https://github.com/esl/MongooseIM) http authentication support. - LDAP authentication - External OAuth provider authentication From b6cf62ddeab04db6bd2695c5537c81e0fb1aecaf Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 10:28:50 +0200 Subject: [PATCH 21/48] Mix: Don't start esshd application if we don't need it. --- mix.exs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 95c052c34..bc5b6204f 100644 --- a/mix.exs +++ b/mix.exs @@ -40,9 +40,16 @@ def project do # # Type `mix help compile.app` for more information. def application do + extra_applications = [:logger, :runtime_tools, :comeonin, :quack] + extra_applications = if Application.get_env(:esshd, :enabled, false) do + [:esshd | extra_applications] + else + extra_applications + end + [ mod: {Pleroma.Application, []}, - extra_applications: [:logger, :runtime_tools, :comeonin, :esshd, :quack], + extra_applications: extra_applications, included_applications: [:ex_syslogger] ] end From db9a82d168cfc452611a44d92df2b81a5e6d1e69 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 10:40:15 +0200 Subject: [PATCH 22/48] Linting. --- mix.exs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index bc5b6204f..25ec46a46 100644 --- a/mix.exs +++ b/mix.exs @@ -41,11 +41,13 @@ def project do # Type `mix help compile.app` for more information. def application do extra_applications = [:logger, :runtime_tools, :comeonin, :quack] - extra_applications = if Application.get_env(:esshd, :enabled, false) do - [:esshd | extra_applications] - else - extra_applications - end + + extra_applications = + if Application.get_env(:esshd, :enabled, false) do + [:esshd | extra_applications] + else + extra_applications + end [ mod: {Pleroma.Application, []}, From b22145cbc40b57cf83f6389f063a76a03625ff16 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 10:44:26 +0200 Subject: [PATCH 23/48] Documentation: Specify PEM format for SSH keys. Otherwise openssh-client 7.9 will generate a different format that can't be used by esshd. --- docs/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 197326bbd..63ca61d1e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -477,7 +477,7 @@ config :esshd, password_authenticator: "Pleroma.BBS.Authenticator" ``` -Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT` +Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT` ## :auth From 3b12e1ba7c99382c678ce17629352135f44dcb9f Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 11:01:10 +0200 Subject: [PATCH 24/48] Changelog: Add tip for debian users. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1e7640d..b88edd072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added -- Optional SSH access mode. +- Optional SSH access mode. (Needs `erlang-ssh` package on Debian). - [MongooseIM](https://github.com/esl/MongooseIM) http authentication support. - LDAP authentication - External OAuth provider authentication From f4cfcead8868481c19ebd93b0fcd2b942dc0e477 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 11:44:17 +0200 Subject: [PATCH 25/48] Mix: Bring ecto-sql back to mainline. --- mix.exs | 5 +---- mix.lock | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 95c052c34..9149b241f 100644 --- a/mix.exs +++ b/mix.exs @@ -66,10 +66,7 @@ defp deps do {:plug_cowboy, "~> 2.0"}, {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 4.0"}, - {:ecto_sql, - git: "https://github.com/elixir-ecto/ecto_sql", - ref: "14cb065a74c488d737d973f7a91bc036c6245f78", - override: true}, + {:ecto_sql, "~> 3.1"}, {:postgrex, ">= 0.13.5"}, {:gettext, "~> 0.15"}, {:comeonin, "~> 4.1.1"}, diff --git a/mix.lock b/mix.lock index bacc09787..857bfca79 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "14cb065a74c488d737d973f7a91bc036c6245f78", [ref: "14cb065a74c488d737d973f7a91bc036c6245f78"]}, + "ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, From f323031927ecaf155e661b17cc9b96333fb9e4ad Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 May 2019 12:57:20 +0200 Subject: [PATCH 26/48] Mix: Only start sshd when needed, second try. --- mix.exs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/mix.exs b/mix.exs index 25ec46a46..b2c075c85 100644 --- a/mix.exs +++ b/mix.exs @@ -40,18 +40,9 @@ def project do # # Type `mix help compile.app` for more information. def application do - extra_applications = [:logger, :runtime_tools, :comeonin, :quack] - - extra_applications = - if Application.get_env(:esshd, :enabled, false) do - [:esshd | extra_applications] - else - extra_applications - end - [ mod: {Pleroma.Application, []}, - extra_applications: extra_applications, + extra_applications: [:logger, :runtime_tools, :comeonin, :quack], included_applications: [:ex_syslogger] ] end @@ -129,7 +120,7 @@ defp deps do {:recon, github: "ferd/recon", tag: "2.4.0"}, {:quack, "~> 0.1.1"}, {:benchee, "~> 1.0"}, - {:esshd, "~> 0.1.0"}, + {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:ex_rated, "~> 1.2"}, {:plug_static_index_html, "~> 1.0.0"}, {:excoveralls, "~> 0.11.1", only: :test} From 78ac8ee56139ed98625c54ce627eb37047a361f0 Mon Sep 17 00:00:00 2001 From: lambda Date: Wed, 22 May 2019 11:07:51 +0000 Subject: [PATCH 27/48] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b88edd072..bb2306fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added -- Optional SSH access mode. (Needs `erlang-ssh` package on Debian). +- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions). - [MongooseIM](https://github.com/esl/MongooseIM) http authentication support. - LDAP authentication - External OAuth provider authentication From 620908a2db86942a00bc0ba9c71c037061e26967 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 22 May 2019 15:44:50 +0000 Subject: [PATCH 28/48] [#699] add worker to clean expired oauth tokens --- CHANGELOG.md | 1 + config/config.exs | 4 +- docs/config.md | 2 + lib/pleroma/application.ex | 1 + lib/pleroma/web/oauth/token.ex | 36 ++++++-------- lib/pleroma/web/oauth/token/clean_worker.ex | 41 +++++++++++++++ lib/pleroma/web/oauth/token/query.ex | 55 +++++++++++++++++++++ test/web/oauth/token_test.exs | 13 +++++ 8 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 lib/pleroma/web/oauth/token/clean_worker.ex create mode 100644 lib/pleroma/web/oauth/token/query.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index b5c42d1fd..02d64a850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - OAuth: added support for refresh tokens - Emoji packs and emoji pack manager - Object pruning (`mix pleroma.database prune_objects`) +- OAuth: added job to clean expired access tokens ### Changed - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer diff --git a/config/config.exs b/config/config.exs index a05f8b1d2..33b7e713d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -481,7 +481,9 @@ config :pleroma, :oauth2, token_expires_in: 600, - issue_new_refresh_token: true + issue_new_refresh_token: true, + clean_expired_tokens: false, + clean_expired_tokens_interval: 86_400_000 config :pleroma, :database, rum_enabled: false diff --git a/docs/config.md b/docs/config.md index a050068f4..264b65499 100644 --- a/docs/config.md +++ b/docs/config.md @@ -550,6 +550,8 @@ Configure OAuth 2 provider capabilities: * `token_expires_in` - The lifetime in seconds of the access token. * `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token. +* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. +* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours). ## :emoji * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index dab45a0b2..76df3945e 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -110,6 +110,7 @@ def start(_type, _args) do hackney_pool_children() ++ [ worker(Pleroma.Web.Federator.RetryQueue, []), + worker(Pleroma.Web.OAuth.Token.CleanWorker, []), worker(Pleroma.Stats, []), worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init), worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init) diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 66c95c2e9..f412f7eb2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.OAuth.Token do use Ecto.Schema - import Ecto.Query import Ecto.Changeset alias Pleroma.Repo @@ -13,6 +12,7 @@ defmodule Pleroma.Web.OAuth.Token do alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Query @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600) @type t :: %__MODULE__{} @@ -31,17 +31,17 @@ defmodule Pleroma.Web.OAuth.Token do @doc "Gets token for app by access token" @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(%App{id: app_id} = _app, token) do - from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token) + Query.get_by_app(app_id) + |> Query.get_by_token(token) |> Repo.find_resource() end @doc "Gets token for app by refresh token" @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_refresh_token(%App{id: app_id} = _app, token) do - from(t in __MODULE__, - where: t.app_id == ^app_id and t.refresh_token == ^token, - preload: [:user] - ) + Query.get_by_app(app_id) + |> Query.get_by_refresh_token(token) + |> Query.preload([:user]) |> Repo.find_resource() end @@ -97,29 +97,25 @@ def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do end def delete_user_tokens(%User{id: user_id}) do - from( - t in Token, - where: t.user_id == ^user_id - ) + Query.get_by_user(user_id) |> Repo.delete_all() end def delete_user_token(%User{id: user_id}, token_id) do - from( - t in Token, - where: t.user_id == ^user_id, - where: t.id == ^token_id - ) + Query.get_by_user(user_id) + |> Query.get_by_id(token_id) + |> Repo.delete_all() + end + + def delete_expired_tokens do + Query.get_expired_tokens() |> Repo.delete_all() end def get_user_tokens(%User{id: user_id}) do - from( - t in Token, - where: t.user_id == ^user_id - ) + Query.get_by_user(user_id) + |> Query.preload([:app]) |> Repo.all() - |> Repo.preload(:app) end def is_expired?(%__MODULE__{valid_until: valid_until}) do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex new file mode 100644 index 000000000..dca852449 --- /dev/null +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.CleanWorker do + @moduledoc """ + The module represents functions to clean an expired oauth tokens. + """ + + # 10 seconds + @start_interval 10_000 + @interval Pleroma.Config.get( + # 24 hours + [:oauth2, :clean_expired_tokens_interval], + 86_400_000 + ) + @queue :background + + alias Pleroma.Web.OAuth.Token + + def start_link, do: GenServer.start_link(__MODULE__, nil) + + def init(_) do + if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do + Process.send_after(self(), :perform, @start_interval) + {:ok, nil} + else + :ignore + end + end + + @doc false + def handle_info(:perform, state) do + Process.send_after(self(), :perform, @interval) + PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean]) + {:noreply, state} + end + + # Job Worker Callbacks + def perform(:clean), do: Token.delete_expired_tokens() +end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex new file mode 100644 index 000000000..d92e1f071 --- /dev/null +++ b/lib/pleroma/web/oauth/token/query.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.Query do + @moduledoc """ + Contains queries for OAuth Token. + """ + + import Ecto.Query, only: [from: 2] + + @type query :: Ecto.Queryable.t() | Token.t() + + alias Pleroma.Web.OAuth.Token + + @spec get_by_refresh_token(query, String.t()) :: query + def get_by_refresh_token(query \\ Token, refresh_token) do + from(q in query, where: q.refresh_token == ^refresh_token) + end + + @spec get_by_token(query, String.t()) :: query + def get_by_token(query \\ Token, token) do + from(q in query, where: q.token == ^token) + end + + @spec get_by_app(query, String.t()) :: query + def get_by_app(query \\ Token, app_id) do + from(q in query, where: q.app_id == ^app_id) + end + + @spec get_by_id(query, String.t()) :: query + def get_by_id(query \\ Token, id) do + from(q in query, where: q.id == ^id) + end + + @spec get_expired_tokens(query, DateTime.t() | nil) :: query + def get_expired_tokens(query \\ Token, date \\ nil) do + expired_date = date || Timex.now() + from(q in query, where: fragment("?", q.valid_until) < ^expired_date) + end + + @spec get_by_user(query, String.t()) :: query + def get_by_user(query \\ Token, user_id) do + from(q in query, where: q.user_id == ^user_id) + end + + @spec preload(query, any) :: query + def preload(query \\ Token, assoc_preload \\ []) + + def preload(query, assoc_preload) when is_list(assoc_preload) do + from(q in query, preload: ^assoc_preload) + end + + def preload(query, _assoc_preload), do: query +end diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs index ad2a49f09..3c07309b7 100644 --- a/test/web/oauth/token_test.exs +++ b/test/web/oauth/token_test.exs @@ -69,4 +69,17 @@ test "deletes all tokens of a user" do assert tokens == 2 end + + test "deletes expired tokens" do + insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) + insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) + t3 = insert(:oauth_token) + t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10)) + {tokens, _} = Token.delete_expired_tokens() + assert tokens == 2 + available_tokens = Pleroma.Repo.all(Token) + + token_ids = available_tokens |> Enum.map(& &1.id) + assert token_ids == [t3.id, t4.id] + end end From 54e10a3e55fe46b71ef7f330605baf8bcccd5a44 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 22 May 2019 20:10:52 +0300 Subject: [PATCH 29/48] Disable timeouts for object pruning query --- lib/mix/tasks/pleroma/database.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index f9bafb277..4d480ac3f 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -107,7 +107,7 @@ def run(["prune_objects" | args]) do where: fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host()) ) - |> Repo.delete_all() + |> Repo.delete_all(timeout: :infinity) if Keyword.get(options, :vacuum) do Logger.info("Runnning VACUUM FULL") From 75b6c4b00433560fb5ee502f13e8261b4b8a246a Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 04:15:59 +0000 Subject: [PATCH 30/48] mrf: defang policy modules for filtering user profile objects --- lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 6 ++++-- lib/pleroma/web/activity_pub/mrf/user_allowlist.ex | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 7190652d2..ffaa4b7db 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -105,8 +105,8 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} defp check_report_removal(_actor_info, object), do: {:ok, object} @impl true - def filter(object) do - actor_info = URI.parse(object["actor"]) + def filter(%{"actor" => actor} = object) do + actor_info = URI.parse(actor) with {:ok, object} <- check_accept(actor_info, object), {:ok, object} <- check_reject(actor_info, object), @@ -119,4 +119,6 @@ def filter(object) do _e -> {:reject, nil} end end + + def filter(object), do: {:ok, object} end diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex index f5078d818..47663414a 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex @@ -19,10 +19,12 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do end @impl true - def filter(object) do - actor_info = URI.parse(object["actor"]) + def filter(%{"actor" => actor} = object) do + actor_info = URI.parse(actor) allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], []) filter_by_list(object, allow_list) end + + def filter(object), do: {:ok, object} end From 60f882b09f5f837546d59f8eef56b905e065ec60 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 04:33:10 +0000 Subject: [PATCH 31/48] activitypub: run user objects through MRF filters --- lib/pleroma/web/activity_pub/activity_pub.ex | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3d9679ec0..aa0229db7 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -909,7 +909,7 @@ def upload(file, opts \\ []) do end end - def user_data_from_user_object(data) do + defp object_to_user_data(data) do avatar = data["icon"]["url"] && %{ @@ -956,9 +956,19 @@ def user_data_from_user_object(data) do {:ok, user_data} end + def user_data_from_user_object(data) do + with {:ok, data} <- MRF.filter(data), + {:ok, data} <- object_to_user_data(data) do + {:ok, data} + else + e -> {:error, e} + end + end + def fetch_and_prepare_user_from_ap_id(ap_id) do - with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do - user_data_from_user_object(data) + with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), + {:ok, data} <- user_data_from_user_object(data) do + {:ok, data} else e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") end From baf72d6c580e5c05ef5fea8a57c57150a5d38589 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 04:55:16 +0000 Subject: [PATCH 32/48] mrf: simple policy: add the ability to strip avatars and banners from user profiles --- config/config.exs | 4 ++- .../web/activity_pub/mrf/simple_policy.ex | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 33b7e713d..e90821d66 100644 --- a/config/config.exs +++ b/config/config.exs @@ -314,7 +314,9 @@ federated_timeline_removal: [], report_removal: [], reject: [], - accept: [] + accept: [], + avatar_removal: [], + banner_removal: [] config :pleroma, :mrf_keyword, reject: [], diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index ffaa4b7db..890d70a7a 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -104,6 +104,26 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} defp check_report_removal(_actor_info, object), do: {:ok, object} + defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do + if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do + {:ok, Map.delete(object, "icon")} + else + {:ok, object} + end + end + + defp check_avatar_removal(_actor_info, object), do: {:ok, object} + + defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do + if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do + {:ok, Map.delete(object, "image")} + else + {:ok, object} + end + end + + defp check_banner_removal(_actor_info, object), do: {:ok, object} + @impl true def filter(%{"actor" => actor} = object) do actor_info = URI.parse(actor) @@ -120,5 +140,17 @@ def filter(%{"actor" => actor} = object) do end end + def filter(%{"id" => actor, "type" => obj_type} = object) + when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do + actor_info = URI.parse(actor) + + with {:ok, object} <- check_avatar_removal(actor_info, object), + {:ok, object} <- check_banner_removal(actor_info, object) do + {:ok, object} + else + _e -> {:reject, nil} + end + end + def filter(object), do: {:ok, object} end From 8086c7aed6cdc3b2ac1c09c6c40344e47be08ed9 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 05:08:37 +0000 Subject: [PATCH 33/48] tests: add tests for banner and avatar removal --- .../activity_pub/mrf/simple_policy_test.exs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 74af7dcde..3d1f26e60 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -17,7 +17,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do federated_timeline_removal: [], report_removal: [], reject: [], - accept: [] + accept: [], + avatar_removal: [], + banner_removal: [] ) on_exit(fn -> @@ -206,6 +208,60 @@ test "has a matching host" do end end + describe "when :avatar_removal" do + test "is empty" do + Config.put([:mrf_simple, :avatar_removal], []) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "is not empty but it doesn't have a matching host" do + Config.put([:mrf_simple, :avatar_removal], ["non.matching.remote"]) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "has a matching host" do + Config.put([:mrf_simple, :avatar_removal], ["remote.instance"]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["icon"] + end + end + + describe "when :banner_removal" do + test "is empty" do + Config.put([:mrf_simple, :banner_removal], []) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "is not empty but it doesn't have a matching host" do + Config.put([:mrf_simple, :banner_removal], ["non.matching.remote"]) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "has a matching host" do + Config.put([:mrf_simple, :banner_removal], ["remote.instance"]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["image"] + end + end + defp build_local_message do %{ "actor" => "#{Pleroma.Web.base_url()}/users/alice", @@ -217,4 +273,19 @@ defp build_local_message do defp build_remote_message do %{"actor" => "https://remote.instance/users/bob"} end + + defp build_remote_user do + %{ + "id" => "https://remote.instance/users/bob", + "icon" => %{ + "url" => "http://example.com/image.jpg", + "type" => "Image" + }, + "image" => %{ + "url" => "http://example.com/image.jpg", + "type" => "Image" + }, + "type" => "Person" + } + end end From 7d9b33b3cebeed451210f754a8c34cc14a9e969b Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 22 May 2019 05:55:09 +0000 Subject: [PATCH 34/48] update documentation for the new MRF features [no-ci] --- CHANGELOG.md | 2 ++ docs/config.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d64a850..2a2b11ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Emoji packs and emoji pack manager - Object pruning (`mix pleroma.database prune_objects`) - OAuth: added job to clean expired access tokens +- MRF: Support for rejecting reports from specific instances (`mrf_simple`) +- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`) ### Changed - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer diff --git a/docs/config.md b/docs/config.md index 264b65499..1d1d24c32 100644 --- a/docs/config.md +++ b/docs/config.md @@ -220,6 +220,9 @@ relates to mascots on the mastodon frontend * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline * `reject`: List of instances to reject any activities from * `accept`: List of instances to accept any activities from +* `report_removal`: List of instances to reject reports from +* `avatar_removal`: List of instances to strip avatars from +* `banner_removal`: List of instances to strip banners from ## :mrf_rejectnonpublic * `allow_followersonly`: whether to allow followers-only posts From 356c047759735bcd984ebd44059a21a3cf22af0e Mon Sep 17 00:00:00 2001 From: Alfie Pates Date: Thu, 23 May 2019 22:33:27 +0100 Subject: [PATCH 35/48] explicitly set reverse proxy upstream to IPv4 since Pleroma.Web.Endpoint binds on IPv4 only and `localhost.` resolves to [::0] on some systems fixes #930. --- installation/caddyfile-pleroma.example | 4 +++- installation/pleroma-apache.conf | 6 ++++-- installation/pleroma.nginx | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/installation/caddyfile-pleroma.example b/installation/caddyfile-pleroma.example index fcf76718e..7985d9c67 100644 --- a/installation/caddyfile-pleroma.example +++ b/installation/caddyfile-pleroma.example @@ -10,7 +10,9 @@ example.tld { gzip - proxy / localhost:4000 { + # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only + # and `localhost.` resolves to [::0] on some systems: see issue #930 + proxy / 127.0.0.1:4000 { websocket transparent } diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf index 2beb7c4cc..b5640ac3d 100644 --- a/installation/pleroma-apache.conf +++ b/installation/pleroma-apache.conf @@ -58,8 +58,10 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined RewriteRule /(.*) ws://localhost:4000/$1 [P,L] ProxyRequests off - ProxyPass / http://localhost:4000/ - ProxyPassReverse / http://localhost:4000/ + # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only + # and `localhost.` resolves to [::0] on some systems: see issue #930 + ProxyPass / http://127.0.0.1:4000/ + ProxyPassReverse / http://127.0.0.1:4000/ RequestHeader set Host ${servername} ProxyPreserveHost On diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index cc75d78b2..7425da33f 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -69,7 +69,9 @@ server { proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; - proxy_pass http://localhost:4000; + # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only + # and `localhost.` resolves to [::0] on some systems: see issue #930 + proxy_pass http://127.0.0.1:4000; client_max_body_size 16m; } From f916e4cdd9a502b83c615146c598be135f47e57a Mon Sep 17 00:00:00 2001 From: feld Date: Fri, 24 May 2019 20:33:55 +0000 Subject: [PATCH 36/48] Move the Cache Control header test to its own file We can consolidate our cache control header tests here --- lib/pleroma/web/endpoint.ex | 19 +++++++++++++++++-- test/plugs/cache_control_test.exs | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/plugs/cache_control_test.exs diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 9ef30e885..8cd7a2270 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -16,17 +16,32 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.UploadedMedia) + @static_cache_control "public, no-cache" + # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well - plug(Pleroma.Plugs.InstanceStatic, at: "/") + # Cache-control headers are duplicated in case we turn off etags in the future + plug(Pleroma.Plugs.InstanceStatic, + at: "/", + gzip: true, + cache_control_for_etags: @static_cache_control, + headers: %{ + "cache-control" => @static_cache_control + } + ) plug( Plug.Static, at: "/", from: :pleroma, only: - ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength + gzip: true, + cache_control_for_etags: @static_cache_control, + headers: %{ + "cache-control" => @static_cache_control + } ) plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") diff --git a/test/plugs/cache_control_test.exs b/test/plugs/cache_control_test.exs new file mode 100644 index 000000000..45151b289 --- /dev/null +++ b/test/plugs/cache_control_test.exs @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.CacheControlTest do + use Pleroma.Web.ConnCase + alias Plug.Conn + + test "Verify Cache-Control header on static assets", %{conn: conn} do + conn = get(conn, "/index.html") + + assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"] + end + + test "Verify Cache-Control header on the API", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + + assert Conn.get_resp_header(conn, "cache-control") == ["max-age=0, private, must-revalidate"] + end +end From bbea5691da67916151a883f09e24da7c2e27d9ba Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Fri, 24 May 2019 20:34:23 +0000 Subject: [PATCH 37/48] Mention all people in the beginning of DM --- lib/pleroma/formatter.ex | 2 +- test/formatter_test.exs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 3e3b9fe97..607843a5b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do alias Pleroma.User alias Pleroma.Web.MediaProxy - @safe_mention_regex ~r/^(\s*(?@.+?\s+)+)(?.*)/s + @safe_mention_regex ~r/^(\s*(?(@.+?\s+){1,})+)(?.*)/s @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 47b91b121..bfa673049 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -184,17 +184,19 @@ test "does not give a replacement for single-character local nicknames who don't test "given the 'safe_mention' option, it will only mention people in the beginning" do user = insert(:user) - _other_user = insert(:user) + other_user = insert(:user) third_user = insert(:user) - text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}" + text = " @#{user.nickname} @#{other_user.nickname} hey dudes i hate @#{third_user.nickname}" {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true) - assert mentions == [{"@#{user.nickname}", user}] + assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}] assert expected_text == "@#{user.nickname} hey dude i hate @#{user.nickname} @#{other_user.nickname} hey dudes i hate @#{third_user.nickname}" end From 9415932af5829f2aa19e362076e0653dd1ce9c5a Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Sat, 25 May 2019 08:15:12 +0800 Subject: [PATCH 38/48] Keep nodeinfo available when not federating --- .../web/nodeinfo/nodeinfo_controller.ex | 2 - test/web/node_info_test.exs | 80 ++++--------------- 2 files changed, 16 insertions(+), 66 deletions(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 3bf2a0fbc..45f90c579 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.Federator.Publisher - plug(Pleroma.Web.FederatingPlug) - def schemas(conn, _params) do response = %{ links: [ diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 2fc42b7cc..be1173513 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -7,6 +7,22 @@ defmodule Pleroma.Web.NodeInfoTest do import Pleroma.Factory + test "GET /.well-known/nodeinfo", %{conn: conn} do + links = + conn + |> get("/.well-known/nodeinfo") + |> json_response(200) + |> Map.fetch!("links") + + Enum.each(links, fn link -> + href = Map.fetch!(link, "href") + + conn + |> get(href) + |> json_response(200) + end) + end + test "nodeinfo shows staff accounts", %{conn: conn} do moderator = insert(:user, %{local: true, info: %{is_moderator: true}}) admin = insert(:user, %{local: true, info: %{is_admin: true}}) @@ -32,70 +48,6 @@ test "nodeinfo shows restricted nicknames", %{conn: conn} do result["metadata"]["restrictedNicknames"] end - test "returns 404 when federation is disabled", %{conn: conn} do - instance = - Application.get_env(:pleroma, :instance) - |> Keyword.put(:federating, false) - - Application.put_env(:pleroma, :instance, instance) - - conn - |> get("/.well-known/nodeinfo") - |> json_response(404) - - conn - |> get("/nodeinfo/2.1.json") - |> json_response(404) - - instance = - Application.get_env(:pleroma, :instance) - |> Keyword.put(:federating, true) - - Application.put_env(:pleroma, :instance, instance) - end - - test "returns 200 when federation is enabled", %{conn: conn} do - conn - |> get("/.well-known/nodeinfo") - |> json_response(200) - - conn - |> get("/nodeinfo/2.1.json") - |> json_response(200) - end - - test "returns 404 when federation is disabled (nodeinfo 2.0)", %{conn: conn} do - instance = - Application.get_env(:pleroma, :instance) - |> Keyword.put(:federating, false) - - Application.put_env(:pleroma, :instance, instance) - - conn - |> get("/.well-known/nodeinfo") - |> json_response(404) - - conn - |> get("/nodeinfo/2.0.json") - |> json_response(404) - - instance = - Application.get_env(:pleroma, :instance) - |> Keyword.put(:federating, true) - - Application.put_env(:pleroma, :instance, instance) - end - - test "returns 200 when federation is enabled (nodeinfo 2.0)", %{conn: conn} do - conn - |> get("/.well-known/nodeinfo") - |> json_response(200) - - conn - |> get("/nodeinfo/2.0.json") - |> json_response(200) - end - test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do conn |> get("/.well-known/nodeinfo") From 9bec891eb4d5d06e6bd84dd2c95259d2c1a4f563 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 25 May 2019 04:24:21 +0000 Subject: [PATCH 39/48] kill @httpoison --- config/config.exs | 1 - lib/pleroma/object/fetcher.ex | 5 ++--- lib/pleroma/reverse_proxy.ex | 5 +++-- lib/pleroma/uploaders/mdii.ex | 5 ++--- lib/pleroma/web/activity_pub/publisher.ex | 5 ++--- .../web/mastodon_api/mastodon_api_controller.ex | 4 ++-- lib/pleroma/web/ostatus/ostatus.ex | 7 +++---- lib/pleroma/web/salmon/salmon.ex | 5 ++--- lib/pleroma/web/web_finger/web_finger.ex | 9 ++++----- lib/pleroma/web/websub/websub.ex | 11 +++++------ 10 files changed, 25 insertions(+), 32 deletions(-) diff --git a/config/config.exs b/config/config.exs index e90821d66..01827fb5f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -186,7 +186,6 @@ config :pleroma, :websub, Pleroma.Web.Websub config :pleroma, :ostatus, Pleroma.Web.OStatus -config :pleroma, :httpoison, Pleroma.HTTP config :tesla, adapter: Tesla.Adapter.Hackney # Configures http settings, upstream proxy etc. diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index bb9388d4f..ca980c629 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,4 +1,5 @@ defmodule Pleroma.Object.Fetcher do + alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Web.ActivityPub.Transmogrifier @@ -6,8 +7,6 @@ defmodule Pleroma.Object.Fetcher do require Logger - @httpoison Application.get_env(:pleroma, :httpoison) - defp reinject_object(data) do Logger.debug("Reinjecting object #{data["id"]}") @@ -78,7 +77,7 @@ def fetch_and_contain_remote_object_from_id(id) do with true <- String.starts_with?(id, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- - @httpoison.get( + HTTP.get( id, [{:Accept, "application/activity+json"}] ), diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index a3f177fec..6e5feb4c3 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.ReverseProxy do + alias Pleroma.HTTP + @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ ~w(if-unmodified-since if-none-match if-range range) @resp_cache_headers ~w(etag date last-modified cache-control) @@ -60,7 +62,6 @@ defmodule Pleroma.ReverseProxy do """ @hackney Application.get_env(:pleroma, :hackney, :hackney) - @httpoison Application.get_env(:pleroma, :httpoison, HTTPoison) @default_hackney_options [] @@ -97,7 +98,7 @@ def call(conn = %{method: method}, url, opts) when method in @methods do hackney_opts = @default_hackney_options |> Keyword.merge(Keyword.get(opts, :http, [])) - |> @httpoison.process_request_options() + |> HTTP.process_request_options() req_headers = build_req_headers(conn.req_headers, opts) diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 190ed9f3a..237544337 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -4,11 +4,10 @@ defmodule Pleroma.Uploaders.MDII do alias Pleroma.Config + alias Pleroma.HTTP @behaviour Pleroma.Uploaders.Uploader - @httpoison Application.get_env(:pleroma, :httpoison) - # MDII-hosted images are never passed through the MediaPlug; only local media. # Delegate to Pleroma.Uploaders.Local def get_file(file) do @@ -25,7 +24,7 @@ def put_file(upload) do query = "#{cgi}?#{extension}" with {:ok, %{status: 200, body: body}} <- - @httpoison.post(query, file_data, [], adapter: [pool: :default]) do + HTTP.post(query, file_data, [], adapter: [pool: :default]) do remote_file_name = String.split(body) |> List.first() public_url = "#{files}/#{remote_file_name}.#{extension}" {:ok, {:url, public_url}} diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 11dba87de..8f1399ce6 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay @@ -16,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do require Logger - @httpoison Application.get_env(:pleroma, :httpoison) - @moduledoc """ ActivityPub outgoing federation module. """ @@ -63,7 +62,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa with {:ok, %{status: code}} when code in 200..299 <- result = - @httpoison.post( + HTTP.post( inbox, json, [ diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 1ec0f30a1..0fe09c285 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Conversation.Participation alias Pleroma.Filter alias Pleroma.Formatter + alias Pleroma.HTTP alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Object.Fetcher @@ -55,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do when action in [:account_register] ) - @httpoison Application.get_env(:pleroma, :httpoison) @local_mastodon_name "Mastodon-Local" action_fallback(:errors) @@ -1691,7 +1691,7 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do |> String.replace("{{user}}", user) with {:ok, %{status: 200, body: body}} <- - @httpoison.get( + HTTP.get( url, [], adapter: [ diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 61515b31e..6ed089d84 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -3,13 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus do - @httpoison Application.get_env(:pleroma, :httpoison) - import Ecto.Query import Pleroma.Web.XML require Logger alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -363,7 +362,7 @@ def get_atom_url(body) do def fetch_activity_from_atom_url(url) do with true <- String.starts_with?(url, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- - @httpoison.get( + HTTP.get( url, [{:Accept, "application/atom+xml"}] ) do @@ -380,7 +379,7 @@ def fetch_activity_from_html_url(url) do Logger.debug("Trying to fetch #{url}") with true <- String.starts_with?(url, "http"), - {:ok, %{body: body}} <- @httpoison.get(url, []), + {:ok, %{body: body}} <- HTTP.get(url, []), {:ok, atom_url} <- get_atom_url(body) do fetch_activity_from_atom_url(atom_url) else diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index f25d92fad..9e91a5a40 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -5,11 +5,10 @@ defmodule Pleroma.Web.Salmon do @behaviour Pleroma.Web.Federator.Publisher - @httpoison Application.get_env(:pleroma, :httpoison) - use Bitwise alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Keys alias Pleroma.User @@ -138,7 +137,7 @@ def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params), def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do with {:ok, %{status: code}} when code in 200..299 <- - @httpoison.post( + HTTP.post( url, feed, [{"Content-Type", "application/magic-envelope+xml"}] diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index c5b7d4acb..3fca72de8 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -3,8 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger do - @httpoison Application.get_env(:pleroma, :httpoison) - + alias Pleroma.HTTP alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.Federator.Publisher @@ -176,11 +175,11 @@ def get_template_from_xml(body) do def find_lrdd_template(domain) do with {:ok, %{status: status, body: body}} when status in 200..299 <- - @httpoison.get("http://#{domain}/.well-known/host-meta", []) do + HTTP.get("http://#{domain}/.well-known/host-meta", []) do get_template_from_xml(body) else _ -> - with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do + with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do get_template_from_xml(body) else e -> {:error, "Can't find LRDD template: #{inspect(e)}"} @@ -209,7 +208,7 @@ def finger(account) do end with response <- - @httpoison.get( + HTTP.get( address, Accept: "application/xrd+xml,application/jrd+json" ), diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index 7ad0414ab..b61f388b8 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Websub do alias Ecto.Changeset alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Repo alias Pleroma.User @@ -24,9 +25,7 @@ defmodule Pleroma.Web.Websub do @behaviour Pleroma.Web.Federator.Publisher - @httpoison Application.get_env(:pleroma, :httpoison) - - def verify(subscription, getter \\ &@httpoison.get/3) do + def verify(subscription, getter \\ &HTTP.get/3) do challenge = Base.encode16(:crypto.strong_rand_bytes(8)) lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at) lease_seconds = lease_seconds |> to_string @@ -207,7 +206,7 @@ def subscribe(subscriber, subscribed, requester \\ &request_subscription/1) do requester.(subscription) end - def gather_feed_data(topic, getter \\ &@httpoison.get/1) do + def gather_feed_data(topic, getter \\ &HTTP.get/1) do with {:ok, response} <- getter.(topic), status when status in 200..299 <- response.status, body <- response.body, @@ -236,7 +235,7 @@ def gather_feed_data(topic, getter \\ &@httpoison.get/1) do end end - def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000) do + def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do data = [ "hub.mode": "subscribe", "hub.topic": websub.topic, @@ -294,7 +293,7 @@ def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret} = Logger.info(fn -> "Pushing #{topic} to #{callback}" end) with {:ok, %{status: code}} when code in 200..299 <- - @httpoison.post( + HTTP.post( callback, xml, [ From 80d55d428fedfe4ebc44569c6134908d269db698 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 25 May 2019 04:34:16 +0000 Subject: [PATCH 40/48] tests: websub: check only that signature validation succeeds or fails --- config/test.exs | 4 ++-- test/web/websub/websub_controller_test.exs | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/config/test.exs b/config/test.exs index 6100989c4..3bc407840 100644 --- a/config/test.exs +++ b/config/test.exs @@ -39,8 +39,8 @@ # Reduce hash rounds for testing config :pbkdf2_elixir, rounds: 1 -config :pleroma, :websub, Pleroma.Web.WebsubMock -config :pleroma, :ostatus, Pleroma.Web.OStatusMock +#config :pleroma, :websub, Pleroma.Web.WebsubMock +#config :pleroma, :ostatus, Pleroma.Web.OStatusMock config :tesla, adapter: Tesla.Mock config :pleroma, :rich_media, enabled: false diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index 1e69ed01a..bf2ee31ee 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -52,7 +52,7 @@ test "websub subscription confirmation", %{conn: conn} do end describe "websub_incoming" do - test "handles incoming feed updates", %{conn: conn} do + test "accepts incoming feed updates", %{conn: conn} do websub = insert(:websub_client_subscription) doc = "some stuff" signature = Websub.sign(websub.secret, doc) @@ -64,8 +64,6 @@ test "handles incoming feed updates", %{conn: conn} do |> post("/push/subscriptions/#{websub.id}", doc) assert response(conn, 200) == "OK" - - assert length(Repo.all(Activity)) == 1 end test "rejects incoming feed updates with the wrong signature", %{conn: conn} do @@ -80,8 +78,6 @@ test "rejects incoming feed updates with the wrong signature", %{conn: conn} do |> post("/push/subscriptions/#{websub.id}", doc) assert response(conn, 500) == "Error" - - assert Enum.empty?(Repo.all(Activity)) end end end From 56fd7dbdd7e3f1a0bb05430ed4805d8715e54935 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 25 May 2019 04:39:32 +0000 Subject: [PATCH 41/48] remove @websub and @ostatus module-level constants --- config/config.exs | 2 -- config/test.exs | 2 -- lib/pleroma/web/federator/federator.ex | 8 +++----- test/support/ostatus_mock.ex | 11 ----------- test/support/websub_mock.ex | 9 --------- test/web/websub/websub_controller_test.exs | 1 - 6 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 test/support/ostatus_mock.ex delete mode 100644 test/support/websub_mock.ex diff --git a/config/config.exs b/config/config.exs index 01827fb5f..68168b279 100644 --- a/config/config.exs +++ b/config/config.exs @@ -184,8 +184,6 @@ "application/ld+json" => ["activity+json"] } -config :pleroma, :websub, Pleroma.Web.Websub -config :pleroma, :ostatus, Pleroma.Web.OStatus config :tesla, adapter: Tesla.Adapter.Hackney # Configures http settings, upstream proxy etc. diff --git a/config/test.exs b/config/test.exs index 3bc407840..41cddb9bd 100644 --- a/config/test.exs +++ b/config/test.exs @@ -39,8 +39,6 @@ # Reduce hash rounds for testing config :pbkdf2_elixir, rounds: 1 -#config :pleroma, :websub, Pleroma.Web.WebsubMock -#config :pleroma, :ostatus, Pleroma.Web.OStatusMock config :tesla, adapter: Tesla.Mock config :pleroma, :rich_media, enabled: false diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 6b0b75284..f4c9fe284 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -11,13 +11,11 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.RetryQueue + alias Pleroma.Web.OStatus alias Pleroma.Web.Websub require Logger - @websub Application.get_env(:pleroma, :websub) - @ostatus Application.get_env(:pleroma, :ostatus) - def init do # 1 minute Process.sleep(1000 * 60) @@ -87,12 +85,12 @@ def perform(:verify_websub, websub) do "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" end) - @websub.verify(websub) + Websub.verify(websub) end def perform(:incoming_doc, doc) do Logger.info("Got document, trying to parse") - @ostatus.handle_incoming(doc) + OStatus.handle_incoming(doc) end def perform(:incoming_ap_doc, params) do diff --git a/test/support/ostatus_mock.ex b/test/support/ostatus_mock.ex deleted file mode 100644 index 9c0f2f323..000000000 --- a/test/support/ostatus_mock.ex +++ /dev/null @@ -1,11 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatusMock do - import Pleroma.Factory - - def handle_incoming(_doc) do - insert(:note_activity) - end -end diff --git a/test/support/websub_mock.ex b/test/support/websub_mock.ex deleted file mode 100644 index e3d5a5b16..000000000 --- a/test/support/websub_mock.ex +++ /dev/null @@ -1,9 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.WebsubMock do - def verify(sub) do - {:ok, sub} - end -end diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index bf2ee31ee..f79745d58 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -5,7 +5,6 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory - alias Pleroma.Activity alias Pleroma.Repo alias Pleroma.Web.Websub alias Pleroma.Web.Websub.WebsubClientSubscription From 79503ce90f6d85f00ee9e2dbc6358df2237d5036 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sun, 26 May 2019 01:57:22 +0000 Subject: [PATCH 42/48] mrf: simple policy: fix matching imported activitypub and ostatus statuses --- CHANGELOG.md | 1 + .../web/activity_pub/mrf/simple_policy.ex | 3 +-- .../activity_pub/mrf/simple_policy_test.exs | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7869e299b..f689160e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Exposing default scope of the user to anyone - Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`] - User-Agent is now sent correctly for all HTTP requests. +- MRF: Simple policy now properly delists imported or relayed statuses ## Removed - Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 890d70a7a..433d23c5f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -74,8 +74,7 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do actor_host ), user <- User.get_cached_by_ap_id(object["actor"]), - true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"], - true <- user.follower_address in object["cc"] do + true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do to = List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address] diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 3d1f26e60..0fd68e103 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -145,6 +145,24 @@ test "has a matching host" do assert SimplePolicy.filter(local_message) == {:ok, local_message} end + + test "has a matching host but only as:Public in to" do + {_actor, ftl_message} = build_ftl_actor_and_message() + + ftl_message_actor_host = + ftl_message + |> Map.fetch!("actor") + |> URI.parse() + |> Map.fetch!(:host) + + ftl_message = Map.put(ftl_message, "cc", []) + + Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host]) + + assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message) + refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"] + assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"] + end end defp build_ftl_actor_and_message do From 9f3bcf0efe7fa06e2b8970386c099c1ea2974d0a Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 28 May 2019 06:49:53 +0000 Subject: [PATCH 43/48] Respect proxy settings federation --- lib/pleroma/http/connection.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index 558005c19..c216cdcb1 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -32,9 +32,11 @@ def new(opts \\ []) do defp hackney_options(opts) do options = Keyword.get(opts, :adapter, []) adapter_options = Pleroma.Config.get([:http, :adapter], []) + proxy_url = Pleroma.Config.get([:http, :proxy_url], nil) @hackney_options |> Keyword.merge(adapter_options) |> Keyword.merge(options) + |> Keyword.merge(proxy: proxy_url) end end From abc15b6dcca39f62f043fcaf0292443a3dcb3d76 Mon Sep 17 00:00:00 2001 From: feld Date: Tue, 28 May 2019 21:20:24 +0000 Subject: [PATCH 44/48] Improve Varnish config. We set sane headers from the backend now. --- installation/pleroma.vcl | 70 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/installation/pleroma.vcl b/installation/pleroma.vcl index 92153d8ef..154747aa6 100644 --- a/installation/pleroma.vcl +++ b/installation/pleroma.vcl @@ -1,4 +1,4 @@ -vcl 4.0; +vcl 4.1; import std; backend default { @@ -35,24 +35,6 @@ sub vcl_recv { } return(purge); } - - # Pleroma MediaProxy - strip headers that will affect caching - if (req.url ~ "^/proxy/") { - unset req.http.Cookie; - unset req.http.Authorization; - unset req.http.Accept; - return (hash); - } - - # Strip headers that will affect caching from all other static content - # This also permits caching of individual toots and AP Activities - if ((req.url ~ "^/(media|static)/") || - (req.url ~ "(?i)\.(html|js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$")) - { - unset req.http.Cookie; - unset req.http.Authorization; - return (hash); - } } sub vcl_backend_response { @@ -61,6 +43,12 @@ sub vcl_backend_response { set beresp.do_gzip = true; } + # Retry broken backend responses. + if (beresp.status == 503) { + set bereq.http.X-Varnish-Backend-503 = "1"; + return (retry); + } + # CHUNKED SUPPORT if (bereq.http.x-range ~ "bytes=" && beresp.status == 206) { set beresp.ttl = 10m; @@ -73,8 +61,6 @@ sub vcl_backend_response { return (deliver); } - # Default object caching of 86400s; - set beresp.ttl = 86400s; # Allow serving cached content for 6h in case backend goes down set beresp.grace = 6h; @@ -90,20 +76,6 @@ sub vcl_backend_response { set beresp.ttl = 30s; return (deliver); } - - # Pleroma MediaProxy internally sets headers properly - if (bereq.url ~ "^/proxy/") { - return (deliver); - } - - # Strip cache-restricting headers from Pleroma on static content that we want to cache - if (bereq.url ~ "(?i)\.(js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$") - { - unset beresp.http.set-cookie; - unset beresp.http.Cache-Control; - unset beresp.http.x-request-id; - set beresp.http.Cache-Control = "public, max-age=86400"; - } } # The synthetic response for 301 redirects @@ -132,10 +104,32 @@ sub vcl_hash { } sub vcl_backend_fetch { + # Be more lenient for slow servers on the fediverse + if bereq.url ~ "^/proxy/" { + set bereq.first_byte_timeout = 300s; + } + # CHUNKED SUPPORT if (bereq.http.x-range) { set bereq.http.Range = bereq.http.x-range; } + + if (bereq.retries == 0) { + # Clean up the X-Varnish-Backend-503 flag that is used internally + # to mark broken backend responses that should be retried. + unset bereq.http.X-Varnish-Backend-503; + } else { + if (bereq.http.X-Varnish-Backend-503) { + if (bereq.method != "POST" && + std.healthy(bereq.backend) && + bereq.retries <= 4) { + # Flush broken backend response flag & try again. + unset bereq.http.X-Varnish-Backend-503; + } else { + return (abandon); + } + } + } } sub vcl_deliver { @@ -145,3 +139,9 @@ sub vcl_deliver { unset resp.http.CR; } } + +sub vcl_backend_error { + # Retry broken backend responses. + set bereq.http.X-Varnish-Backend-503 = "1"; + return (retry); +} From 0159a6dbe97330150d2913c7d7a060151f83f7eb Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 29 May 2019 10:58:45 +0000 Subject: [PATCH 45/48] router: require oauth_read for searching Search calls are generally expensive and allow unauthenticated users to crawl the instance for user profiles or posts which contain specified keywords. An adversary can build a distributed search engine which not only will consume significant instance resources, but also can be used for undesirable purposes such as datamining. Accordingly, require authenticated access to use the search API endpoints. This acts as a nice balance as it allows guest users to make use of most functionality available in Pleroma FE while ensuring that Pleroma instances are reasonably protected from resource exhaustion. It also removes Pleroma as a potential vector in distributed search engines. --- lib/pleroma/web/router.ex | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 352268b96..08c74a742 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -414,7 +414,12 @@ defmodule Pleroma.Web.Router do get("/trends", MastodonAPIController, :empty_array) - get("/accounts/search", MastodonAPIController, :account_search) + scope [] do + pipe_through(:oauth_read) + + get("/search", MastodonAPIController, :search) + get("/accounts/search", MastodonAPIController, :account_search) + end scope [] do pipe_through(:oauth_read_or_public) @@ -431,14 +436,12 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/following", MastodonAPIController, :following) get("/accounts/:id", MastodonAPIController, :user) - get("/search", MastodonAPIController, :search) - get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites) end end scope "/api/v2", Pleroma.Web.MastodonAPI do - pipe_through([:api, :oauth_read_or_public]) + pipe_through([:api, :oauth_read]) get("/search", MastodonAPIController, :search2) end @@ -480,9 +483,14 @@ defmodule Pleroma.Web.Router do get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - get("/search", TwitterAPI.Controller, :search) get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) end + + scope [] do + pipe_through(:oauth_read) + + get("/search", TwitterAPI.Controller, :search) + end end scope "/api", Pleroma.Web do @@ -500,7 +508,7 @@ defmodule Pleroma.Web.Router do end scope "/api", Pleroma.Web, as: :twitter_api_search do - pipe_through([:api, :oauth_read_or_public]) + pipe_through([:api, :oauth_read]) get("/pleroma/search_user", TwitterAPI.Controller, :search_user) end From 672fddb7213b69f3ee8b9a7728426373090847ed Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 29 May 2019 08:06:26 -0500 Subject: [PATCH 46/48] Default search limit should be 40 https://docs.joinmastodon.org/api/rest/search/ --- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 0fe09c285..2110027c3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1084,7 +1084,7 @@ def status_search(user, query) do from([a, o] in Activity.with_preloaded_object(Activity), where: fragment("?->>'type' = 'Create'", a.data), where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, - limit: 20 + limit: 40 ) q = From db94294dfb6f25ce0f08acfb78b3c4cc2241e9f5 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 29 May 2019 14:04:58 +0000 Subject: [PATCH 47/48] [#936] fix tests --- .../admin_api/admin_api_controller_test.exs | 176 ++++++++++-------- 1 file changed, 96 insertions(+), 80 deletions(-) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index c15c67e31..43dcf945a 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -437,27 +437,31 @@ test "renders users array for the first page", %{conn: conn, admin: admin} do user = insert(:user, local: false, tags: ["foo", "bar"]) conn = get(conn, "/api/pleroma/admin/users?page=1") + users = + [ + %{ + "deactivated" => admin.info.deactivated, + "id" => admin.id, + "nickname" => admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "local" => true, + "tags" => [] + }, + %{ + "deactivated" => user.info.deactivated, + "id" => user.id, + "nickname" => user.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => false, + "tags" => ["foo", "bar"] + } + ] + |> Enum.sort_by(& &1["nickname"]) + assert json_response(conn, 200) == %{ "count" => 2, "page_size" => 50, - "users" => [ - %{ - "deactivated" => admin.info.deactivated, - "id" => admin.id, - "nickname" => admin.nickname, - "roles" => %{"admin" => true, "moderator" => false}, - "local" => true, - "tags" => [] - }, - %{ - "deactivated" => user.info.deactivated, - "id" => user.id, - "nickname" => user.nickname, - "roles" => %{"admin" => false, "moderator" => false}, - "local" => false, - "tags" => ["foo", "bar"] - } - ] + "users" => users } end @@ -659,35 +663,39 @@ test "only local users with no query", %{admin: old_admin} do |> assign(:user, admin) |> get("/api/pleroma/admin/users?filters=local") + users = + [ + %{ + "deactivated" => user.info.deactivated, + "id" => user.id, + "nickname" => user.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => true, + "tags" => [] + }, + %{ + "deactivated" => admin.info.deactivated, + "id" => admin.id, + "nickname" => admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "local" => true, + "tags" => [] + }, + %{ + "deactivated" => false, + "id" => old_admin.id, + "local" => true, + "nickname" => old_admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "tags" => [] + } + ] + |> Enum.sort_by(& &1["nickname"]) + assert json_response(conn, 200) == %{ "count" => 3, "page_size" => 50, - "users" => [ - %{ - "deactivated" => user.info.deactivated, - "id" => user.id, - "nickname" => user.nickname, - "roles" => %{"admin" => false, "moderator" => false}, - "local" => true, - "tags" => [] - }, - %{ - "deactivated" => admin.info.deactivated, - "id" => admin.id, - "nickname" => admin.nickname, - "roles" => %{"admin" => true, "moderator" => false}, - "local" => true, - "tags" => [] - }, - %{ - "deactivated" => false, - "id" => old_admin.id, - "local" => true, - "nickname" => old_admin.nickname, - "roles" => %{"admin" => true, "moderator" => false}, - "tags" => [] - } - ] + "users" => users } end @@ -698,27 +706,31 @@ test "load only admins", %{conn: conn, admin: admin} do conn = get(conn, "/api/pleroma/admin/users?filters=is_admin") + users = + [ + %{ + "deactivated" => false, + "id" => admin.id, + "nickname" => admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "local" => admin.local, + "tags" => [] + }, + %{ + "deactivated" => false, + "id" => second_admin.id, + "nickname" => second_admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "local" => second_admin.local, + "tags" => [] + } + ] + |> Enum.sort_by(& &1["nickname"]) + assert json_response(conn, 200) == %{ "count" => 2, "page_size" => 50, - "users" => [ - %{ - "deactivated" => false, - "id" => admin.id, - "nickname" => admin.nickname, - "roles" => %{"admin" => true, "moderator" => false}, - "local" => admin.local, - "tags" => [] - }, - %{ - "deactivated" => false, - "id" => second_admin.id, - "nickname" => second_admin.nickname, - "roles" => %{"admin" => true, "moderator" => false}, - "local" => second_admin.local, - "tags" => [] - } - ] + "users" => users } end @@ -753,27 +765,31 @@ test "load users with tags list", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second") + users = + [ + %{ + "deactivated" => false, + "id" => user1.id, + "nickname" => user1.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => user1.local, + "tags" => ["first"] + }, + %{ + "deactivated" => false, + "id" => user2.id, + "nickname" => user2.nickname, + "roles" => %{"admin" => false, "moderator" => false}, + "local" => user2.local, + "tags" => ["second"] + } + ] + |> Enum.sort_by(& &1["nickname"]) + assert json_response(conn, 200) == %{ "count" => 2, "page_size" => 50, - "users" => [ - %{ - "deactivated" => false, - "id" => user1.id, - "nickname" => user1.nickname, - "roles" => %{"admin" => false, "moderator" => false}, - "local" => user1.local, - "tags" => ["first"] - }, - %{ - "deactivated" => false, - "id" => user2.id, - "nickname" => user2.nickname, - "roles" => %{"admin" => false, "moderator" => false}, - "local" => user2.local, - "tags" => ["second"] - } - ] + "users" => users } end From 6aec0d1b58c12db2fa669fcff043338174290a35 Mon Sep 17 00:00:00 2001 From: kaniini Date: Wed, 29 May 2019 22:10:16 +0000 Subject: [PATCH 48/48] Revert "Merge branch 'feature/search-authenticated-only' into 'develop'" This reverts merge request !1209 --- lib/pleroma/web/router.ex | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 08c74a742..352268b96 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -414,12 +414,7 @@ defmodule Pleroma.Web.Router do get("/trends", MastodonAPIController, :empty_array) - scope [] do - pipe_through(:oauth_read) - - get("/search", MastodonAPIController, :search) - get("/accounts/search", MastodonAPIController, :account_search) - end + get("/accounts/search", MastodonAPIController, :account_search) scope [] do pipe_through(:oauth_read_or_public) @@ -436,12 +431,14 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/following", MastodonAPIController, :following) get("/accounts/:id", MastodonAPIController, :user) + get("/search", MastodonAPIController, :search) + get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites) end end scope "/api/v2", Pleroma.Web.MastodonAPI do - pipe_through([:api, :oauth_read]) + pipe_through([:api, :oauth_read_or_public]) get("/search", MastodonAPIController, :search2) end @@ -483,13 +480,8 @@ defmodule Pleroma.Web.Router do get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) - end - - scope [] do - pipe_through(:oauth_read) - get("/search", TwitterAPI.Controller, :search) + get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) end end @@ -508,7 +500,7 @@ defmodule Pleroma.Web.Router do end scope "/api", Pleroma.Web, as: :twitter_api_search do - pipe_through([:api, :oauth_read]) + pipe_through([:api, :oauth_read_or_public]) get("/pleroma/search_user", TwitterAPI.Controller, :search_user) end