From bbdad8556861c60ae1f526f63de9c5857c4ad547 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 8 May 2020 23:06:47 +0300 Subject: Initial implementation of image preview proxy. Media proxy tests refactoring. --- lib/pleroma/helpers/mogrify_helper.ex | 25 +++++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 3 +- lib/pleroma/web/media_proxy/media_proxy.ex | 53 +++++++++++++-- .../web/media_proxy/media_proxy_controller.ex | 76 ++++++++++++++++++---- lib/pleroma/web/router.ex | 2 + 5 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 lib/pleroma/helpers/mogrify_helper.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/mogrify_helper.ex b/lib/pleroma/helpers/mogrify_helper.ex new file mode 100644 index 000000000..67edb35c3 --- /dev/null +++ b/lib/pleroma/helpers/mogrify_helper.ex @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.MogrifyHelper do + @moduledoc """ + Handles common Mogrify operations. + """ + + @spec store_as_temporary_file(String.t(), binary()) :: {:ok, String.t()} | {:error, atom()} + @doc "Stores binary content fetched from specified URL as a temporary file." + def store_as_temporary_file(url, body) do + path = Mogrify.temporary_path_for(%{path: url}) + with :ok <- File.write(path, body), do: {:ok, path} + end + + @spec store_as_temporary_file(String.t(), String.t()) :: Mogrify.Image.t() | any() + @doc "Modifies file at specified path by resizing to specified limit dimensions." + def in_place_resize_to_limit(path, resize_dimensions) do + path + |> Mogrify.open() + |> Mogrify.resize_to_limit(resize_dimensions) + |> Mogrify.save(in_place: true) + end +end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 24167f66f..2a206f743 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -419,6 +419,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do [attachment_url | _] = attachment["url"] media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" href = attachment_url["href"] |> MediaProxy.url() + href_preview = attachment_url["href"] |> MediaProxy.preview_url() type = cond do @@ -434,7 +435,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do id: to_string(attachment["id"] || hash_id), url: href, remote_url: href, - preview_url: href, + preview_url: href_preview, text_url: href, type: type, description: attachment["name"], diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index b2b524524..f4791c758 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -20,6 +20,14 @@ defmodule Pleroma.Web.MediaProxy do end end + def preview_url(url) do + if disabled?() or whitelisted?(url) do + url + else + encode_preview_url(url) + end + end + defp disabled?, do: !Config.get([:media_proxy, :enabled], false) defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) @@ -43,17 +51,29 @@ defmodule Pleroma.Web.MediaProxy do end) end - def encode_url(url) do + defp base64_sig64(url) do base64 = Base.url_encode64(url, @base64_opts) sig64 = base64 - |> signed_url + |> signed_url() |> Base.url_encode64(@base64_opts) + {base64, sig64} + end + + def encode_url(url) do + {base64, sig64} = base64_sig64(url) + build_url(sig64, base64, filename(url)) end + def encode_preview_url(url) do + {base64, sig64} = base64_sig64(url) + + build_preview_url(sig64, base64, filename(url)) + end + def decode_url(sig, url) do with {:ok, sig} <- Base.url_decode64(sig, @base64_opts), signature when signature == sig <- signed_url(url) do @@ -71,10 +91,10 @@ defmodule Pleroma.Web.MediaProxy do if path = URI.parse(url_or_path).path, do: Path.basename(path) end - def build_url(sig_base64, url_base64, filename \\ nil) do + defp proxy_url(path, sig_base64, url_base64, filename) do [ Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()), - "proxy", + path, sig_base64, url_base64, filename @@ -82,4 +102,29 @@ defmodule Pleroma.Web.MediaProxy do |> Enum.filter(& &1) |> Path.join() end + + def build_url(sig_base64, url_base64, filename \\ nil) do + proxy_url("proxy", sig_base64, url_base64, filename) + end + + def build_preview_url(sig_base64, url_base64, filename \\ nil) do + proxy_url("proxy/preview", sig_base64, url_base64, filename) + end + + def filename_matches(%{"filename" => _} = _, path, url) do + filename = filename(url) + + if filename && not basename_matches?(path, filename) do + {:wrong_filename, filename} + else + :ok + end + end + + def filename_matches(_, _, _), do: :ok + + defp basename_matches?(path, filename) do + basename = Path.basename(path) + basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename + end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 4657a4383..fe3f61c18 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -5,19 +5,21 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller + alias Pleroma.Config + alias Pleroma.Helpers.MogrifyHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] def remote(conn, %{"sig" => sig64, "url" => url64} = params) do - with config <- Pleroma.Config.get([:media_proxy], []), - true <- Keyword.get(config, :enabled, false), + with config <- Config.get([:media_proxy], []), + {_, true} <- {:enabled, Keyword.get(config, :enabled, false)}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), - :ok <- filename_matches(params, conn.request_path, url) do + :ok <- MediaProxy.filename_matches(params, conn.request_path, url) do ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts)) else - false -> + {:enabled, false} -> send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> @@ -28,20 +30,68 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - def filename_matches(%{"filename" => _} = _, path, url) do - filename = MediaProxy.filename(url) + def preview(conn, %{"sig" => sig64, "url" => url64} = params) do + with {_, true} <- {:enabled, Config.get([:media_preview_proxy, :enabled], false)}, + {:ok, url} <- MediaProxy.decode_url(sig64, url64), + :ok <- MediaProxy.filename_matches(params, conn.request_path, url) do + handle_preview(conn, url) + else + {:enabled, false} -> + send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + + {:error, :invalid_signature} -> + send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403)) + + {:wrong_filename, filename} -> + redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename)) + end + end - if filename && does_not_match(path, filename) do - {:wrong_filename, filename} + defp handle_preview(conn, url) do + with {:ok, %{status: status} = head_response} when status in 200..299 <- Tesla.head(url), + {_, true} <- {:acceptable_content_length, acceptable_body_length?(head_response)} do + content_type = Tesla.get_header(head_response, "content-type") + handle_preview(content_type, conn, url) else - :ok + {_, %{status: status}} -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") + + {:acceptable_content_length, false} -> + send_resp(conn, :unprocessable_entity, "Source file size exceeds limit.") end end - def filename_matches(_, _, _), do: :ok + defp handle_preview("image/" <> _, %{params: params} = conn, url) do + with {:ok, %{status: status, body: body}} when status in 200..299 <- Tesla.get(url), + {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body), + resize_dimensions <- + Map.get( + params, + "limit_dimensions", + Config.get([:media_preview_proxy, :limit_dimensions]) + ), + %Mogrify.Image{} <- MogrifyHelper.in_place_resize_to_limit(path, resize_dimensions) do + send_file(conn, 200, path) + else + {_, %{status: _}} -> + send_resp(conn, :failed_dependency, "Can't fetch the image.") + + _ -> + send_resp(conn, :failed_dependency, "Can't handle image preview.") + end + end + + defp handle_preview(content_type, conn, _url) do + send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") + end + + defp acceptable_body_length?(head_response) do + max_body_length = Config.get([:media_preview_proxy, :max_body_length], nil) + content_length = Tesla.get_header(head_response, "content-length") + content_length = with {int, _} <- Integer.parse(content_length), do: int - defp does_not_match(path, filename) do - basename = Path.basename(path) - basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename + content_length == :error or + max_body_length in [nil, :infinity] or + content_length <= max_body_length end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7a171f9fb..6fb47029a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -663,6 +663,8 @@ defmodule Pleroma.Web.Router do end scope "/proxy/", Pleroma.Web.MediaProxy do + get("/preview/:sig/:url", MediaProxyController, :preview) + get("/preview/:sig/:url/:filename", MediaProxyController, :preview) get("/:sig/:url", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote) end -- cgit v1.2.3 From 1b23acf164ebc4fde3fe1e4fdca6e11b7caa90ef Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 11 May 2020 23:21:53 +0300 Subject: [#2497] Media preview proxy for images: fixes, tweaks, refactoring, tests adjustments. --- lib/pleroma/reverse_proxy/reverse_proxy.ex | 4 ++ lib/pleroma/web/media_proxy/media_proxy.ex | 33 ++++++++---- .../web/media_proxy/media_proxy_controller.ex | 62 +++++++++++++--------- 3 files changed, 64 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 4bbeb493c..aeaf9bd39 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -16,6 +16,8 @@ defmodule Pleroma.ReverseProxy do @failed_request_ttl :timer.seconds(60) @methods ~w(GET HEAD) + def max_read_duration_default, do: @max_read_duration + @moduledoc """ A reverse proxy. @@ -370,6 +372,8 @@ defmodule Pleroma.ReverseProxy do defp body_size_constraint(_, _), do: :ok + defp check_read_duration(nil = _duration, max), do: check_read_duration(@max_read_duration, max) + defp check_read_duration(duration, max) when is_integer(duration) and is_integer(max) and max > 0 do if duration > max do diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index f4791c758..4e01c14e4 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -13,26 +13,32 @@ defmodule Pleroma.Web.MediaProxy do def url("/" <> _ = url), do: url def url(url) do - if disabled?() or local?(url) or whitelisted?(url) do + if not enabled?() or local?(url) or whitelisted?(url) do url else encode_url(url) end end + # Note: routing all URLs to preview handler (even local and whitelisted). + # Preview handler will call url/1 on decoded URLs, and applicable ones will detour media proxy. def preview_url(url) do - if disabled?() or whitelisted?(url) do - url - else + if preview_enabled?() do encode_preview_url(url) + else + url end end - defp disabled?, do: !Config.get([:media_proxy, :enabled], false) + def enabled?, do: Config.get([:media_proxy, :enabled], false) - defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + # Note: media proxy must be enabled for media preview proxy in order to load all + # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved. + def preview_enabled?, do: enabled?() and Config.get([:media_preview_proxy, :enabled], false) - defp whitelisted?(url) do + def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + + def whitelisted?(url) do %{host: domain} = URI.parse(url) mediaproxy_whitelist = Config.get([:media_proxy, :whitelist]) @@ -111,17 +117,24 @@ defmodule Pleroma.Web.MediaProxy do proxy_url("proxy/preview", sig_base64, url_base64, filename) end - def filename_matches(%{"filename" => _} = _, path, url) do + def verify_request_path_and_url( + %Plug.Conn{params: %{"filename" => _}, request_path: request_path}, + url + ) do + verify_request_path_and_url(request_path, url) + end + + def verify_request_path_and_url(request_path, url) when is_binary(request_path) do filename = filename(url) - if filename && not basename_matches?(path, filename) do + if filename && not basename_matches?(request_path, filename) do {:wrong_filename, filename} else :ok end end - def filename_matches(_, _, _), do: :ok + def verify_request_path_and_url(_, _), do: :ok defp basename_matches?(path, filename) do basename = Path.basename(path) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index fe3f61c18..157365e08 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -10,14 +10,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy - @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] - - def remote(conn, %{"sig" => sig64, "url" => url64} = params) do - with config <- Config.get([:media_proxy], []), - {_, true} <- {:enabled, Keyword.get(config, :enabled, false)}, + def remote(conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), - :ok <- MediaProxy.filename_matches(params, conn.request_path, url) do - ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts)) + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + proxy_opts = Config.get([:media_proxy, :proxy_opts], []) + ReverseProxy.call(conn, url, proxy_opts) else {:enabled, false} -> send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) @@ -30,10 +28,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - def preview(conn, %{"sig" => sig64, "url" => url64} = params) do - with {_, true} <- {:enabled, Config.get([:media_preview_proxy, :enabled], false)}, + def preview(conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), - :ok <- MediaProxy.filename_matches(params, conn.request_path, url) do + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do handle_preview(conn, url) else {:enabled, false} -> @@ -48,21 +46,27 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_preview(conn, url) do - with {:ok, %{status: status} = head_response} when status in 200..299 <- Tesla.head(url), - {_, true} <- {:acceptable_content_length, acceptable_body_length?(head_response)} do + with {:ok, %{status: status} = head_response} when status in 200..299 <- + Tesla.head(url, opts: [adapter: [timeout: preview_head_request_timeout()]]) do content_type = Tesla.get_header(head_response, "content-type") handle_preview(content_type, conn, url) else {_, %{status: status}} -> send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") - {:acceptable_content_length, false} -> - send_resp(conn, :unprocessable_entity, "Source file size exceeds limit.") + {:error, :recv_response_timeout} -> + send_resp(conn, :failed_dependency, "HEAD request timeout.") + + _ -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers.") end end - defp handle_preview("image/" <> _, %{params: params} = conn, url) do - with {:ok, %{status: status, body: body}} when status in 200..299 <- Tesla.get(url), + defp handle_preview("image/" <> _ = content_type, %{params: params} = conn, url) do + with {:ok, %{status: status, body: body}} when status in 200..299 <- + url + |> MediaProxy.url() + |> Tesla.get(opts: [adapter: [timeout: preview_timeout()]]), {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body), resize_dimensions <- Map.get( @@ -70,12 +74,19 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do "limit_dimensions", Config.get([:media_preview_proxy, :limit_dimensions]) ), - %Mogrify.Image{} <- MogrifyHelper.in_place_resize_to_limit(path, resize_dimensions) do - send_file(conn, 200, path) + %Mogrify.Image{} <- MogrifyHelper.in_place_resize_to_limit(path, resize_dimensions), + {:ok, image_binary} <- File.read(path), + _ <- File.rm(path) do + conn + |> put_resp_header("content-type", content_type) + |> send_resp(200, image_binary) else {_, %{status: _}} -> send_resp(conn, :failed_dependency, "Can't fetch the image.") + {:error, :recv_response_timeout} -> + send_resp(conn, :failed_dependency, "Downstream timeout.") + _ -> send_resp(conn, :failed_dependency, "Can't handle image preview.") end @@ -85,13 +96,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp acceptable_body_length?(head_response) do - max_body_length = Config.get([:media_preview_proxy, :max_body_length], nil) - content_length = Tesla.get_header(head_response, "content-length") - content_length = with {int, _} <- Integer.parse(content_length), do: int + defp preview_head_request_timeout do + Config.get([:media_preview_proxy, :proxy_opts, :head_request_max_read_duration]) || + preview_timeout() + end - content_length == :error or - max_body_length in [nil, :infinity] or - content_length <= max_body_length + defp preview_timeout do + Config.get([:media_preview_proxy, :proxy_opts, :max_read_duration]) || + Config.get([:media_proxy, :proxy_opts, :max_read_duration]) || + ReverseProxy.max_read_duration_default() end end -- cgit v1.2.3 From f1f588fd5271c0b3bf09df002a83dbb57c42bae0 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 14 May 2020 20:18:31 +0300 Subject: [#2497] Added support for :eimp for image resizing. --- .../web/media_proxy/media_proxy_controller.ex | 64 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 157365e08..8d8d073e9 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -62,24 +62,64 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + defp thumbnail_max_dimensions(params) do + config = Config.get([:media_preview_proxy], []) + + thumbnail_max_width = + if w = params["thumbnail_max_width"] do + String.to_integer(w) + else + Keyword.fetch!(config, :thumbnail_max_width) + end + + thumbnail_max_height = + if h = params["thumbnail_max_height"] do + String.to_integer(h) + else + Keyword.fetch!(config, :thumbnail_max_height) + end + + {thumbnail_max_width, thumbnail_max_height} + end + + defp thumbnail_binary(url, body, params) do + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions(params) + + with true <- Config.get([:media_preview_proxy, :enable_eimp]), + {:ok, [type: image_type, width: source_width, height: source_height]} <- + :eimp.identify(body), + scale_factor <- + Enum.max([source_width / thumbnail_max_width, source_height / thumbnail_max_height]), + {:ok, thumbnail_binary} = + :eimp.convert(body, image_type, [ + {:scale, {round(source_width / scale_factor), round(source_height / scale_factor)}} + ]) do + {:ok, thumbnail_binary} + else + _ -> + mogrify_dimensions = "#{thumbnail_max_width}x#{thumbnail_max_height}" + + with {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body), + %Mogrify.Image{} <- + MogrifyHelper.in_place_resize_to_limit(path, mogrify_dimensions), + {:ok, thumbnail_binary} <- File.read(path), + _ <- File.rm(path) do + {:ok, thumbnail_binary} + else + _ -> :error + end + end + end + defp handle_preview("image/" <> _ = content_type, %{params: params} = conn, url) do - with {:ok, %{status: status, body: body}} when status in 200..299 <- + with {:ok, %{status: status, body: image_contents}} when status in 200..299 <- url |> MediaProxy.url() |> Tesla.get(opts: [adapter: [timeout: preview_timeout()]]), - {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body), - resize_dimensions <- - Map.get( - params, - "limit_dimensions", - Config.get([:media_preview_proxy, :limit_dimensions]) - ), - %Mogrify.Image{} <- MogrifyHelper.in_place_resize_to_limit(path, resize_dimensions), - {:ok, image_binary} <- File.read(path), - _ <- File.rm(path) do + {:ok, thumbnail_binary} <- thumbnail_binary(url, image_contents, params) do conn |> put_resp_header("content-type", content_type) - |> send_resp(200, image_binary) + |> send_resp(200, thumbnail_binary) else {_, %{status: _}} -> send_resp(conn, :failed_dependency, "Can't fetch the image.") -- cgit v1.2.3 From 1871a5ddb4a803ebe4fae6943a9b9c94f1f9c1a8 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 20 May 2020 20:26:43 +0300 Subject: [#2497] Image preview proxy: implemented ffmpeg-based resizing, removed eimp & mogrify-based resizing. --- lib/pleroma/helpers/media_helper.ex | 62 ++++++++++++++++++++++ lib/pleroma/helpers/mogrify_helper.ex | 25 --------- .../web/media_proxy/media_proxy_controller.ex | 50 ++++------------- 3 files changed, 71 insertions(+), 66 deletions(-) create mode 100644 lib/pleroma/helpers/media_helper.ex delete mode 100644 lib/pleroma/helpers/mogrify_helper.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex new file mode 100644 index 000000000..6d1f8ab22 --- /dev/null +++ b/lib/pleroma/helpers/media_helper.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.MediaHelper do + @moduledoc """ + Handles common media-related operations. + """ + + @ffmpeg_opts [{:sync, true}, {:stdout, true}] + + def ffmpeg_resize_remote(uri, max_width, max_height) do + cmd = ~s""" + curl -L "#{uri}" | + ffmpeg -i pipe:0 -vf \ + "scale='min(#{max_width},iw)':min'(#{max_height},ih)':force_original_aspect_ratio=decrease" \ + -f image2 pipe:1 | \ + cat + """ + + with {:ok, [stdout: stdout_list]} <- Exexec.run(cmd, @ffmpeg_opts) do + {:ok, Enum.join(stdout_list)} + end + end + + @doc "Returns a temporary path for an URI" + def temporary_path_for(uri) do + name = Path.basename(uri) + random = rand_uniform(999_999) + Path.join(System.tmp_dir(), "#{random}-#{name}") + end + + @doc "Stores binary content fetched from specified URL as a temporary file." + @spec store_as_temporary_file(String.t(), binary()) :: {:ok, String.t()} | {:error, atom()} + def store_as_temporary_file(url, body) do + path = temporary_path_for(url) + with :ok <- File.write(path, body), do: {:ok, path} + end + + @doc "Modifies image file at specified path by resizing to specified limit dimensions." + @spec mogrify_resize_to_limit(String.t(), String.t()) :: :ok | any() + def mogrify_resize_to_limit(path, resize_dimensions) do + with %Mogrify.Image{} <- + path + |> Mogrify.open() + |> Mogrify.resize_to_limit(resize_dimensions) + |> Mogrify.save(in_place: true) do + :ok + end + end + + defp rand_uniform(high) do + Code.ensure_loaded(:rand) + + if function_exported?(:rand, :uniform, 1) do + :rand.uniform(high) + else + # Erlang/OTP < 19 + apply(:crypto, :rand_uniform, [1, high]) + end + end +end diff --git a/lib/pleroma/helpers/mogrify_helper.ex b/lib/pleroma/helpers/mogrify_helper.ex deleted file mode 100644 index 67edb35c3..000000000 --- a/lib/pleroma/helpers/mogrify_helper.ex +++ /dev/null @@ -1,25 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Helpers.MogrifyHelper do - @moduledoc """ - Handles common Mogrify operations. - """ - - @spec store_as_temporary_file(String.t(), binary()) :: {:ok, String.t()} | {:error, atom()} - @doc "Stores binary content fetched from specified URL as a temporary file." - def store_as_temporary_file(url, body) do - path = Mogrify.temporary_path_for(%{path: url}) - with :ok <- File.write(path, body), do: {:ok, path} - end - - @spec store_as_temporary_file(String.t(), String.t()) :: Mogrify.Image.t() | any() - @doc "Modifies file at specified path by resizing to specified limit dimensions." - def in_place_resize_to_limit(path, resize_dimensions) do - path - |> Mogrify.open() - |> Mogrify.resize_to_limit(resize_dimensions) - |> Mogrify.save(in_place: true) - end -end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 8d8d073e9..fb4b80379 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller alias Pleroma.Config - alias Pleroma.Helpers.MogrifyHelper + alias Pleroma.Helpers.MediaHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy @@ -82,51 +82,19 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do {thumbnail_max_width, thumbnail_max_height} end - defp thumbnail_binary(url, body, params) do - {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions(params) - - with true <- Config.get([:media_preview_proxy, :enable_eimp]), - {:ok, [type: image_type, width: source_width, height: source_height]} <- - :eimp.identify(body), - scale_factor <- - Enum.max([source_width / thumbnail_max_width, source_height / thumbnail_max_height]), - {:ok, thumbnail_binary} = - :eimp.convert(body, image_type, [ - {:scale, {round(source_width / scale_factor), round(source_height / scale_factor)}} - ]) do - {:ok, thumbnail_binary} - else - _ -> - mogrify_dimensions = "#{thumbnail_max_width}x#{thumbnail_max_height}" - - with {:ok, path} <- MogrifyHelper.store_as_temporary_file(url, body), - %Mogrify.Image{} <- - MogrifyHelper.in_place_resize_to_limit(path, mogrify_dimensions), - {:ok, thumbnail_binary} <- File.read(path), - _ <- File.rm(path) do - {:ok, thumbnail_binary} - else - _ -> :error - end - end - end - defp handle_preview("image/" <> _ = content_type, %{params: params} = conn, url) do - with {:ok, %{status: status, body: image_contents}} when status in 200..299 <- - url - |> MediaProxy.url() - |> Tesla.get(opts: [adapter: [timeout: preview_timeout()]]), - {:ok, thumbnail_binary} <- thumbnail_binary(url, image_contents, params) do + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + media_proxy_url <- MediaProxy.url(url), + {:ok, thumbnail_binary} <- + MediaHelper.ffmpeg_resize_remote( + media_proxy_url, + thumbnail_max_width, + thumbnail_max_height + ) do conn |> put_resp_header("content-type", content_type) |> send_resp(200, thumbnail_binary) else - {_, %{status: _}} -> - send_resp(conn, :failed_dependency, "Can't fetch the image.") - - {:error, :recv_response_timeout} -> - send_resp(conn, :failed_dependency, "Downstream timeout.") - _ -> send_resp(conn, :failed_dependency, "Can't handle image preview.") end -- cgit v1.2.3 From 610343edb318654126d9539775ba4b9ff30c8831 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 21 May 2020 17:35:42 +0300 Subject: [#2497] Image preview proxy: image resize & background color fix with ffmpeg -filter_complex. --- lib/pleroma/helpers/media_helper.ex | 47 +++------------------- .../web/media_proxy/media_proxy_controller.ex | 7 ++-- 2 files changed, 9 insertions(+), 45 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 6d1f8ab22..ee6b76c41 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -9,12 +9,14 @@ defmodule Pleroma.Helpers.MediaHelper do @ffmpeg_opts [{:sync, true}, {:stdout, true}] - def ffmpeg_resize_remote(uri, max_width, max_height) do + def ffmpeg_resize_remote(uri, %{max_width: max_width, max_height: max_height}) do cmd = ~s""" curl -L "#{uri}" | - ffmpeg -i pipe:0 -vf \ - "scale='min(#{max_width},iw)':min'(#{max_height},ih)':force_original_aspect_ratio=decrease" \ - -f image2 pipe:1 | \ + ffmpeg -i pipe:0 -f lavfi -i color=c=white \ + -filter_complex "[0:v] scale='min(#{max_width},iw)':'min(#{max_height},ih)': \ + force_original_aspect_ratio=decrease [scaled]; \ + [1][scaled] scale2ref [bg][img]; [bg] setsar=1 [bg]; [bg][img] overlay=shortest=1" \ + -f image2 -vcodec mjpeg -frames:v 1 pipe:1 | \ cat """ @@ -22,41 +24,4 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, Enum.join(stdout_list)} end end - - @doc "Returns a temporary path for an URI" - def temporary_path_for(uri) do - name = Path.basename(uri) - random = rand_uniform(999_999) - Path.join(System.tmp_dir(), "#{random}-#{name}") - end - - @doc "Stores binary content fetched from specified URL as a temporary file." - @spec store_as_temporary_file(String.t(), binary()) :: {:ok, String.t()} | {:error, atom()} - def store_as_temporary_file(url, body) do - path = temporary_path_for(url) - with :ok <- File.write(path, body), do: {:ok, path} - end - - @doc "Modifies image file at specified path by resizing to specified limit dimensions." - @spec mogrify_resize_to_limit(String.t(), String.t()) :: :ok | any() - def mogrify_resize_to_limit(path, resize_dimensions) do - with %Mogrify.Image{} <- - path - |> Mogrify.open() - |> Mogrify.resize_to_limit(resize_dimensions) - |> Mogrify.save(in_place: true) do - :ok - end - end - - defp rand_uniform(high) do - Code.ensure_loaded(:rand) - - if function_exported?(:rand, :uniform, 1) do - :rand.uniform(high) - else - # Erlang/OTP < 19 - apply(:crypto, :rand_uniform, [1, high]) - end - end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index fb4b80379..12d4401fa 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -82,17 +82,16 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do {thumbnail_max_width, thumbnail_max_height} end - defp handle_preview("image/" <> _ = content_type, %{params: params} = conn, url) do + defp handle_preview("image/" <> _ = _content_type, %{params: params} = conn, url) do with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), media_proxy_url <- MediaProxy.url(url), {:ok, thumbnail_binary} <- MediaHelper.ffmpeg_resize_remote( media_proxy_url, - thumbnail_max_width, - thumbnail_max_height + %{max_width: thumbnail_max_width, max_height: thumbnail_max_height} ) do conn - |> put_resp_header("content-type", content_type) + |> put_resp_header("content-type", "image/jpeg") |> send_resp(200, thumbnail_binary) else _ -> -- cgit v1.2.3 From 3a1e810aaaea3e44c4dfc82a014485cf886d6b88 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 21 May 2020 21:47:32 +0300 Subject: [#2497] Customized `exexec` launch to support root operation (currently required by Gitlab CI). --- lib/pleroma/exec.ex | 38 +++++++++++++++++++++++++++++++++++++ lib/pleroma/helpers/media_helper.ex | 4 +--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/exec.ex (limited to 'lib') diff --git a/lib/pleroma/exec.ex b/lib/pleroma/exec.ex new file mode 100644 index 000000000..1b088d322 --- /dev/null +++ b/lib/pleroma/exec.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Exec do + @moduledoc "Pleroma wrapper around Exexec commands." + + alias Pleroma.Config + + def ensure_started(options_overrides \\ %{}) do + options = + if Config.get([:exexec, :root_mode]) || System.get_env("USER") == "root" do + # Note: running as `root` is discouraged (yet Gitlab CI does that by default) + %{root: true, user: "root", limit_users: ["root"]} + else + %{} + end + + options = + options + |> Map.merge(Config.get([:exexec, :options], %{})) + |> Map.merge(options_overrides) + + with {:error, {:already_started, pid}} <- Exexec.start(options) do + {:ok, pid} + end + end + + def run(cmd, options \\ %{}) do + ensure_started() + Exexec.run(cmd, options) + end + + def cmd(cmd, options \\ %{}) do + options = Map.merge(%{sync: true, stdout: true}, options) + run(cmd, options) + end +end diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index ee6b76c41..ecd234558 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,8 +7,6 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ - @ffmpeg_opts [{:sync, true}, {:stdout, true}] - def ffmpeg_resize_remote(uri, %{max_width: max_width, max_height: max_height}) do cmd = ~s""" curl -L "#{uri}" | @@ -20,7 +18,7 @@ defmodule Pleroma.Helpers.MediaHelper do cat """ - with {:ok, [stdout: stdout_list]} <- Exexec.run(cmd, @ffmpeg_opts) do + with {:ok, [stdout: stdout_list]} <- Pleroma.Exec.cmd(cmd) do {:ok, Enum.join(stdout_list)} end end -- cgit v1.2.3 From ec1452fd1cdb9cb1db9b8bad872916d3213489e2 Mon Sep 17 00:00:00 2001 From: href Date: Thu, 14 May 2020 21:36:31 +0200 Subject: Pleroma.MIME: use gen_magic --- lib/pleroma/application.ex | 1 + lib/pleroma/mime.ex | 84 ++++++++++++---------------------------------- 2 files changed, 23 insertions(+), 62 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 9d3d92b38..c74255629 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -80,6 +80,7 @@ defmodule Pleroma.Application do [ Pleroma.Stats, Pleroma.JobQueueMonitor, + Pleroma.MIME, {Oban, Config.get(Oban)} ] ++ task_children(@env) ++ diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex index 6ee055f50..3b406630e 100644 --- a/lib/pleroma/mime.ex +++ b/lib/pleroma/mime.ex @@ -6,8 +6,21 @@ defmodule Pleroma.MIME do @moduledoc """ Returns the mime-type of a binary and optionally a normalized file-name. """ - @default "application/octet-stream" @read_bytes 35 + @pool __MODULE__.GenMagicPool + + def child_spec(_) do + pool_size = Pleroma.Config.get!([:gen_magic_pool, :size]) + name = @pool + + %{ + id: __MODULE__, + start: {GenMagic.Pool, :start_link, [[name: name, pool_size: pool_size]]}, + type: :worker, + restart: :permanent, + shutdown: 500 + } + end @spec file_mime_type(String.t(), String.t()) :: {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error @@ -20,9 +33,10 @@ defmodule Pleroma.MIME do @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error def file_mime_type(filename) do - File.open(filename, [:read], fn f -> - check_mime_type(IO.binread(f, @read_bytes)) - end) + case GenMagic.Pool.perform(@pool, filename) do + {:ok, %GenMagic.Result{mime_type: content_type}} -> {:ok, content_type} + error -> error + end end def bin_mime_type(binary, filename) do @@ -34,13 +48,14 @@ defmodule Pleroma.MIME do @spec bin_mime_type(binary()) :: {:ok, String.t()} | :error def bin_mime_type(<>) do - {:ok, check_mime_type(head)} + case GenMagic.Pool.perform(@pool, {:bytes, head}) do + {:ok, %GenMagic.Result{mime_type: content_type}} -> {:ok, content_type} + error -> error + end end def bin_mime_type(_), do: :error - def mime_type(<<_::binary>>), do: {:ok, @default} - defp fix_extension(filename, content_type) do parts = String.split(filename, ".") @@ -62,59 +77,4 @@ defmodule Pleroma.MIME do Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".") end end - - defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do - "image/png" - end - - defp check_mime_type(<<0x47, 0x49, 0x46, 0x38, _, 0x61, _::binary>>) do - "image/gif" - end - - defp check_mime_type(<<0xFF, 0xD8, 0xFF, _::binary>>) do - "image/jpeg" - end - - defp check_mime_type(<<0x1A, 0x45, 0xDF, 0xA3, _::binary>>) do - "video/webm" - end - - defp check_mime_type(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do - "video/mp4" - end - - defp check_mime_type(<<0x49, 0x44, 0x33, _::binary>>) do - "audio/mpeg" - end - - defp check_mime_type(<<255, 251, _, 68, 0, 0, 0, 0, _::binary>>) do - "audio/mpeg" - end - - defp check_mime_type( - <<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::size(160), 0x80, 0x74, 0x68, 0x65, - 0x6F, 0x72, 0x61, _::binary>> - ) do - "video/ogg" - end - - defp check_mime_type(<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::binary>>) do - "audio/ogg" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "WAVE", _::binary>>) do - "audio/wav" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "WEBP", _::binary>>) do - "image/webp" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "AVI.", _::binary>>) do - "video/avi" - end - - defp check_mime_type(_) do - @default - end end -- cgit v1.2.3 From f124f6820582d50be83ba7a1709b14ce8ee1abcc Mon Sep 17 00:00:00 2001 From: href Date: Tue, 16 Jun 2020 15:11:45 +0200 Subject: Switch from gen_magic to majic, use Majic.Plug, remove Pleroma.MIME --- lib/pleroma/application.ex | 2 +- lib/pleroma/mime.ex | 80 ---------------------- lib/pleroma/upload.ex | 19 ++--- .../web/activity_pub/activity_pub_controller.ex | 2 + .../mastodon_api/controllers/media_controller.ex | 1 + .../pleroma_api/controllers/account_controller.ex | 5 ++ .../pleroma_api/controllers/mascot_controller.ex | 10 +-- 7 files changed, 25 insertions(+), 94 deletions(-) delete mode 100644 lib/pleroma/mime.ex (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index c74255629..9c74fa00e 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -80,7 +80,7 @@ defmodule Pleroma.Application do [ Pleroma.Stats, Pleroma.JobQueueMonitor, - Pleroma.MIME, + {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]}, {Oban, Config.get(Oban)} ] ++ task_children(@env) ++ diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex deleted file mode 100644 index 3b406630e..000000000 --- a/lib/pleroma/mime.ex +++ /dev/null @@ -1,80 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.MIME do - @moduledoc """ - Returns the mime-type of a binary and optionally a normalized file-name. - """ - @read_bytes 35 - @pool __MODULE__.GenMagicPool - - def child_spec(_) do - pool_size = Pleroma.Config.get!([:gen_magic_pool, :size]) - name = @pool - - %{ - id: __MODULE__, - start: {GenMagic.Pool, :start_link, [[name: name, pool_size: pool_size]]}, - type: :worker, - restart: :permanent, - shutdown: 500 - } - end - - @spec file_mime_type(String.t(), String.t()) :: - {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error - def file_mime_type(path, filename) do - with {:ok, content_type} <- file_mime_type(path), - filename <- fix_extension(filename, content_type) do - {:ok, content_type, filename} - end - end - - @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error - def file_mime_type(filename) do - case GenMagic.Pool.perform(@pool, filename) do - {:ok, %GenMagic.Result{mime_type: content_type}} -> {:ok, content_type} - error -> error - end - end - - def bin_mime_type(binary, filename) do - with {:ok, content_type} <- bin_mime_type(binary), - filename <- fix_extension(filename, content_type) do - {:ok, content_type, filename} - end - end - - @spec bin_mime_type(binary()) :: {:ok, String.t()} | :error - def bin_mime_type(<>) do - case GenMagic.Pool.perform(@pool, {:bytes, head}) do - {:ok, %GenMagic.Result{mime_type: content_type}} -> {:ok, content_type} - error -> error - end - end - - def bin_mime_type(_), do: :error - - defp fix_extension(filename, content_type) do - parts = String.split(filename, ".") - - new_filename = - if length(parts) > 1 do - Enum.drop(parts, -1) |> Enum.join(".") - else - Enum.join(parts) - end - - cond do - content_type == "application/octet-stream" -> - filename - - ext = List.first(MIME.extensions(content_type)) -> - new_filename <> "." <> ext - - true -> - Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".") - end - end -end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 797555bff..a0ba2f4c0 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -57,6 +57,7 @@ defmodule Pleroma.Upload do defstruct [:id, :name, :tempfile, :content_type, :path] @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} + @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." def store(upload, opts \\ []) do opts = get_opts(opts) @@ -123,14 +124,13 @@ defmodule Pleroma.Upload do end defp prepare_upload(%Plug.Upload{} = file, opts) do - with :ok <- check_file_size(file.path, opts.size_limit), - {:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do + with :ok <- check_file_size(file.path, opts.size_limit) do {:ok, %__MODULE__{ id: UUID.generate(), - name: name, + name: file.filename, tempfile: file.path, - content_type: content_type + content_type: file.content_type }} end end @@ -138,16 +138,17 @@ defmodule Pleroma.Upload do defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do parsed = Regex.named_captures(~r/(?jpeg|png|gif);base64,(?.*)/, image_data) data = Base.decode64!(parsed["data"], ignore: :whitespace) - hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data))) + hash = Base.encode16(:crypto.hash(:sha256, data), lower: true) with :ok <- check_binary_size(data, opts.size_limit), tmp_path <- tempfile_for_image(data), - {:ok, content_type, name} <- - Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do + {:ok, %{mime_type: content_type}} <- + Majic.perform({:bytes, data}, pool: Pleroma.MajicPool), + [ext | _] <- MIME.extensions(content_type) do {:ok, %__MODULE__{ id: UUID.generate(), - name: name, + name: hash <> "." <> ext, tempfile: tmp_path, content_type: content_type }} @@ -156,7 +157,7 @@ defmodule Pleroma.Upload do # For Mix.Tasks.MigrateLocalUploads defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do - with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do + with {:ok, %{mime_type: content_type}} <- Majic.perform(path, pool: Pleroma.MajicPool) do {:ok, %__MODULE__{upload | content_type: content_type}} 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 f0b5c6e93..e2a5fb9e9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -45,6 +45,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do when action in [:read_inbox, :update_outbox, :whoami, :upload_media] ) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media]) + plug( Pleroma.Plugs.Cache, [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 513de279f..06bb718ef 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -16,6 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MediaOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index f3554d919..97a6ae60d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -56,6 +56,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) + plug( + Majic.Plug, + [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] + ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation @doc "POST /api/v1/pleroma/accounts/confirmation_resend" diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index df6c50ca5..4ba4154dd 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaMascotOperation @@ -22,14 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do @doc "PUT /api/v1/pleroma/mascot" def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do - with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), - # Reject if not an image - %{type: "image"} = attachment <- render_attachment(object) do + with {:content_type, "image" <> _} <- {:content_type, file.content_type}, + {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + attachment = render_attachment(object) {:ok, _user} = User.mascot_update(user, attachment) json(conn, attachment) else - %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") + {:content_type, _} -> + render_error(conn, :unsupported_media_type, "mascots can only be images") end end -- cgit v1.2.3 From 39f7fc5b8ef781c98136d1f9be50a14bff394233 Mon Sep 17 00:00:00 2001 From: href Date: Tue, 16 Jun 2020 19:00:54 +0200 Subject: Update majic & call plug before OpenApiSpex --- lib/pleroma/web/mastodon_api/controllers/media_controller.ex | 2 +- lib/pleroma/web/pleroma_api/controllers/account_controller.ex | 10 +++++----- lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 06bb718ef..09acea7f4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do alias Pleroma.Web.ActivityPub.ActivityPub action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) plug(OAuthScopesPlug, %{scopes: ["read:media"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action != :show) - plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MediaOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 97a6ae60d..c76cbfc48 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -18,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do require Pleroma.Constants + plug( + Majic.Plug, + [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] + ) + plug( OpenApiSpex.Plug.PutApiSpec, [module: Pleroma.Web.ApiSpec] when action == :confirmation_resend @@ -56,11 +61,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) - plug( - Majic.Plug, - [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] - ) - defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation @doc "POST /api/v1/pleroma/accounts/confirmation_resend" diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index 4ba4154dd..7e2f6c328 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -9,10 +9,10 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) - plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaMascotOperation -- cgit v1.2.3 From 9faa63203717e71d666afb6755ff0b781b491823 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 5 Jul 2020 19:02:43 +0300 Subject: [#2497] Fixed merge issue. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 0f4575e2f..583c177f2 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,8 +12,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, - {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), + {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, :ok <- MediaProxy.verify_request_path_and_url(conn, url) do proxy_opts = Config.get([:media_proxy, :proxy_opts], []) ReverseProxy.call(conn, url, proxy_opts) -- cgit v1.2.3 From da116d81fb0028913c2a0f30ac35532fb500e8fc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 18 Aug 2020 18:23:27 +0300 Subject: [#2497] Added video preview proxy. Switched from exexec to Port. --- lib/pleroma/exec.ex | 38 ---------------- lib/pleroma/helpers/media_helper.ex | 19 +++++--- .../web/media_proxy/media_proxy_controller.ex | 50 +++++++++++++--------- 3 files changed, 41 insertions(+), 66 deletions(-) delete mode 100644 lib/pleroma/exec.ex (limited to 'lib') diff --git a/lib/pleroma/exec.ex b/lib/pleroma/exec.ex deleted file mode 100644 index 1b088d322..000000000 --- a/lib/pleroma/exec.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Exec do - @moduledoc "Pleroma wrapper around Exexec commands." - - alias Pleroma.Config - - def ensure_started(options_overrides \\ %{}) do - options = - if Config.get([:exexec, :root_mode]) || System.get_env("USER") == "root" do - # Note: running as `root` is discouraged (yet Gitlab CI does that by default) - %{root: true, user: "root", limit_users: ["root"]} - else - %{} - end - - options = - options - |> Map.merge(Config.get([:exexec, :options], %{})) - |> Map.merge(options_overrides) - - with {:error, {:already_started, pid}} <- Exexec.start(options) do - {:ok, pid} - end - end - - def run(cmd, options \\ %{}) do - ensure_started() - Exexec.run(cmd, options) - end - - def cmd(cmd, options \\ %{}) do - options = Map.merge(%{sync: true, stdout: true}, options) - run(cmd, options) - end -end diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index ecd234558..ca46698cc 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,19 +7,24 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ - def ffmpeg_resize_remote(uri, %{max_width: max_width, max_height: max_height}) do + def ffmpeg_resize(uri_or_path, %{max_width: max_width, max_height: max_height}) do cmd = ~s""" - curl -L "#{uri}" | - ffmpeg -i pipe:0 -f lavfi -i color=c=white \ + ffmpeg -i #{uri_or_path} -f lavfi -i color=c=white \ -filter_complex "[0:v] scale='min(#{max_width},iw)':'min(#{max_height},ih)': \ force_original_aspect_ratio=decrease [scaled]; \ [1][scaled] scale2ref [bg][img]; [bg] setsar=1 [bg]; [bg][img] overlay=shortest=1" \ - -f image2 -vcodec mjpeg -frames:v 1 pipe:1 | \ - cat + -loglevel quiet -f image2 -vcodec mjpeg -frames:v 1 pipe:1 """ - with {:ok, [stdout: stdout_list]} <- Pleroma.Exec.cmd(cmd) do - {:ok, Enum.join(stdout_list)} + pid = Port.open({:spawn, cmd}, [:use_stdio, :in, :stream, :exit_status, :binary]) + + receive do + {^pid, {:data, data}} -> + send(pid, {self(), :close}) + {:ok, data} + + {^pid, {:exit_status, status}} when status > 0 -> + {:error, status} end end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 583c177f2..8861398dd 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -66,31 +66,23 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp thumbnail_max_dimensions(params) do - config = Config.get([:media_preview_proxy], []) - - thumbnail_max_width = - if w = params["thumbnail_max_width"] do - String.to_integer(w) - else - Keyword.fetch!(config, :thumbnail_max_width) - end + defp handle_preview("image/" <> _ = _content_type, conn, url) do + handle_image_or_video_preview(conn, url) + end - thumbnail_max_height = - if h = params["thumbnail_max_height"] do - String.to_integer(h) - else - Keyword.fetch!(config, :thumbnail_max_height) - end + defp handle_preview("video/" <> _ = _content_type, conn, url) do + handle_image_or_video_preview(conn, url) + end - {thumbnail_max_width, thumbnail_max_height} + defp handle_preview(content_type, conn, _url) do + send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp handle_preview("image/" <> _ = _content_type, %{params: params} = conn, url) do + defp handle_image_or_video_preview(%{params: params} = conn, url) do with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), media_proxy_url <- MediaProxy.url(url), {:ok, thumbnail_binary} <- - MediaHelper.ffmpeg_resize_remote( + MediaHelper.ffmpeg_resize( media_proxy_url, %{max_width: thumbnail_max_width, max_height: thumbnail_max_height} ) do @@ -99,12 +91,28 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> send_resp(200, thumbnail_binary) else _ -> - send_resp(conn, :failed_dependency, "Can't handle image preview.") + send_resp(conn, :failed_dependency, "Can't handle preview.") end end - defp handle_preview(content_type, conn, _url) do - send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") + defp thumbnail_max_dimensions(params) do + config = Config.get([:media_preview_proxy], []) + + thumbnail_max_width = + if w = params["thumbnail_max_width"] do + String.to_integer(w) + else + Keyword.fetch!(config, :thumbnail_max_width) + end + + thumbnail_max_height = + if h = params["thumbnail_max_height"] do + String.to_integer(h) + else + Keyword.fetch!(config, :thumbnail_max_height) + end + + {thumbnail_max_width, thumbnail_max_height} end defp preview_head_request_timeout do -- cgit v1.2.3 From 7794d7c694110543998ee1fb278c68babda37301 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 19 Aug 2020 06:50:20 +0300 Subject: added Pleroma.Web.PleromaAPI.EmojiFileController --- .../operations/pleroma_emoji_file_operation.ex | 133 +++++++++++++++++++++ .../operations/pleroma_emoji_pack_operation.ex | 105 ---------------- .../controllers/emoji_file_controller.ex | 122 +++++++++++++++++++ .../controllers/emoji_pack_controller.ex | 107 +---------------- lib/pleroma/web/router.ex | 6 +- 5 files changed, 259 insertions(+), 214 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex new file mode 100644 index 000000000..b6932157a --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -0,0 +1,133 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Add new file to the pack", + operationId: "PleromaAPI.EmojiPackController.add_file", + security: [%{"oAuth" => ["write"]}], + requestBody: request_body("Parameters", create_request(), required: true), + parameters: [name_param()], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 409 => Operation.response("Conflict", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + type: :object, + required: [:file], + properties: %{ + file: %Schema{ + description: + "File needs to be uploaded with the multipart request or link to remote file", + anyOf: [ + %Schema{type: :string, format: :binary}, + %Schema{type: :string, format: :uri} + ] + }, + shortcode: %Schema{ + type: :string, + description: + "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." + }, + filename: %Schema{ + type: :string, + description: + "New emoji file name. If not specified will be taken from original filename." + } + } + } + end + + def update_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Add new file to the pack", + operationId: "PleromaAPI.EmojiPackController.update_file", + security: [%{"oAuth" => ["write"]}], + requestBody: request_body("Parameters", update_request(), required: true), + parameters: [name_param()], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 409 => Operation.response("Conflict", "application/json", ApiError) + } + } + end + + defp update_request do + %Schema{ + type: :object, + required: [:shortcode, :new_shortcode, :new_filename], + properties: %{ + shortcode: %Schema{ + type: :string, + description: "Emoji file shortcode" + }, + new_shortcode: %Schema{ + type: :string, + description: "New emoji file shortcode" + }, + new_filename: %Schema{ + type: :string, + description: "New filename for emoji file" + }, + force: %Schema{ + type: :boolean, + description: "With true value to overwrite existing emoji with new shortcode", + default: false + } + } + } + end + + def delete_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Delete emoji file from pack", + operationId: "PleromaAPI.EmojiPackController.delete_file", + security: [%{"oAuth" => ["write"]}], + parameters: [ + name_param(), + Operation.parameter(:shortcode, :query, :string, "File shortcode", + example: "cofe", + required: true + ) + ], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 400 => Operation.response("Bad Request", "application/json", ApiError) + } + } + end + + defp name_param do + Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) + end + + defp files_object do + %Schema{ + type: :object, + additionalProperties: %Schema{type: :string}, + description: "Object with emoji names as keys and filenames as values" + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index b2b4f8713..59548af13 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -175,111 +175,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do } end - def add_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Add new file to the pack", - operationId: "PleromaAPI.EmojiPackController.add_file", - security: [%{"oAuth" => ["write"]}], - requestBody: request_body("Parameters", add_file_request(), required: true), - parameters: [name_param()], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) - } - } - end - - defp add_file_request do - %Schema{ - type: :object, - required: [:file], - properties: %{ - file: %Schema{ - description: - "File needs to be uploaded with the multipart request or link to remote file", - anyOf: [ - %Schema{type: :string, format: :binary}, - %Schema{type: :string, format: :uri} - ] - }, - shortcode: %Schema{ - type: :string, - description: - "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." - }, - filename: %Schema{ - type: :string, - description: - "New emoji file name. If not specified will be taken from original filename." - } - } - } - end - - def update_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Add new file to the pack", - operationId: "PleromaAPI.EmojiPackController.update_file", - security: [%{"oAuth" => ["write"]}], - requestBody: request_body("Parameters", update_file_request(), required: true), - parameters: [name_param()], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) - } - } - end - - defp update_file_request do - %Schema{ - type: :object, - required: [:shortcode, :new_shortcode, :new_filename], - properties: %{ - shortcode: %Schema{ - type: :string, - description: "Emoji file shortcode" - }, - new_shortcode: %Schema{ - type: :string, - description: "New emoji file shortcode" - }, - new_filename: %Schema{ - type: :string, - description: "New filename for emoji file" - }, - force: %Schema{ - type: :boolean, - description: "With true value to overwrite existing emoji with new shortcode", - default: false - } - } - } - end - - def delete_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Delete emoji file from pack", - operationId: "PleromaAPI.EmojiPackController.delete_file", - security: [%{"oAuth" => ["write"]}], - parameters: [ - name_param(), - Operation.parameter(:shortcode, :query, :string, "File shortcode", - example: "cofe", - required: true - ) - ], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError) - } - } - end - def import_from_filesystem_operation do %Operation{ tags: ["Emoji Packs"], diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex new file mode 100644 index 000000000..ba9f07795 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -0,0 +1,122 @@ +defmodule Pleroma.Web.PleromaAPI.EmojiFileController do + use Pleroma.Web, :controller + + alias Pleroma.Emoji.Pack + alias Pleroma.Web.ApiSpec + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + Pleroma.Plugs.OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action in [ + :create, + :update, + :delete + ] + ) + + defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation + + def create(%{body_params: params} = conn, %{name: pack_name}) do + filename = params[:filename] || get_filename(params[:file]) + shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) + + with {:ok, pack} <- Pack.add_file(pack_name, shortcode, filename, params[:file]) do + json(conn, pack.files) + else + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + + {:error, :not_found} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name, shortcode or filename cannot be empty"}) + + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while adding file to pack." + ) + end + end + + def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do + new_shortcode = params[:new_shortcode] + new_filename = params[:new_filename] + force = params[:force] + + with {:ok, pack} <- Pack.update_file(pack_name, shortcode, new_shortcode, new_filename, force) do + json(conn, pack.files) + else + {:error, :doesnt_exist} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{ + error: + "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" + }) + + {:error, :not_found} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while updating file in pack." + ) + end + end + + def delete(conn, %{name: pack_name, shortcode: shortcode}) do + with {:ok, pack} <- Pack.delete_file(pack_name, shortcode) do + json(conn, pack.files) + else + {:error, :doesnt_exist} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + + {:error, :not_found} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name or shortcode cannot be empty"}) + + {:error, _} -> + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while removing file from pack." + ) + end + end + + defp get_filename(%Plug.Upload{filename: filename}), do: filename + defp get_filename(url) when is_binary(url), do: Path.basename(url) +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 657f46324..e3969fee1 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -14,10 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do :download, :create, :update, - :delete, - :add_file, - :update_file, - :delete_file + :delete ] ) @@ -184,105 +181,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do end end - def add_file(%{body_params: params} = conn, %{name: name}) do - filename = params[:filename] || get_filename(params[:file]) - shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) - - with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do - json(conn, pack.files) - else - {:error, :already_exists} -> - conn - |> put_status(:conflict) - |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack name, shortcode or filename cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while adding file to pack." - ) - end - end - - def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do - new_shortcode = params[:new_shortcode] - new_filename = params[:new_filename] - force = params[:force] - - with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do - json(conn, pack.files) - else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - {:error, :already_exists} -> - conn - |> put_status(:conflict) - |> json(%{ - error: - "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" - }) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while updating file in pack." - ) - end - end - - def delete_file(conn, %{name: name, shortcode: shortcode}) do - with {:ok, pack} <- Pack.delete_file(name, shortcode) do - json(conn, pack.files) - else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack name or shortcode cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while removing file from pack." - ) - end - end - def import_from_filesystem(conn, _params) do with {:ok, names} <- Pack.import_from_filesystem() do json(conn, names) @@ -298,7 +196,4 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> json(%{error: "Error accessing emoji pack directory"}) end end - - defp get_filename(%Plug.Upload{filename: filename}), do: filename - defp get_filename(url) when is_binary(url), do: Path.basename(url) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c6433cc53..8a307d591 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -229,9 +229,9 @@ defmodule Pleroma.Web.Router do patch("/:name", EmojiPackController, :update) delete("/:name", EmojiPackController, :delete) - post("/:name/files", EmojiPackController, :add_file) - patch("/:name/files", EmojiPackController, :update_file) - delete("/:name/files", EmojiPackController, :delete_file) + post("/:name/files", EmojiFileController, :create) + patch("/:name/files", EmojiFileController, :update) + delete("/:name/files", EmojiFileController, :delete) end # Pack info / downloading -- cgit v1.2.3 From 4ee15e991efb5bd5bf69d84d27dbbee81443d1dc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 19 Aug 2020 21:36:26 +0300 Subject: [#2497] Media preview proxy config refactoring & documentation. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 8861398dd..31d18c119 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -15,8 +15,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do {:ok, url} <- MediaProxy.decode_url(sig64, url64), {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, :ok <- MediaProxy.verify_request_path_and_url(conn, url) do - proxy_opts = Config.get([:media_proxy, :proxy_opts], []) - ReverseProxy.call(conn, url, proxy_opts) + ReverseProxy.call(conn, url, media_proxy_opts()) else {:enabled, false} -> send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) @@ -116,13 +115,16 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp preview_head_request_timeout do - Config.get([:media_preview_proxy, :proxy_opts, :head_request_max_read_duration]) || - preview_timeout() + Keyword.get(media_preview_proxy_opts(), :head_request_max_read_duration) || + Keyword.get(media_proxy_opts(), :max_read_duration) || + ReverseProxy.max_read_duration_default() end - defp preview_timeout do - Config.get([:media_preview_proxy, :proxy_opts, :max_read_duration]) || - Config.get([:media_proxy, :proxy_opts, :max_read_duration]) || - ReverseProxy.max_read_duration_default() + defp media_proxy_opts do + Config.get([:media_proxy, :proxy_opts], []) + end + + defp media_preview_proxy_opts do + Config.get([:media_preview_proxy, :proxy_opts], []) end end -- cgit v1.2.3 From 02ad1cd8e97c44824b92b53ea1879a965bbd8358 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 20 Aug 2020 09:58:50 +0300 Subject: [#2497] Media preview proxy: added Content-Disposition header with filename to response. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 31d18c119..5513432f0 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -87,6 +87,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do ) do conn |> put_resp_header("content-type", "image/jpeg") + |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") |> send_resp(200, thumbnail_binary) else _ -> -- cgit v1.2.3 From aa0a5ffb4849880b5adbcc9188de01ef778381e3 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 21 Aug 2020 08:59:08 +0300 Subject: [#2497] Media preview proxy: added `quality` config setting, adjusted width/height defaults. --- lib/pleroma/helpers/media_helper.ex | 6 ++++-- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index ca46698cc..e11038052 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,13 +7,15 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ - def ffmpeg_resize(uri_or_path, %{max_width: max_width, max_height: max_height}) do + def ffmpeg_resize(uri_or_path, %{max_width: max_width, max_height: max_height} = options) do + quality = options[:quality] || 1 + cmd = ~s""" ffmpeg -i #{uri_or_path} -f lavfi -i color=c=white \ -filter_complex "[0:v] scale='min(#{max_width},iw)':'min(#{max_height},ih)': \ force_original_aspect_ratio=decrease [scaled]; \ [1][scaled] scale2ref [bg][img]; [bg] setsar=1 [bg]; [bg][img] overlay=shortest=1" \ - -loglevel quiet -f image2 -vcodec mjpeg -frames:v 1 pipe:1 + -loglevel quiet -f image2 -vcodec mjpeg -frames:v 1 -q:v #{quality} pipe:1 """ pid = Port.open({:spawn, cmd}, [:use_stdio, :in, :stream, :exit_status, :binary]) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 5513432f0..1c51aa5e3 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -78,12 +78,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_image_or_video_preview(%{params: params} = conn, url) do + quality = Config.get!([:media_preview_proxy, :quality]) + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), media_proxy_url <- MediaProxy.url(url), {:ok, thumbnail_binary} <- MediaHelper.ffmpeg_resize( media_proxy_url, - %{max_width: thumbnail_max_width, max_height: thumbnail_max_height} + %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} ) do conn |> put_resp_header("content-type", "image/jpeg") -- cgit v1.2.3 From 967afa064bb0dc85c054495b795a57a13cdf3b3c Mon Sep 17 00:00:00 2001 From: href Date: Fri, 21 Aug 2020 17:02:57 +0000 Subject: Fix truncated images --- lib/pleroma/helpers/media_helper.ex | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index e11038052..f87be8874 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -19,14 +19,24 @@ defmodule Pleroma.Helpers.MediaHelper do """ pid = Port.open({:spawn, cmd}, [:use_stdio, :in, :stream, :exit_status, :binary]) + loop_recv(pid) + end + + defp loop_recv(pid) do + loop_recv(pid, <<>>) + end + defp loop_recv(pid, acc) do receive do {^pid, {:data, data}} -> - send(pid, {self(), :close}) - {:ok, data} + loop_recv(pid, acc <> data) - {^pid, {:exit_status, status}} when status > 0 -> + {^pid, {:exit_status, 0}} -> + {:ok, acc} + + {^pid, {:exit_status, status}} -> {:error, status} end end + end -- cgit v1.2.3 From 4e6eb22b4af70e611cc61f94ba3d81758036a392 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 21 Aug 2020 12:19:35 -0500 Subject: Try to warm the cache with the preview image if preview proxy enabled --- lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index dfab105a3..5d8bb72aa 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do end url - |> MediaProxy.url() + |> MediaProxy.preview_url() |> HTTP.get([], adapter: opts) end -- cgit v1.2.3 From edde0d9b54b45a366ecdec01e9826f1ee8d1dc3a Mon Sep 17 00:00:00 2001 From: href Date: Fri, 21 Aug 2020 17:40:49 +0000 Subject: Remove newline for linter --- lib/pleroma/helpers/media_helper.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index f87be8874..89dd4204b 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -38,5 +38,4 @@ defmodule Pleroma.Helpers.MediaHelper do {:error, status} end end - end -- cgit v1.2.3 From f5845ff03395816902a637a28412f85e671575e7 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 22 Aug 2020 10:42:02 +0300 Subject: upload emoji zip file --- lib/pleroma/emoji/pack.ex | 112 ++++++++++++++++----- lib/pleroma/utils.ex | 18 ++++ .../operations/pleroma_emoji_file_operation.ex | 6 +- .../controllers/emoji_file_controller.ex | 42 ++++++-- 4 files changed, 145 insertions(+), 33 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index d076ae312..03aed33bb 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Emoji.Pack do } alias Pleroma.Emoji + alias Pleroma.Emoji.Pack @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do @@ -64,24 +65,93 @@ defmodule Pleroma.Emoji.Pack do end end - @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) :: - {:ok, t()} | {:error, File.posix() | atom()} - def add_file(name, shortcode, filename, file) do - with :ok <- validate_not_empty([name, shortcode, filename]), + @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t()) :: + {:ok, t()} + | {:error, File.posix() | atom()} + def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do + with {:ok, zip_items} <- :zip.table(to_charlist(file.path)) do + emojies = + for {_, path, s, _, _, _} <- zip_items, elem(s, 2) == :regular do + filename = Path.basename(path) + shortcode = Path.basename(filename, Path.extname(filename)) + + %{ + path: path, + filename: path, + shortcode: shortcode, + exist: not is_nil(Pleroma.Emoji.get(shortcode)) + } + end + |> Enum.group_by(& &1[:exist]) + + case Map.get(emojies, false, []) do + [_ | _] = new_emojies -> + {:ok, tmp_dir} = Pleroma.Utils.tmp_dir("emoji") + + try do + {:ok, _emoji_files} = + :zip.unzip( + to_charlist(file.path), + [ + {:file_list, Enum.map(new_emojies, & &1[:path])}, + {:cwd, tmp_dir} + ] + ) + + {_, updated_pack} = + Enum.map_reduce(new_emojies, pack, fn item, emoji_pack -> + emoji_file = %Plug.Upload{ + filename: item[:filename], + path: Path.join(tmp_dir, item[:path]) + } + + {:ok, updated_pack} = + do_add_file( + emoji_pack, + item[:shortcode], + to_string(item[:filename]), + emoji_file + ) + + {item, updated_pack} + end) + + Emoji.reload() + + {:ok, updated_pack} + after + File.rm_rf(tmp_dir) + end + + _ -> + {:ok, pack} + end + end + end + + def add_file(%Pack{} = pack, shortcode, filename, file) do + with :ok <- validate_not_empty([shortcode, filename]), :ok <- validate_emoji_not_exists(shortcode), - {:ok, pack} <- load_pack(name), - :ok <- save_file(file, pack, filename), - {:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do + {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do Emoji.reload() {:ok, updated_pack} end end - @spec delete_file(String.t(), String.t()) :: + defp do_add_file(pack, shortcode, filename, file) do + with :ok <- save_file(file, pack, filename), + {:ok, updated_pack} <- + pack + |> put_emoji(shortcode, filename) + |> save_pack() do + {:ok, updated_pack} + end + end + + @spec delete_file(t(), String.t()) :: {:ok, t()} | {:error, File.posix() | atom()} - def delete_file(name, shortcode) do - with :ok <- validate_not_empty([name, shortcode]), - {:ok, pack} <- load_pack(name), + def delete_file(%Pack{} = pack, shortcode) do + with :ok <- validate_not_empty([shortcode]), :ok <- remove_file(pack, shortcode), {:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do Emoji.reload() @@ -89,11 +159,10 @@ defmodule Pleroma.Emoji.Pack do end end - @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: + @spec update_file(t(), String.t(), String.t(), String.t(), boolean()) :: {:ok, t()} | {:error, File.posix() | atom()} - def update_file(name, shortcode, new_shortcode, new_filename, force) do - with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]), - {:ok, pack} <- load_pack(name), + def update_file(%Pack{} = pack, shortcode, new_shortcode, new_filename, force) do + with :ok <- validate_not_empty([shortcode, new_shortcode, new_filename]), {:ok, filename} <- get_filename(pack, shortcode), :ok <- validate_emoji_not_exists(new_shortcode, force), :ok <- rename_file(pack, filename, new_filename), @@ -386,19 +455,12 @@ defmodule Pleroma.Emoji.Pack do end end - defp save_file(file, pack, filename) do + defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do file_path = Path.join(pack.path, filename) create_subdirs(file_path) - case file do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - with {:ok, _} <- File.copy(upload_path, file_path), do: :ok - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write(file_path, file_contents) + with {:ok, _} <- File.copy(upload_path, file_path) do + :ok end end diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index 21d1159be..fcb8c64c7 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -24,4 +24,22 @@ defmodule Pleroma.Utils do def command_available?(command) do match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"])) end + + @doc "creates the uniq temporary directory" + @spec tmp_dir(String.t()) :: {:ok, String.t()} | {:error, :file.posix()} + def tmp_dir(prefix \\ "") do + sub_dir = [ + prefix, + Timex.to_unix(Timex.now()), + :os.getpid(), + String.downcase(Integer.to_string(:rand.uniform(0x100000000), 36)) + ] + + tmp_dir = Path.join(System.tmp_dir!(), Enum.join(sub_dir, "-")) + + case File.mkdir(tmp_dir) do + :ok -> {:ok, tmp_dir} + error -> error + end + end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index b6932157a..7dd4ce311 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -24,6 +24,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do parameters: [name_param()], responses: %{ 200 => Operation.response("Files Object", "application/json", files_object()), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError), 409 => Operation.response("Conflict", "application/json", ApiError) } @@ -67,6 +69,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do parameters: [name_param()], responses: %{ 200 => Operation.response("Files Object", "application/json", files_object()), + 404 => Operation.response("Not Found", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError), 409 => Operation.response("Conflict", "application/json", ApiError) } @@ -114,7 +117,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do ], responses: %{ 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError) + 400 => Operation.response("Bad Request", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index ba9f07795..d10f46fde 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -22,7 +22,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do filename = params[:filename] || get_filename(params[:file]) shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) - with {:ok, pack} <- Pack.add_file(pack_name, shortcode, filename, params[:file]) do + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, file} <- get_file(params[:file]), + {:ok, pack} <- Pack.add_file(pack, shortcode, filename, file) do json(conn, pack.files) else {:error, :already_exists} -> @@ -32,12 +34,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do {:error, :not_found} -> conn - |> put_status(:bad_request) + |> put_status(:not_found) |> json(%{error: "pack \"#{pack_name}\" is not found"}) {:error, :empty_values} -> conn - |> put_status(:bad_request) + |> put_status(:unprocessable_entity) |> json(%{error: "pack name, shortcode or filename cannot be empty"}) {:error, _} -> @@ -54,7 +56,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do new_filename = params[:new_filename] force = params[:force] - with {:ok, pack} <- Pack.update_file(pack_name, shortcode, new_shortcode, new_filename, force) do + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do json(conn, pack.files) else {:error, :doesnt_exist} -> @@ -72,7 +75,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do {:error, :not_found} -> conn - |> put_status(:bad_request) + |> put_status(:not_found) |> json(%{error: "pack \"#{pack_name}\" is not found"}) {:error, :empty_values} -> @@ -90,7 +93,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do end def delete(conn, %{name: pack_name, shortcode: shortcode}) do - with {:ok, pack} <- Pack.delete_file(pack_name, shortcode) do + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.delete_file(pack, shortcode) do json(conn, pack.files) else {:error, :doesnt_exist} -> @@ -100,7 +104,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do {:error, :not_found} -> conn - |> put_status(:bad_request) + |> put_status(:not_found) |> json(%{error: "pack \"#{pack_name}\" is not found"}) {:error, :empty_values} -> @@ -119,4 +123,28 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do defp get_filename(%Plug.Upload{filename: filename}), do: filename defp get_filename(url) when is_binary(url), do: Path.basename(url) + + def get_file(%Plug.Upload{} = file), do: {:ok, file} + + def get_file(url) when is_binary(url) do + with {:ok, %Tesla.Env{body: body, status: code, headers: headers}} + when code in 200..299 <- Pleroma.HTTP.get(url) do + path = Plug.Upload.random_file!("emoji") + + content_type = + case List.keyfind(headers, "content-type", 0) do + {"content-type", value} -> value + nil -> nil + end + + File.write(path, body) + + {:ok, + %Plug.Upload{ + filename: Path.basename(url), + path: path, + content_type: content_type + }} + end + end end -- cgit v1.2.3 From 0922791e4d2233d527dda23e66a952e3f359a3fe Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 22 Aug 2020 10:56:26 +0300 Subject: updated errors on add emoji --- .../operations/pleroma_emoji_file_operation.ex | 6 +- .../controllers/emoji_file_controller.ex | 73 +++++++++------------- 2 files changed, 32 insertions(+), 47 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index 7dd4ce311..efbfce75f 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -71,7 +71,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do 200 => Operation.response("Files Object", "application/json", files_object()), 404 => Operation.response("Not Found", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) + 409 => Operation.response("Conflict", "application/json", ApiError), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError) } } end @@ -118,7 +119,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do responses: %{ 200 => Operation.response("Files Object", "application/json", files_object()), 400 => Operation.response("Bad Request", "application/json", ApiError), - 404 => Operation.response("Not Found", "application/json", ApiError) + 404 => Operation.response("Not Found", "application/json", ApiError), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index d10f46fde..71c53df1d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -32,22 +32,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> put_status(:conflict) |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - {:error, :not_found} -> - conn - |> put_status(:not_found) - |> json(%{error: "pack \"#{pack_name}\" is not found"}) - {:error, :empty_values} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "pack name, shortcode or filename cannot be empty"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while adding file to pack." - ) + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name}) end end @@ -60,11 +51,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do json(conn, pack.files) else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - {:error, :already_exists} -> conn |> put_status(:conflict) @@ -73,22 +59,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" }) - {:error, :not_found} -> - conn - |> put_status(:not_found) - |> json(%{error: "pack \"#{pack_name}\" is not found"}) - {:error, :empty_values} -> conn - |> put_status(:bad_request) + |> put_status(:unprocessable_entity) |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while updating file in pack." - ) + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) end end @@ -97,30 +74,36 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do {:ok, pack} <- Pack.delete_file(pack, shortcode) do json(conn, pack.files) else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - {:error, :not_found} -> - conn - |> put_status(:not_found) - |> json(%{error: "pack \"#{pack_name}\" is not found"}) - {:error, :empty_values} -> conn - |> put_status(:bad_request) + |> put_status(:unprocessable_entity) |> json(%{error: "pack name or shortcode cannot be empty"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while removing file from pack." - ) + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) end end + defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) + end + + defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do + conn + |> put_status(:not_found) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + end + + defp handle_error(conn, {:error, _}, _) do + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while adding file to pack." + ) + end + defp get_filename(%Plug.Upload{filename: filename}), do: filename defp get_filename(url) when is_binary(url), do: Path.basename(url) -- cgit v1.2.3 From 98f8851f29f940051656caa1715820bce70f8c29 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 22 Aug 2020 15:12:11 -0500 Subject: Use the image thumbnail for rich metadata (OGP/Twittercards) --- lib/pleroma/web/metadata/utils.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 2f0dfb474..8a206e019 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -38,7 +38,7 @@ defmodule Pleroma.Web.Metadata.Utils do def scrub_html(content), do: content def attachment_url(url) do - MediaProxy.url(url) + MediaProxy.preview_url(url) end def user_name_string(user) do -- cgit v1.2.3 From 14ec12ac956ffa9964254cb3be390c9903103da3 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 24 Aug 2020 09:47:25 +0300 Subject: added tests --- lib/pleroma/emoji.ex | 3 ++ lib/pleroma/emoji/pack.ex | 129 +++++++++++++++++++++++----------------------- lib/pleroma/utils.ex | 16 +++--- 3 files changed, 77 insertions(+), 71 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index f6016d73f..04936155b 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -56,6 +56,9 @@ defmodule Pleroma.Emoji do end end + @spec exist?(String.t()) :: boolean() + def exist?(name), do: not is_nil(get(name)) + @doc "Returns all the emojos!!" @spec get_all() :: list({String.t(), String.t(), String.t()}) def get_all do diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 03aed33bb..dd79bdfab 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -65,71 +65,73 @@ defmodule Pleroma.Emoji.Pack do end end + @spec unpack_zip_emojies(list(tuple())) :: list(map()) + defp unpack_zip_emojies(zip_files) do + Enum.reduce(zip_files, [], fn + {_, path, s, _, _, _}, acc when elem(s, 2) == :regular -> + with( + filename <- Path.basename(path), + shortcode <- Path.basename(filename, Path.extname(filename)), + false <- Emoji.exist?(shortcode) + ) do + acc ++ [%{path: path, filename: path, shortcode: shortcode}] + else + _ -> acc + end + + _, acc -> + acc + end) + end + @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t()) :: {:ok, t()} | {:error, File.posix() | atom()} def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do - with {:ok, zip_items} <- :zip.table(to_charlist(file.path)) do - emojies = - for {_, path, s, _, _, _} <- zip_items, elem(s, 2) == :regular do - filename = Path.basename(path) - shortcode = Path.basename(filename, Path.extname(filename)) - - %{ - path: path, - filename: path, - shortcode: shortcode, - exist: not is_nil(Pleroma.Emoji.get(shortcode)) - } - end - |> Enum.group_by(& &1[:exist]) - - case Map.get(emojies, false, []) do - [_ | _] = new_emojies -> - {:ok, tmp_dir} = Pleroma.Utils.tmp_dir("emoji") - - try do - {:ok, _emoji_files} = - :zip.unzip( - to_charlist(file.path), - [ - {:file_list, Enum.map(new_emojies, & &1[:path])}, - {:cwd, tmp_dir} - ] + with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), + [_ | _] = emojies <- unpack_zip_emojies(zip_files), + {:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do + try do + {:ok, _emoji_files} = + :zip.unzip( + to_charlist(file.path), + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + ) + + {_, updated_pack} = + Enum.map_reduce(emojies, pack, fn item, emoji_pack -> + emoji_file = %Plug.Upload{ + filename: item[:filename], + path: Path.join(tmp_dir, item[:path]) + } + + {:ok, updated_pack} = + do_add_file( + emoji_pack, + item[:shortcode], + to_string(item[:filename]), + emoji_file ) - {_, updated_pack} = - Enum.map_reduce(new_emojies, pack, fn item, emoji_pack -> - emoji_file = %Plug.Upload{ - filename: item[:filename], - path: Path.join(tmp_dir, item[:path]) - } - - {:ok, updated_pack} = - do_add_file( - emoji_pack, - item[:shortcode], - to_string(item[:filename]), - emoji_file - ) - - {item, updated_pack} - end) - - Emoji.reload() - - {:ok, updated_pack} - after - File.rm_rf(tmp_dir) - end + {item, updated_pack} + end) + + Emoji.reload() - _ -> - {:ok, pack} + {:ok, updated_pack} + after + File.rm_rf(tmp_dir) end + else + {:error, _} = error -> + error + + _ -> + {:ok, pack} end end - def add_file(%Pack{} = pack, shortcode, filename, file) do + def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do with :ok <- validate_not_empty([shortcode, filename]), :ok <- validate_emoji_not_exists(shortcode), {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do @@ -139,12 +141,10 @@ defmodule Pleroma.Emoji.Pack do end defp do_add_file(pack, shortcode, filename, file) do - with :ok <- save_file(file, pack, filename), - {:ok, updated_pack} <- - pack - |> put_emoji(shortcode, filename) - |> save_pack() do - {:ok, updated_pack} + with :ok <- save_file(file, pack, filename) do + pack + |> put_emoji(shortcode, filename) + |> save_pack() end end @@ -312,9 +312,10 @@ defmodule Pleroma.Emoji.Pack do defp validate_emoji_not_exists(_shortcode, true), do: :ok defp validate_emoji_not_exists(shortcode, _) do - case Emoji.get(shortcode) do - nil -> :ok - _ -> {:error, :already_exists} + if Emoji.exist?(shortcode) do + {:error, :already_exists} + else + :ok end end @@ -466,7 +467,7 @@ defmodule Pleroma.Emoji.Pack do defp put_emoji(pack, shortcode, filename) do files = Map.put(pack.files, shortcode, filename) - %{pack | files: files} + %{pack | files: files, files_count: length(Map.keys(files))} end defp delete_emoji(pack, shortcode) do diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index fcb8c64c7..e95766223 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -28,14 +28,16 @@ defmodule Pleroma.Utils do @doc "creates the uniq temporary directory" @spec tmp_dir(String.t()) :: {:ok, String.t()} | {:error, :file.posix()} def tmp_dir(prefix \\ "") do - sub_dir = [ - prefix, - Timex.to_unix(Timex.now()), - :os.getpid(), - String.downcase(Integer.to_string(:rand.uniform(0x100000000), 36)) - ] + sub_dir = + [ + prefix, + Timex.to_unix(Timex.now()), + :os.getpid(), + String.downcase(Integer.to_string(:rand.uniform(0x100000000), 36)) + ] + |> Enum.join("-") - tmp_dir = Path.join(System.tmp_dir!(), Enum.join(sub_dir, "-")) + tmp_dir = Path.join(System.tmp_dir!(), sub_dir) case File.mkdir(tmp_dir) do :ok -> {:ok, tmp_dir} -- cgit v1.2.3 From b267b751d451508bd655142a4711776ea15484f4 Mon Sep 17 00:00:00 2001 From: Maksim Date: Tue, 25 Aug 2020 05:38:25 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/pleroma/emoji/pack.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index dd79bdfab..930bbb422 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -74,7 +74,7 @@ defmodule Pleroma.Emoji.Pack do shortcode <- Path.basename(filename, Path.extname(filename)), false <- Emoji.exist?(shortcode) ) do - acc ++ [%{path: path, filename: path, shortcode: shortcode}] + [%{path: path, filename: path, shortcode: shortcode} | acc] else _ -> acc end -- cgit v1.2.3 From 899ea2da3e77ca64598e45eba986d5315b523120 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 25 Aug 2020 17:18:22 -0500 Subject: Switch to imagemagick, only support videos --- lib/pleroma/helpers/media_helper.ex | 13 ++++++------- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 15 +++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 89dd4204b..07e6dba5e 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,18 +7,17 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ - def ffmpeg_resize(uri_or_path, %{max_width: max_width, max_height: max_height} = options) do - quality = options[:quality] || 1 + def image_resize(url, %{max_width: max_width, max_height: max_height} = options) do + quality = options[:quality] || 85 cmd = ~s""" - ffmpeg -i #{uri_or_path} -f lavfi -i color=c=white \ - -filter_complex "[0:v] scale='min(#{max_width},iw)':'min(#{max_height},ih)': \ - force_original_aspect_ratio=decrease [scaled]; \ - [1][scaled] scale2ref [bg][img]; [bg] setsar=1 [bg]; [bg][img] overlay=shortest=1" \ - -loglevel quiet -f image2 -vcodec mjpeg -frames:v 1 -q:v #{quality} pipe:1 + convert - -resize '#{max_width}x#{max_height}>' -quality #{quality} - """ pid = Port.open({:spawn, cmd}, [:use_stdio, :in, :stream, :exit_status, :binary]) + {:ok, env} = url |> Pleroma.Web.MediaProxy.url() |> Pleroma.HTTP.get() + image = env.body + Port.command(pid, image) loop_recv(pid) end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 1c51aa5e3..b925973ba 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -66,25 +66,20 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_preview("image/" <> _ = _content_type, conn, url) do - handle_image_or_video_preview(conn, url) - end - - defp handle_preview("video/" <> _ = _content_type, conn, url) do - handle_image_or_video_preview(conn, url) + handle_image_preview(conn, url) end defp handle_preview(content_type, conn, _url) do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp handle_image_or_video_preview(%{params: params} = conn, url) do - quality = Config.get!([:media_preview_proxy, :quality]) + defp handle_image_preview(%{params: params} = conn, url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), - media_proxy_url <- MediaProxy.url(url), {:ok, thumbnail_binary} <- - MediaHelper.ffmpeg_resize( - media_proxy_url, + MediaHelper.image_resize( + url, %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} ) do conn -- cgit v1.2.3 From ddbddc08fc9fe5458edc983c81a77671da34a71f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 25 Aug 2020 17:31:55 -0500 Subject: Redirects for videos right now --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index b925973ba..6abbf9e23 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -69,6 +69,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do handle_image_preview(conn, url) end + defp handle_preview("video/" <> _ = _content_type, conn, url) do + mediaproxy_url = url |> MediaProxy.url() + + redirect(conn, external: mediaproxy_url) + end + defp handle_preview(content_type, conn, _url) do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end -- cgit v1.2.3 From afa03ca8e2cffc85628beb5f9a70401d984ab216 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 25 Aug 2020 17:36:53 -0500 Subject: Allow both stdin and stdout --- lib/pleroma/helpers/media_helper.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 07e6dba5e..5fe135584 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Helpers.MediaHelper do convert - -resize '#{max_width}x#{max_height}>' -quality #{quality} - """ - pid = Port.open({:spawn, cmd}, [:use_stdio, :in, :stream, :exit_status, :binary]) + pid = Port.open({:spawn, cmd}, [:use_stdio, :stream, :exit_status, :binary]) {:ok, env} = url |> Pleroma.Web.MediaProxy.url() |> Pleroma.HTTP.get() image = env.body Port.command(pid, image) -- cgit v1.2.3 From a136e7e9b590e3f23e472bf27c7c6a81d8d7792b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 25 Aug 2020 18:10:27 -0500 Subject: Try specifying fd0, force jpg out --- lib/pleroma/helpers/media_helper.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 5fe135584..01f42d9b0 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Helpers.MediaHelper do quality = options[:quality] || 85 cmd = ~s""" - convert - -resize '#{max_width}x#{max_height}>' -quality #{quality} - + convert fd:0 -resize '#{max_width}x#{max_height}>' -quality #{quality} jpg:- """ pid = Port.open({:spawn, cmd}, [:use_stdio, :stream, :exit_status, :binary]) -- cgit v1.2.3 From bc94f0c6da2405e2f1cdae89696970728b6e987f Mon Sep 17 00:00:00 2001 From: href Date: Wed, 26 Aug 2020 16:12:34 +0200 Subject: Use mkfifo to feed ImageMagick --- lib/pleroma/helpers/media_helper.ex | 70 +++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 01f42d9b0..a43352ae0 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,18 +7,66 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ - def image_resize(url, %{max_width: max_width, max_height: max_height} = options) do + @tmp_base "/tmp/pleroma-media_preview-pipe" + + def image_resize(url, options) do + with executable when is_binary(executable) <- System.find_executable("convert"), + {:ok, args} <- prepare_resize_args(options), + url = Pleroma.Web.MediaProxy.url(url), + {:ok, env} <- Pleroma.HTTP.get(url), + {:ok, fifo_path} <- mkfifo() + do + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:convert, :command_not_found}} + {:error, _} = error -> error + end + end + + defp prepare_resize_args(%{max_width: max_width, max_height: max_height} = options) do quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + args = [ + "-auto-orient", # Support for EXIF rotation + "-resize", resize, + "-quality", to_string(quality) + ] + {:ok, args} + end - cmd = ~s""" - convert fd:0 -resize '#{max_width}x#{max_height}>' -quality #{quality} jpg:- - """ + defp prepare_resize_args(_), do: {:error, :missing_options} - pid = Port.open({:spawn, cmd}, [:use_stdio, :stream, :exit_status, :binary]) - {:ok, env} = url |> Pleroma.Web.MediaProxy.url() |> Pleroma.HTTP.get() - image = env.body - Port.command(pid, image) + defp run_fifo(fifo_path, env, executable, args) do + args = List.flatten([fifo_path, args, "jpg:fd:1"]) + pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) + fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) + true = Port.command(fifo, env.body) + :erlang.port_close(fifo) loop_recv(pid) + after + File.rm(fifo_path) + end + + defp mkfifo() do + path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}" + case System.cmd("mkfifo", [path]) do + {_, 0} -> + spawn(fifo_guard(path)) + {:ok, path} + {_, err} -> + {:error, {:fifo_failed, err}} + end + end + + defp fifo_guard(path) do + pid = self() + fn() -> + ref = Process.monitor(pid) + receive do + {:DOWN, ^ref, :process, ^pid, _} -> + File.rm(path) + end + end end defp loop_recv(pid) do @@ -29,12 +77,14 @@ defmodule Pleroma.Helpers.MediaHelper do receive do {^pid, {:data, data}} -> loop_recv(pid, acc <> data) - {^pid, {:exit_status, 0}} -> {:ok, acc} - {^pid, {:exit_status, status}} -> {:error, status} + after + 5000 -> + :erlang.port_close(pid) + {:error, :timeout} end end end -- cgit v1.2.3 From d4d1192341868d978e19777c17be85e331367264 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 14:28:25 +0000 Subject: Remove auto-orient; don't use it on previews, only originals --- lib/pleroma/helpers/media_helper.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index a43352ae0..db0c4b0cf 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -27,7 +27,6 @@ defmodule Pleroma.Helpers.MediaHelper do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) args = [ - "-auto-orient", # Support for EXIF rotation "-resize", resize, "-quality", to_string(quality) ] -- cgit v1.2.3 From 2c95533ead56217ec27e09e0ead0050e110dff22 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 15:37:45 +0000 Subject: Change method of convert using stdout, make progressive jpegs --- lib/pleroma/helpers/media_helper.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index db0c4b0cf..3256802a0 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Helpers.MediaHelper do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) args = [ + "-interlace", "Plane", "-resize", resize, "-quality", to_string(quality) ] @@ -36,7 +37,7 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_resize_args(_), do: {:error, :missing_options} defp run_fifo(fifo_path, env, executable, args) do - args = List.flatten([fifo_path, args, "jpg:fd:1"]) + args = List.flatten([fifo_path, args, "jpg:-"]) pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) true = Port.command(fifo, env.body) -- cgit v1.2.3 From eead2276e79f29c4d0e10d23eb7524a9ee5f5045 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 16:18:11 -0500 Subject: Ensure GIFs are redirected to the original or they become static. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 6abbf9e23..d465ce8d1 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -65,6 +65,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + defp handle_preview("image/gif" = _content_type, conn, url) do + mediaproxy_url = url |> MediaProxy.url() + + redirect(conn, external: mediaproxy_url) + end + defp handle_preview("image/" <> _ = _content_type, conn, url) do handle_image_preview(conn, url) end -- cgit v1.2.3 From 9567b96c7927be433eac4f023051adc5cbd6610c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 16:40:13 -0500 Subject: Rename to make it obvious this is for images not videos --- lib/pleroma/helpers/media_helper.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 3256802a0..fe11dd460 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Helpers.MediaHelper do def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), - {:ok, args} <- prepare_resize_args(options), + {:ok, args} <- prepare_image_resize_args(options), url = Pleroma.Web.MediaProxy.url(url), {:ok, env} <- Pleroma.HTTP.get(url), {:ok, fifo_path} <- mkfifo() @@ -23,7 +23,7 @@ defmodule Pleroma.Helpers.MediaHelper do end end - defp prepare_resize_args(%{max_width: max_width, max_height: max_height} = options) do + defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) args = [ @@ -34,7 +34,7 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, args} end - defp prepare_resize_args(_), do: {:error, :missing_options} + defp prepare_image_resize_args(_), do: {:error, :missing_options} defp run_fifo(fifo_path, env, executable, args) do args = List.flatten([fifo_path, args, "jpg:-"]) -- cgit v1.2.3 From 697bea04731614bcd2e1e10f0564863dc49a49fa Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 17:43:25 -0500 Subject: Move arg for images to the list so we can reuse these fifo functions for videos --- lib/pleroma/helpers/media_helper.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index fe11dd460..0299b16ae 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -29,7 +29,8 @@ defmodule Pleroma.Helpers.MediaHelper do args = [ "-interlace", "Plane", "-resize", resize, - "-quality", to_string(quality) + "-quality", to_string(quality), + "jpg:-" ] {:ok, args} end @@ -37,7 +38,7 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_image_resize_args(_), do: {:error, :missing_options} defp run_fifo(fifo_path, env, executable, args) do - args = List.flatten([fifo_path, args, "jpg:-"]) + args = List.flatten([fifo_path, args]) pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) true = Port.command(fifo, env.body) -- cgit v1.2.3 From 157ecf402230c0b786f5765dd8b709d45c45974a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 11:46:56 -0500 Subject: Follow redirects. I think we should be using some global adapter options here, though. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index d465ce8d1..736b7db56 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -50,7 +50,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp handle_preview(conn, url) do with {:ok, %{status: status} = head_response} when status in 200..299 <- - Tesla.head(url, opts: [adapter: [timeout: preview_head_request_timeout()]]) do + Tesla.head(url, + opts: [adapter: [timeout: preview_head_request_timeout(), follow_redirect: true]] + ) do content_type = Tesla.get_header(head_response, "content-type") handle_preview(content_type, conn, url) else -- cgit v1.2.3 From ef9d12fcc500d7429bee0d6ccffe3596434aee52 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 12:31:55 -0500 Subject: Attempt at supporting video thumbnails via ffmpeg --- lib/pleroma/helpers/media_helper.ex | 19 +++++++++++++++++++ lib/pleroma/web/media_proxy/media_proxy_controller.ex | 17 ++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 0299b16ae..7e1af8bac 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -37,6 +37,25 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_image_resize_args(_), do: {:error, :missing_options} + def video_framegrab(url) do + with executable when is_binary(executable) <- System.find_executable("ffmpeg"), + args = [ + "-i", "-", + "-vframes", "1", + "-f", "mjpeg", + "-loglevel", "error", + "-" + ], + url = Pleroma.Web.MediaProxy.url(url), + {:ok, env} <- Pleroma.HTTP.get(url), + {:ok, fifo_path} <- mkfifo() do + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:ffmpeg, :command_not_found}} + {:error, _} = error -> error + end + end + defp run_fifo(fifo_path, env, executable, args) do args = List.flatten([fifo_path, args]) pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 736b7db56..7ac1a97e2 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -78,9 +78,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_preview("video/" <> _ = _content_type, conn, url) do - mediaproxy_url = url |> MediaProxy.url() - - redirect(conn, external: mediaproxy_url) + handle_video_preview(conn, url) end defp handle_preview(content_type, conn, _url) do @@ -106,6 +104,19 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + defp handle_video_preview(conn, url) do + with {:ok, thumbnail_binary} <- + MediaHelper.video_framegrab(url) do + conn + |> put_resp_header("content-type", "image/jpeg") + |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") + |> send_resp(200, thumbnail_binary) + else + _ -> + send_resp(conn, :failed_dependency, "Can't handle preview.") + end + end + defp thumbnail_max_dimensions(params) do config = Config.get([:media_preview_proxy], []) -- cgit v1.2.3 From f1218a2b4e16178c8c1285157f7cd995dc950e3e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 12:47:29 -0500 Subject: ffmpeg needs input from fifo path, not stdin --- lib/pleroma/helpers/media_helper.ex | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 7e1af8bac..7c2bfbc53 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -39,16 +39,16 @@ defmodule Pleroma.Helpers.MediaHelper do def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), + url = Pleroma.Web.MediaProxy.url(url), + {:ok, env} <- Pleroma.HTTP.get(url), + {:ok, fifo_path} <- mkfifo(), args = [ - "-i", "-", + "-i", fifo_path, "-vframes", "1", "-f", "mjpeg", "-loglevel", "error", "-" - ], - url = Pleroma.Web.MediaProxy.url(url), - {:ok, env} <- Pleroma.HTTP.get(url), - {:ok, fifo_path} <- mkfifo() do + ] do run_fifo(fifo_path, env, executable, args) else nil -> {:error, {:ffmpeg, :command_not_found}} @@ -57,7 +57,12 @@ defmodule Pleroma.Helpers.MediaHelper do end defp run_fifo(fifo_path, env, executable, args) do - args = List.flatten([fifo_path, args]) + args = + if _executable = System.find_executable("convert") do + List.flatten([fifo_path, args]) + else + args + end pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) true = Port.command(fifo, env.body) -- cgit v1.2.3 From dd1de994d57e3d9c99bb4e4c7019c696b5153f50 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 13:10:40 -0500 Subject: Try to trick ffmpeg into working with this named pipe --- lib/pleroma/helpers/media_helper.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 7c2bfbc53..385a4df81 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -43,11 +43,12 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, env} <- Pleroma.HTTP.get(url), {:ok, fifo_path} <- mkfifo(), args = [ + "-y", "-i", fifo_path, "-vframes", "1", "-f", "mjpeg", "-loglevel", "error", - "-" + "pipe:" ] do run_fifo(fifo_path, env, executable, args) else -- cgit v1.2.3 From 3a5231ec8fd0583d7f4bf05378d8bb81096c4f40 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 16:33:37 -0500 Subject: Keep args construction within video/image scopes instead of mangling down in fifo town --- lib/pleroma/helpers/media_helper.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 385a4df81..b42612ccb 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -16,6 +16,7 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, env} <- Pleroma.HTTP.get(url), {:ok, fifo_path} <- mkfifo() do + args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) else nil -> {:error, {:convert, :command_not_found}} @@ -58,12 +59,6 @@ defmodule Pleroma.Helpers.MediaHelper do end defp run_fifo(fifo_path, env, executable, args) do - args = - if _executable = System.find_executable("convert") do - List.flatten([fifo_path, args]) - else - args - end pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) true = Port.command(fifo, env.body) -- cgit v1.2.3 From 67c79394e81cf9f5404afad29a397acf32dece33 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 17:15:23 -0500 Subject: Support static avatars and header images with Mediaproxy Preview --- lib/pleroma/web/mastodon_api/views/account_view.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 864c0417f..eef45b35d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -181,8 +181,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname - image = User.avatar_url(user) |> MediaProxy.url() + avatar = User.avatar_url(user) |> MediaProxy.url() + avatar_static = User.avatar_url(user) |> MediaProxy.preview_url() header = User.banner_url(user) |> MediaProxy.url() + header_static = User.banner_url(user) |> MediaProxy.preview_url() following_count = if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do @@ -247,10 +249,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do statuses_count: user.note_count, note: user.bio || "", url: user.uri || user.ap_id, - avatar: image, - avatar_static: image, + avatar: avatar, + avatar_static: avatar_static, header: header, - header_static: header, + header_static: header_static, emojis: emojis, fields: user.fields, bot: bot, -- cgit v1.2.3 From 5b4d483f522f470b9d2cdb7f43d98dde427a1241 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 27 Aug 2020 17:28:21 -0500 Subject: Add a note about the avatars and banners situation --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 7ac1a97e2..411dc95d0 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -67,6 +67,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + # TODO: find a workaround so avatar_static and banner_static can work. + # Those only permit GIFs for animation, so we have to permit a way to + # allow those to get real static variants. defp handle_preview("image/gif" = _content_type, conn, url) do mediaproxy_url = url |> MediaProxy.url() -- cgit v1.2.3 From f0fefc4f5c3aa4fa62f2edee72ee864a16e7176d Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 28 Aug 2020 18:17:44 +0300 Subject: marks notifications as read after mute --- lib/pleroma/notification.ex | 12 ++++++++++++ lib/pleroma/web/common_api/common_api.ex | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index c1825f810..b952e81fa 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -648,4 +648,16 @@ defmodule Pleroma.Notification do ) |> Repo.one() end + + @spec mark_as_read(User.t(), Activity.t()) :: {integer(), nil | [term()]} + def mark_as_read(%User{id: id}, %Activity{data: %{"context" => context}}) do + from( + n in Notification, + join: a in assoc(n, :activity), + where: n.user_id == ^id, + where: n.seen == false, + where: fragment("?->>'context'", a.data) == ^context + ) + |> Repo.update_all(set: [seen: true]) + end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5ad2b91c2..43e9e39a8 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -452,7 +452,8 @@ defmodule Pleroma.Web.CommonAPI do end def add_mute(user, activity) do - with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do + with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), + _ <- Pleroma.Notification.mark_as_read(user, activity) do {:ok, activity} else {:error, _} -> {:error, dgettext("errors", "conversation is already muted")} -- cgit v1.2.3 From dfceb03cf47374fdeab60784476b2e266208a4bb Mon Sep 17 00:00:00 2001 From: href Date: Fri, 28 Aug 2020 21:14:28 +0200 Subject: Rewrite MP4/MOV binaries to be faststart In some cases, MP4/MOV files can have the data _before_ the meta-data. Thus, ffmpeg (and all similar tools) cannot really process the input if it's given over stdin/streaming/pipes. BUT I REALLY DON'T WANT TO MAKE TEMPORARY FILES so here we go, an implementation of qtfaststart in elixir. --- lib/pleroma/helpers/media_helper.ex | 59 +++++++++++----- lib/pleroma/helpers/qt_fast_start.ex | 131 +++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 lib/pleroma/helpers/qt_fast_start.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index b42612ccb..5ac75b326 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -14,8 +14,7 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, args} <- prepare_image_resize_args(options), url = Pleroma.Web.MediaProxy.url(url), {:ok, env} <- Pleroma.HTTP.get(url), - {:ok, fifo_path} <- mkfifo() - do + {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) else @@ -27,12 +26,17 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) + args = [ - "-interlace", "Plane", - "-resize", resize, - "-quality", to_string(quality), - "jpg:-" + "-interlace", + "Plane", + "-resize", + resize, + "-quality", + to_string(quality), + "jpg:-" ] + {:ok, args} end @@ -45,11 +49,15 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, fifo_path} <- mkfifo(), args = [ "-y", - "-i", fifo_path, - "-vframes", "1", - "-f", "mjpeg", - "-loglevel", "error", - "pipe:" + "-i", + fifo_path, + "-vframes", + "1", + "-f", + "mjpeg", + "-loglevel", + "error", + "-" ] do run_fifo(fifo_path, env, executable, args) else @@ -59,9 +67,18 @@ defmodule Pleroma.Helpers.MediaHelper do end defp run_fifo(fifo_path, env, executable, args) do - pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) + pid = + Port.open({:spawn_executable, executable}, [ + :use_stdio, + :stream, + :exit_status, + :binary, + args: args + ]) + fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) - true = Port.command(fifo, env.body) + fix = Pleroma.Helpers.QtFastStart.fix(env.body) + true = Port.command(fifo, fix) :erlang.port_close(fifo) loop_recv(pid) after @@ -70,10 +87,12 @@ defmodule Pleroma.Helpers.MediaHelper do defp mkfifo() do path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}" + case System.cmd("mkfifo", [path]) do {_, 0} -> spawn(fifo_guard(path)) {:ok, path} + {_, err} -> {:error, {:fifo_failed, err}} end @@ -81,8 +100,10 @@ defmodule Pleroma.Helpers.MediaHelper do defp fifo_guard(path) do pid = self() - fn() -> + + fn -> ref = Process.monitor(pid) + receive do {:DOWN, ^ref, :process, ^pid, _} -> File.rm(path) @@ -98,14 +119,16 @@ defmodule Pleroma.Helpers.MediaHelper do receive do {^pid, {:data, data}} -> loop_recv(pid, acc <> data) + {^pid, {:exit_status, 0}} -> {:ok, acc} + {^pid, {:exit_status, status}} -> {:error, status} - after - 5000 -> - :erlang.port_close(pid) - {:error, :timeout} + after + 5000 -> + :erlang.port_close(pid) + {:error, :timeout} end end end diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex new file mode 100644 index 000000000..694b583b9 --- /dev/null +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -0,0 +1,131 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.QtFastStart do + @moduledoc """ + (WIP) Converts a "slow start" (data before metadatas) mov/mp4 file to a "fast start" one (metadatas before data). + """ + + # TODO: Cleanup and optimizations + # Inspirations: https://www.ffmpeg.org/doxygen/3.4/qt-faststart_8c_source.html + # https://github.com/danielgtaylor/qtfaststart/blob/master/qtfaststart/processor.py + # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 + # Paracetamol + + def fix(binary = <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do + index = fix(binary, binary, 0, []) + + case index do + [{"ftyp", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + [{"ftyp", _, _, _, _}, {"free", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + _ -> binary + end + end + + def fix(binary) do + binary + end + + defp fix(<<>>, _bin, _pos, acc) do + :lists.reverse(acc) + end + + defp fix( + <>, + bin, + pos, + acc + ) do + if fourcc == "mdat" && size == 0 do + # mdat with 0 size means "seek to the end" -- also, in that case the file is probably OK. + acc = [ + {fourcc, pos, byte_size(bin) - pos, byte_size(bin) - pos, + <>} + | acc + ] + + fix(<<>>, bin, byte_size(bin), acc) + else + full_size = size - 8 + <> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <>} + | acc + ] + + fix(rest, bin, pos + size, acc) + end + end + + defp faststart(index) do + {{_ftyp, _, _, _, ftyp}, index} = List.keytake(index, "ftyp", 0) + + # Skip re-writing the free fourcc as it's kind of useless. Why stream useless bytes when you can do without? + {free_size, index} = + case List.keytake(index, "free", 0) do + {{_, _, _, size, _}, index} -> {size, index} + _ -> {0, index} + end + + {{_moov, _, _, moov_size, moov}, index} = List.keytake(index, "moov", 0) + offset = -free_size + moov_size + rest = for {_, _, _, _, data} <- index, do: data, into: <<>> + <> = moov + new_moov = fix_moov(moov_data, offset) + <> + end + + defp fix_moov(moov, offset) do + fix_moov(moov, offset, <<>>) + end + + defp fix_moov(<<>>, _, acc), do: acc + + defp fix_moov( + <>, + offset, + acc + ) do + full_size = size - 8 + <> = rest + + data = + cond do + fourcc in ["trak", "mdia", "minf", "stbl"] -> + # Theses contains sto or co64 part + <>)::binary>> + + fourcc in ["stco", "co64"] -> + # fix the damn thing + <> = data + + entry_size = + case fourcc do + "stco" -> 4 + "co64" -> 8 + end + + {_, result} = + Enum.reduce(1..count, {rest, <<>>}, fn _, + {<>, acc} -> + {rest, <>} + end) + + <> + + true -> + <> + end + + acc = <> + fix_moov(rest, offset, acc) + end +end -- cgit v1.2.3 From 24d522c3b366b54b23bebaf07371145d50820d4a Mon Sep 17 00:00:00 2001 From: href Date: Sat, 29 Aug 2020 13:05:23 +0200 Subject: QtFastStart: optimize ~4-6x faster ~3~4x memory usage reduction (now mostly adds what we are rewriting in the metadatas) --- lib/pleroma/helpers/qt_fast_start.ex | 115 +++++++++++++++++------------------ 1 file changed, 57 insertions(+), 58 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex index 694b583b9..8cba06e54 100644 --- a/lib/pleroma/helpers/qt_fast_start.ex +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -13,10 +13,11 @@ defmodule Pleroma.Helpers.QtFastStart do # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 # Paracetamol - def fix(binary = <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do - index = fix(binary, binary, 0, []) + def fix(binary = <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::bits>>) do + index = fix(binary, 0, nil, nil, []) case index do + :abort -> binary [{"ftyp", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) [{"ftyp", _, _, _, _}, {"free", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) _ -> binary @@ -27,37 +28,32 @@ defmodule Pleroma.Helpers.QtFastStart do binary end - defp fix(<<>>, _bin, _pos, acc) do - :lists.reverse(acc) + # MOOV have been seen before MDAT- abort + defp fix(<<_::bits>>, _, true, false, _) do + :abort end defp fix( - <>, - bin, + <>, pos, + got_moov, + got_mdat, acc ) do - if fourcc == "mdat" && size == 0 do - # mdat with 0 size means "seek to the end" -- also, in that case the file is probably OK. - acc = [ - {fourcc, pos, byte_size(bin) - pos, byte_size(bin) - pos, - <>} - | acc - ] - - fix(<<>>, bin, byte_size(bin), acc) - else - full_size = size - 8 - <> = rest - - acc = [ - {fourcc, pos, pos + size, size, - <>} - | acc - ] - - fix(rest, bin, pos + size, acc) - end + full_size = (size - 8) * 8 + <> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <>} + | acc + ] + + fix(rest, pos + size, got_moov || fourcc == "moov", got_mdat || fourcc == "mdat", acc) + end + + defp fix(<<>>, _pos, _, _, acc) do + :lists.reverse(acc) end defp faststart(index) do @@ -72,60 +68,63 @@ defmodule Pleroma.Helpers.QtFastStart do {{_moov, _, _, moov_size, moov}, index} = List.keytake(index, "moov", 0) offset = -free_size + moov_size - rest = for {_, _, _, _, data} <- index, do: data, into: <<>> - <> = moov - new_moov = fix_moov(moov_data, offset) - <> - end - - defp fix_moov(moov, offset) do - fix_moov(moov, offset, <<>>) + rest = for {_, _, _, _, data} <- index, do: data, into: [] + <> = moov + [ftyp, moov_head, fix_moov(moov_data, offset, []), rest] end - defp fix_moov(<<>>, _, acc), do: acc - defp fix_moov( - <>, + <>, offset, acc ) do - full_size = size - 8 - <> = rest + full_size = (size - 8) * 8 + <> = rest data = cond do fourcc in ["trak", "mdia", "minf", "stbl"] -> # Theses contains sto or co64 part - <>)::binary>> + [<>, fix_moov(data, offset, [])] fourcc in ["stco", "co64"] -> # fix the damn thing - <> = data + <> = data entry_size = case fourcc do - "stco" -> 4 - "co64" -> 8 + "stco" -> 32 + "co64" -> 64 end - {_, result} = - Enum.reduce(1..count, {rest, <<>>}, fn _, - {<>, acc} -> - {rest, <>} - end) - - <> + [ + <>, + rewrite_entries(entry_size, offset, rest, []) + ] true -> - <> + [<>, data] end - acc = <> + acc = [acc | data] fix_moov(rest, offset, acc) end + + defp fix_moov(<<>>, _, acc), do: acc + + for size <- [32, 64] do + defp rewrite_entries( + unquote(size), + offset, + <>, + acc + ) do + rewrite_entries(unquote(size), offset, rest, [ + acc | <> + ]) + end + end + + defp rewrite_entries(_, _, <<>>, acc), do: acc end -- cgit v1.2.3 From 2d2af75777ae468fb08a2b09dc5af4636106a04b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 30 Aug 2020 09:17:24 -0500 Subject: Support PNG previews to preserve alpha channels --- lib/pleroma/helpers/media_helper.ex | 17 ++++++++++++ .../web/media_proxy/media_proxy_controller.ex | 32 ++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 5ac75b326..d8a6db4e1 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -23,6 +23,23 @@ defmodule Pleroma.Helpers.MediaHelper do end end + defp prepare_image_resize_args( + %{max_width: max_width, max_height: max_height, format: "png"} = options + ) do + quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + + args = [ + "-resize", + resize, + "-quality", + to_string(quality), + "png:-" + ] + + {:ok, args} + end + defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 411dc95d0..94fae6cac 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -76,8 +76,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do redirect(conn, external: mediaproxy_url) end + defp handle_preview("image/png" <> _ = _content_type, conn, url) do + handle_png_preview(conn, url) + end + defp handle_preview("image/" <> _ = _content_type, conn, url) do - handle_image_preview(conn, url) + handle_jpeg_preview(conn, url) end defp handle_preview("video/" <> _ = _content_type, conn, url) do @@ -88,7 +92,31 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp handle_image_preview(%{params: params} = conn, url) do + defp handle_png_preview(%{params: params} = conn, url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + url, + %{ + max_width: thumbnail_max_width, + max_height: thumbnail_max_height, + quality: quality, + format: "png" + } + ) do + conn + |> put_resp_header("content-type", "image/png") + |> put_resp_header("content-disposition", "inline; filename=\"preview.png\"") + |> send_resp(200, thumbnail_binary) + else + _ -> + send_resp(conn, :failed_dependency, "Can't handle preview.") + end + end + + defp handle_jpeg_preview(%{params: params} = conn, url) do quality = Config.get!([:media_preview_proxy, :image_quality]) with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), -- cgit v1.2.3 From 4ef210a587113313cd6887b7499832d0c0798f7f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 30 Aug 2020 09:32:22 -0500 Subject: Credo --- lib/pleroma/helpers/media_helper.ex | 2 +- lib/pleroma/helpers/qt_fast_start.ex | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index d8a6db4e1..9bd815c26 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -102,7 +102,7 @@ defmodule Pleroma.Helpers.MediaHelper do File.rm(fifo_path) end - defp mkfifo() do + defp mkfifo do path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}" case System.cmd("mkfifo", [path]) do diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex index 8cba06e54..bb93224b5 100644 --- a/lib/pleroma/helpers/qt_fast_start.ex +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Helpers.QtFastStart do # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 # Paracetamol - def fix(binary = <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::bits>>) do + def fix(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::bits>> = binary) do index = fix(binary, 0, nil, nil, []) case index do @@ -59,7 +59,8 @@ defmodule Pleroma.Helpers.QtFastStart do defp faststart(index) do {{_ftyp, _, _, _, ftyp}, index} = List.keytake(index, "ftyp", 0) - # Skip re-writing the free fourcc as it's kind of useless. Why stream useless bytes when you can do without? + # Skip re-writing the free fourcc as it's kind of useless. + # Why stream useless bytes when you can do without? {free_size, index} = case List.keytake(index, "free", 0) do {{_, _, _, size, _}, index} -> {size, index} -- cgit v1.2.3 From d91c4feebeb199f7c584f0a4292ce6f9cc331798 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 31 Aug 2020 11:02:54 +0200 Subject: Notification: Small refactor. --- lib/pleroma/notification.ex | 4 ++-- lib/pleroma/web/common_api/common_api.ex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b952e81fa..8868a910e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -649,8 +649,8 @@ defmodule Pleroma.Notification do |> Repo.one() end - @spec mark_as_read(User.t(), Activity.t()) :: {integer(), nil | [term()]} - def mark_as_read(%User{id: id}, %Activity{data: %{"context" => context}}) do + @spec mark_context_as_read(User.t(), String.t()) :: {integer(), nil | [term()]} + def mark_context_as_read(%User{id: id}, context) do from( n in Notification, join: a in assoc(n, :activity), diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 43e9e39a8..4ab533658 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -453,7 +453,7 @@ defmodule Pleroma.Web.CommonAPI do def add_mute(user, activity) do with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), - _ <- Pleroma.Notification.mark_as_read(user, activity) do + _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do {:ok, activity} else {:error, _} -> {:error, dgettext("errors", "conversation is already muted")} -- cgit v1.2.3 From 0a839d51a7adb034d6514ea647d90546c829813d Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 31 Aug 2020 13:08:50 +0300 Subject: [#2497] Added Cache-Control response header for media proxy preview endpoint. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 94fae6cac..2afcd861a 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -107,8 +107,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do } ) do conn - |> put_resp_header("content-type", "image/png") - |> put_resp_header("content-disposition", "inline; filename=\"preview.png\"") + |> put_preview_response_headers() |> send_resp(200, thumbnail_binary) else _ -> @@ -126,8 +125,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} ) do conn - |> put_resp_header("content-type", "image/jpeg") - |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") + |> put_preview_response_headers() |> send_resp(200, thumbnail_binary) else _ -> @@ -139,8 +137,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do with {:ok, thumbnail_binary} <- MediaHelper.video_framegrab(url) do conn - |> put_resp_header("content-type", "image/jpeg") - |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") + |> put_preview_response_headers() |> send_resp(200, thumbnail_binary) else _ -> @@ -148,6 +145,13 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + defp put_preview_response_headers(conn) do + conn + |> put_resp_header("content-type", "image/jpeg") + |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") + |> put_resp_header("cache-control", "max-age=0, private, must-revalidate") + end + defp thumbnail_max_dimensions(params) do config = Config.get([:media_preview_proxy], []) -- cgit v1.2.3 From 0b621a834acf751332f4d202bd50d4ff3e789176 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 31 Aug 2020 16:48:17 +0200 Subject: Chats: Add cascading delete on both referenced users. Also remove the now-superfluous join in the chat controller, which was only used to filter out these cases. --- lib/pleroma/web/pleroma_api/controllers/chat_controller.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index 1f2e953f7..e8a1746d4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -149,9 +149,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do from(c in Chat, where: c.user_id == ^user_id, where: c.recipient not in ^blocked_ap_ids, - order_by: [desc: c.updated_at], - inner_join: u in User, - on: u.ap_id == c.recipient + order_by: [desc: c.updated_at] ) |> Repo.all() -- cgit v1.2.3 From dc3a418c270e48c6849159097f8bba57d2a2578e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 1 Sep 2020 09:08:54 +0300 Subject: application.ex: disable warnings_as_errors at runtime see changed files for rationale --- lib/pleroma/application.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index c0b5db9f1..005aba50a 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -39,6 +39,9 @@ defmodule Pleroma.Application do # every time the application is restarted, so we disable module # conflicts at runtime Code.compiler_options(ignore_module_conflict: true) + # Disable warnings_as_errors at runtime, it breaks Phoenix live reload + # due to protocol consolidation warnings + Code.compiler_options(warnings_as_errors: false) Pleroma.Telemetry.Logger.attach() Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() -- cgit v1.2.3 From 2ecc7d92308d624dc9edb50665d752a71f55f608 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 25 Aug 2020 00:38:10 +0200 Subject: transmogrifier: Remove mastodon emoji-format from emoji field --- lib/pleroma/web/activity_pub/transmogrifier.ex | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 76298c4a0..0831efadc 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -318,9 +318,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Map.put(mapping, name, data["icon"]["url"]) end) - # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats - emoji = Map.merge(object["emoji"] || %{}, emoji) - Map.put(object, "emoji", emoji) end -- cgit v1.2.3 From a142da3e4f03f2bfee7af30cca59b0fdc82da73f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 25 Aug 2020 01:16:12 +0200 Subject: Add new Emoji Ecto.Type and fix emoji in Question --- .../activity_pub/object_validators/emoji.ex | 34 ++++++++++++++++++++++ .../object_validators/audio_validator.ex | 5 ++-- .../object_validators/chat_message_validator.ex | 2 +- .../object_validators/event_validator.ex | 5 ++-- .../object_validators/note_validator.ex | 11 +++++-- .../object_validators/question_validator.ex | 5 ++-- 6 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex (limited to 'lib') diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex new file mode 100644 index 000000000..4aacc5c88 --- /dev/null +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Emoji do + use Ecto.Type + + def type, do: :map + + def cast(data) when is_map(data) do + has_invalid_emoji? = + Enum.find(data, fn + {name, uri} when is_binary(name) and is_binary(uri) -> + # based on ObjectValidators.Uri.cast() + case URI.parse(uri) do + %URI{host: nil} -> true + %URI{host: ""} -> true + %URI{scheme: scheme} when scheme in ["https", "http"] -> false + _ -> true + end + + {_name, _uri} -> + true + end) + + if has_invalid_emoji?, do: :error, else: {:ok, data} + end + + def cast(_data), do: :error + + def dump(data), do: {:ok, data} + + def load(data), do: {:ok, data} +end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex index d1869f188..1a97c504a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -33,8 +34,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -83,6 +83,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() |> fix_url() end diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex index 91b475393..6acd4a771 100644 --- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do field(:content, ObjectValidators.SafeText) field(:actor, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) embeds_one(:attachment, AttachmentValidator) end diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index 07e4821a4..0b4c99dc0 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -39,8 +40,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -74,6 +74,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() end def changeset(struct, data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex index 20e735619..ab4469a59 100644 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do use Ecto.Schema alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -32,8 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do field(:actor, ObjectValidators.ObjectID) field(:attributedTo, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) # TODO: Write type field(:attachment, {:array, :map}, default: []) @@ -53,7 +53,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do |> validate_data() end + defp fix(data) do + data + |> Transmogrifier.fix_emoji() + end + def cast_data(data) do + data = fix(data) + %__MODULE__{} |> cast(data, __schema__(:fields)) end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 712047424..934d3c1ea 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -35,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -85,6 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() |> fix_closed() end -- cgit v1.2.3 From b960cede9a3183098ac5eb330fbc4d1c14efc034 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 25 Aug 2020 02:18:33 +0200 Subject: common_fixes: Force inserting context and context_id --- lib/pleroma/web/activity_pub/object_validators/common_fixes.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 721749de0..720213d73 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do Utils.create_context(data["context"] || data["conversation"]) data - |> Map.put_new("context", context) - |> Map.put_new("context_id", context_id) + |> Map.put("context", context) + |> Map.put("context_id", context_id) end def fix_attribution(data) do -- cgit v1.2.3 From d9a21e4784a83a0780b353c967520cd203f44f3f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 25 Aug 2020 02:21:19 +0200 Subject: fetcher: Remove fix_object call for Question activities --- lib/pleroma/object/fetcher.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 6fdbc8efd..d26c5adf5 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -36,8 +36,7 @@ defmodule Pleroma.Object.Fetcher do defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do Logger.debug("Reinjecting object #{new_data["id"]}") - with new_data <- Transmogrifier.fix_object(new_data), - data <- maybe_reinject_internal_fields(object, new_data), + with data <- maybe_reinject_internal_fields(object, new_data), {:ok, data, _} <- ObjectValidator.validate(data, %{}), changeset <- Object.change(object, %{data: data}), changeset <- touch_changeset(changeset), -- cgit v1.2.3 From 126461942b63bbb74900f296ebcee72d2a33f3d2 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 1 Sep 2020 09:25:32 +0300 Subject: User table: ensure bio is always a string Gets rid of '|| ""' in multiple places and fixes #2067 --- lib/pleroma/user.ex | 4 ++-- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/auth/pleroma_authenticator.ex | 2 +- lib/pleroma/web/mastodon_api/views/account_view.ex | 2 +- lib/pleroma/web/metadata/opengraph.ex | 2 +- lib/pleroma/web/metadata/twitter_card.ex | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d2ad9516f..94c96de8d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -83,7 +83,7 @@ defmodule Pleroma.User do ] schema "users" do - field(:bio, :string) + field(:bio, :string, default: "") field(:raw_bio, :string) field(:email, :string) field(:name, :string) @@ -1587,7 +1587,7 @@ defmodule Pleroma.User do # "Right to be forgotten" # https://gdpr.eu/right-to-be-forgotten/ change(user, %{ - bio: nil, + bio: "", raw_bio: nil, email: nil, name: nil, diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 624a508ae..333621413 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1224,7 +1224,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do name: data["name"], follower_address: data["followers"], following_address: data["following"], - bio: data["summary"], + bio: data["summary"] || "", actor_type: actor_type, also_known_as: Map.get(data, "alsoKnownAs", []), public_key: public_key, diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 200ca03dc..c611b3e09 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -68,7 +68,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do nickname = value([registration_attrs["nickname"], Registration.nickname(registration)]) email = value([registration_attrs["email"], Registration.email(registration)]) name = value([registration_attrs["name"], Registration.name(registration)]) || nickname - bio = value([registration_attrs["bio"], Registration.description(registration)]) + bio = value([registration_attrs["bio"], Registration.description(registration)]) || "" random_password = :crypto.strong_rand_bytes(64) |> Base.encode64() diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 864c0417f..d2a30a548 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -245,7 +245,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do followers_count: followers_count, following_count: following_count, statuses_count: user.note_count, - note: user.bio || "", + note: user.bio, url: user.uri || user.ap_id, avatar: image, avatar_static: image, diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex index 68c871e71..bb1b23208 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/opengraph.ex @@ -61,7 +61,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do @impl Provider def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do [ {:meta, [ diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex index 5d08ce422..df34b033f 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/twitter_card.ex @@ -40,7 +40,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do @impl Provider def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do [ title_tag(user), {:meta, [property: "twitter:description", content: truncated_bio], []}, -- cgit v1.2.3 From d8728580468ecf876e531440fa31aef6a3e33f7b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 1 Sep 2020 12:48:39 +0200 Subject: Fix removing an account from a list Mastodon (Frontend) changed a different method for deletes, keeping old format as mastodon documentation is too loose --- .../web/api_spec/operations/list_operation.ex | 20 ++++++++++++++------ .../web/mastodon_api/controllers/list_controller.ex | 13 +++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index c88ed5dd0..15039052e 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -114,7 +114,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do description: "Add accounts to the given list.", operationId: "ListController.add_to_list", parameters: [id_param()], - requestBody: add_remove_accounts_request(), + requestBody: add_remove_accounts_request(true), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -127,8 +127,16 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do tags: ["Lists"], summary: "Remove accounts from list", operationId: "ListController.remove_from_list", - parameters: [id_param()], - requestBody: add_remove_accounts_request(), + parameters: [ + id_param(), + Operation.parameter( + :account_ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of account IDs" + ) + ], + requestBody: add_remove_accounts_request(false), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -171,7 +179,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do ) end - defp add_remove_accounts_request do + defp add_remove_accounts_request(required) when is_boolean(required) do request_body( "Parameters", %Schema{ @@ -180,9 +188,9 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do properties: %{ account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID} }, - required: [:account_ids] + required: required && [:account_ids] }, - required: true + required: required ) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index acdc76fd2..4df13cb81 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -86,6 +86,19 @@ defmodule Pleroma.Web.MastodonAPI.ListController do json(conn, %{}) end + def remove_from_list( + %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, + _ + ) do + Enum.each(account_ids, fn account_id -> + with %User{} = followed <- User.get_cached_by_id(account_id) do + Pleroma.List.unfollow(list, followed) + end + end) + + json(conn, %{}) + end + defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) -- cgit v1.2.3 From 03d06062ab8feffbf7b03ecb7ff86c4a42af79ef Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 1 Sep 2020 19:12:45 +0300 Subject: don't fail on url fetch --- lib/pleroma/web/rich_media/parser.ex | 44 ++++++++++++---------- .../web/rich_media/parsers/ttl/aws_signed_url.ex | 15 +++----- 2 files changed, 29 insertions(+), 30 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index ca592833f..e9aa2dd03 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parser do + require Logger + defp parsers do Pleroma.Config.get([:rich_media, :parsers]) end @@ -10,18 +12,19 @@ defmodule Pleroma.Web.RichMedia.Parser do def parse(nil), do: {:error, "No URL provided"} if Pleroma.Config.get(:env) == :test do + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url), do: parse_url(url) else + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url) do - try do - Cachex.fetch!(:rich_media_cache, url, fn _ -> - {:commit, parse_url(url)} - end) - |> set_ttl_based_on_image(url) - rescue - e -> - {:error, "Cachex error: #{inspect(e)}"} - end + Cachex.fetch!(:rich_media_cache, url, fn _ -> + with {:ok, data} <- parse_url(url) do + {:commit, {:ok, data}} + else + error -> {:ignore, error} + end + end) + |> set_ttl_based_on_image(url) end end @@ -47,9 +50,11 @@ defmodule Pleroma.Web.RichMedia.Parser do config :pleroma, :rich_media, ttl_setters: [MyModule] """ + @spec set_ttl_based_on_image({:ok, map()} | {:error, any()}, String.t()) :: + {:ok, map()} | {:error, any()} def set_ttl_based_on_image({:ok, data}, url) do with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url), - ttl when is_number(ttl) <- get_ttl_from_image(data, url) do + {:ok, ttl} when is_number(ttl) <- get_ttl_from_image(data, url) do Cachex.expire_at(:rich_media_cache, url, ttl * 1000) {:ok, data} else @@ -58,8 +63,14 @@ defmodule Pleroma.Web.RichMedia.Parser do end end + def set_ttl_based_on_image({:error, _} = error, _) do + Logger.error("parsing error: #{inspect(error)}") + error + end + defp get_ttl_from_image(data, url) do - Pleroma.Config.get([:rich_media, :ttl_setters]) + [:rich_media, :ttl_setters] + |> Pleroma.Config.get() |> Enum.reduce({:ok, nil}, fn module, {:ok, _ttl} -> module.ttl(data, url) @@ -70,23 +81,16 @@ defmodule Pleroma.Web.RichMedia.Parser do end defp parse_url(url) do - try do - {:ok, %Tesla.Env{body: html}} = Pleroma.Web.RichMedia.Helpers.rich_media_get(url) - + with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), + {:ok, html} <- Floki.parse_document(html) do html - |> parse_html() |> maybe_parse() |> Map.put("url", url) |> clean_parsed_data() |> check_parsed_data() - rescue - e -> - {:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"} end end - defp parse_html(html), do: Floki.parse_document!(html) - defp maybe_parse(html) do Enum.reduce_while(parsers(), %{}, fn parser, acc -> case parser.parse(html, acc) do diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex index 0dc1efdaf..c5aaea2d4 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex @@ -10,20 +10,15 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do |> parse_query_params() |> format_query_params() |> get_expiration_timestamp() + else + {:error, "Not aws signed url #{inspect(image)}"} end end - defp is_aws_signed_url(""), do: nil - defp is_aws_signed_url(nil), do: nil - - defp is_aws_signed_url(image) when is_binary(image) do + defp is_aws_signed_url(image) when is_binary(image) and image != "" do %URI{host: host, query: query} = URI.parse(image) - if String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") do - image - else - nil - end + String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") end defp is_aws_signed_url(_), do: nil @@ -46,6 +41,6 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do |> Map.get("X-Amz-Date") |> Timex.parse("{ISO:Basic:Z}") - Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires")) + {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))} end end -- cgit v1.2.3 From 0a9c63fb4351ed29a521697f2c584b0ae007696c Mon Sep 17 00:00:00 2001 From: Sean King Date: Tue, 1 Sep 2020 12:20:32 -0600 Subject: Fix frontend install mix task bug --- lib/mix/tasks/pleroma/frontend.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 2adbf8d72..0a48be1fe 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -69,7 +69,7 @@ defmodule Mix.Tasks.Pleroma.Frontend do fe_label = "#{frontend} (#{ref})" - tmp_dir = Path.join(dest, "tmp") + tmp_dir = Path.join([instance_static_dir, "frontends", "tmp"]) with {_, :ok} <- {:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])}, @@ -133,6 +133,7 @@ defmodule Mix.Tasks.Pleroma.Frontend do defp install_frontend(frontend_info, source, dest) do from = frontend_info["build_dir"] || "dist" + File.rm_rf!(dest) File.mkdir_p!(dest) File.cp_r!(Path.join([source, from]), dest) :ok -- cgit v1.2.3 From 6ce28c409137972ee9b105b9d7ab4a0fd2a0d08b Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 1 Sep 2020 21:21:58 +0300 Subject: [#2497] Fix for png media proxy preview response headers (content-type & content-disposition). --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 2afcd861a..961c73666 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -67,7 +67,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - # TODO: find a workaround so avatar_static and banner_static can work. + # TODO: find a workaround so avatar_static and header_static can work. # Those only permit GIFs for animation, so we have to permit a way to # allow those to get real static variants. defp handle_preview("image/gif" = _content_type, conn, url) do @@ -107,7 +107,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do } ) do conn - |> put_preview_response_headers() + |> put_preview_response_headers("image/png", "preview.png") |> send_resp(200, thumbnail_binary) else _ -> @@ -145,10 +145,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp put_preview_response_headers(conn) do + defp put_preview_response_headers(conn, content_type \\ "image/jpeg", filename \\ "preview.jpg") do conn - |> put_resp_header("content-type", "image/jpeg") - |> put_resp_header("content-disposition", "inline; filename=\"preview.jpg\"") + |> put_resp_header("content-type", content_type) + |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") |> put_resp_header("cache-control", "max-age=0, private, must-revalidate") end -- cgit v1.2.3 From 868057871ac041346d8367181f00f0b127b33945 Mon Sep 17 00:00:00 2001 From: Karol Kosek Date: Tue, 1 Sep 2020 19:56:32 +0200 Subject: search: fix 'following' query parameter The parameter included the accounts that are following you (followers) instead of those you are actually following. Co-Authored-By: Haelwenn (lanodan) Monnier --- lib/pleroma/user/search.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index d4fd31069..adbef7fb8 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -116,7 +116,7 @@ defmodule Pleroma.User.Search do end defp base_query(_user, false), do: User - defp base_query(user, true), do: User.get_followers_query(user) + defp base_query(user, true), do: User.get_friends_query(user) defp filter_invisible_users(query) do from(q in query, where: q.invisible == false) -- cgit v1.2.3 From c17d83cd7330d5c874f647a5224a3a130ed88fb0 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 19 Aug 2020 10:22:59 +0300 Subject: improvements and fixes for http requests - fix for gun worker termination in some circumstances - pool for http clients (ex_aws, tzdata) - default pool timeouts for gun - gun retries on gun_down messages - s3 upload timeout if streaming enabled --- lib/pleroma/gun/connection_pool/worker.ex | 35 +++++++++++++++++++++---------- lib/pleroma/http/adapter_helper.ex | 16 ++++++++------ lib/pleroma/http/adapter_helper/gun.ex | 18 ++++++++-------- lib/pleroma/http/ex_aws.ex | 2 ++ lib/pleroma/http/tzdata.ex | 4 ++++ lib/pleroma/uploaders/s3.ex | 19 +++++++++++------ 6 files changed, 62 insertions(+), 32 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index fec9d0efa..c36332817 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -83,17 +83,25 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do end) {ref, state} = pop_in(state.client_monitors[client_pid]) - Process.demonitor(ref) - - timer = - if used_by == [] do - max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) - Process.send_after(self(), :idle_close, max_idle) + # DOWN message can receive right after `remove_client` call and cause worker to terminate + state = + if is_nil(ref) do + state else - nil + Process.demonitor(ref) + + timer = + if used_by == [] do + max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) + Process.send_after(self(), :idle_close, max_idle) + else + nil + end + + %{state | timer: timer} end - {:reply, :ok, %{state | timer: timer}, :hibernate} + {:reply, :ok, state, :hibernate} end @impl true @@ -103,16 +111,21 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do {:stop, :normal, state} end + @impl true + def handle_info({:gun_up, _pid, _protocol}, state) do + {:noreply, state, :hibernate} + end + # Gracefully shutdown if the connection got closed without any streams left @impl true def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do {:stop, :normal, state} end - # Otherwise, shutdown with an error + # Otherwise, wait for retry @impl true - def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams} = down_message, state) do - {:stop, {:error, down_message}, state} + def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do + {:noreply, state, :hibernate} end @impl true diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 9ec3836b0..740e6e9ff 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -10,6 +10,7 @@ defmodule Pleroma.HTTP.AdapterHelper do @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() + @type pool() :: :federation | :upload | :media | :default alias Pleroma.Config alias Pleroma.HTTP.AdapterHelper @@ -44,14 +45,13 @@ defmodule Pleroma.HTTP.AdapterHelper do @spec options(URI.t(), keyword()) :: keyword() def options(%URI{} = uri, opts \\ []) do @defaults - |> put_timeout() |> Keyword.merge(opts) + |> put_timeout() |> adapter_helper().options(uri) end - # For Hackney, this is the time a connection can stay idle in the pool. - # For Gun, this is the timeout to receive a message from Gun. - defp put_timeout(opts) do + @spec pool_timeout(pool()) :: non_neg_integer() + def pool_timeout(pool) do {config_key, default} = if adapter() == Tesla.Adapter.Gun do {:pools, Config.get([:pools, :default, :timeout], 5_000)} @@ -59,9 +59,13 @@ defmodule Pleroma.HTTP.AdapterHelper do {:hackney_pools, 10_000} end - timeout = Config.get([config_key, opts[:pool], :timeout], default) + Config.get([config_key, pool, :timeout], default) + end - Keyword.merge(opts, timeout: timeout) + # For Hackney, this is the time a connection can stay idle in the pool. + # For Gun, this is the timeout to receive a message from Gun. + defp put_timeout(opts) do + Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) end def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index b4ff8306c..db0a298b3 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -5,6 +5,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @behaviour Pleroma.HTTP.AdapterHelper + alias Pleroma.Config alias Pleroma.Gun.ConnectionPool alias Pleroma.HTTP.AdapterHelper @@ -14,7 +15,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do connect_timeout: 5_000, domain_lookup_timeout: 5_000, tls_handshake_timeout: 5_000, - retry: 0, + retry: 1, retry_timeout: 1000, await_up_timeout: 5_000 ] @@ -22,10 +23,11 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @spec options(keyword(), URI.t()) :: keyword() def options(incoming_opts \\ [], %URI{} = uri) do proxy = - Pleroma.Config.get([:http, :proxy_url]) + [:http, :proxy_url] + |> Config.get() |> AdapterHelper.format_proxy() - config_opts = Pleroma.Config.get([:http, :adapter], []) + config_opts = Config.get([:http, :adapter], []) @defaults |> Keyword.merge(config_opts) @@ -37,8 +39,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do defp add_scheme_opts(opts, %{scheme: "http"}), do: opts defp add_scheme_opts(opts, %{scheme: "https"}) do - opts - |> Keyword.put(:certificates_verification, true) + Keyword.put(opts, :certificates_verification, true) end @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} @@ -51,11 +52,11 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @prefix Pleroma.Gun.ConnectionPool def limiter_setup do - wait = Pleroma.Config.get([:connections_pool, :connection_acquisition_wait]) - retries = Pleroma.Config.get([:connections_pool, :connection_acquisition_retries]) + wait = Config.get([:connections_pool, :connection_acquisition_wait]) + retries = Config.get([:connections_pool, :connection_acquisition_retries]) :pools - |> Pleroma.Config.get([]) + |> Config.get([]) |> Enum.each(fn {name, opts} -> max_running = Keyword.get(opts, :size, 50) max_waiting = Keyword.get(opts, :max_waiting, 10) @@ -69,7 +70,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do case result do :ok -> :ok {:error, :existing} -> :ok - e -> raise e end end) diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex index e53e64077..2fe8beafc 100644 --- a/lib/pleroma/http/ex_aws.ex +++ b/lib/pleroma/http/ex_aws.ex @@ -11,6 +11,8 @@ defmodule Pleroma.HTTP.ExAws do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do + http_opts = Keyword.put(http_opts, :adapter, pool: :upload) + case HTTP.request(method, url, body, headers, http_opts) do {:ok, env} -> {:ok, %{status_code: env.status, headers: env.headers, body: env.body}} diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 34bb253a7..7dd3352ff 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,6 +11,8 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do + options = Keyword.put(options, :adapter, pool: :upload) + with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} end @@ -18,6 +20,8 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def head(url, headers, options) do + options = Keyword.put(options, :adapter, pool: :upload) + with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} end diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index a13ff23b6..ed9794ca2 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -46,12 +46,19 @@ defmodule Pleroma.Uploaders.S3 do op = if streaming do - upload.tempfile - |> ExAws.S3.Upload.stream_file() - |> ExAws.S3.upload(bucket, s3_name, [ - {:acl, :public_read}, - {:content_type, upload.content_type} - ]) + op = + upload.tempfile + |> ExAws.S3.Upload.stream_file() + |> ExAws.S3.upload(bucket, s3_name, [ + {:acl, :public_read}, + {:content_type, upload.content_type} + ]) + + # set s3 upload timeout to respect :upload pool timeout + # timeout should be slightly larger, so s3 can retry upload on fail + timeout = Pleroma.HTTP.AdapterHelper.pool_timeout(:upload) + 1_000 + opts = Keyword.put(op.opts, :timeout, timeout) + Map.put(op, :opts, opts) else {:ok, file_data} = File.read(upload.tempfile) -- cgit v1.2.3 From 5e8adf91b46c3f23ec423d53afccbb062df4a241 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 1 Sep 2020 11:30:56 +0300 Subject: don't overwrite passed pool option in http clients --- lib/pleroma/http/ex_aws.ex | 2 +- lib/pleroma/http/tzdata.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex index 2fe8beafc..c3f335c73 100644 --- a/lib/pleroma/http/ex_aws.ex +++ b/lib/pleroma/http/ex_aws.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.ExAws do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do - http_opts = Keyword.put(http_opts, :adapter, pool: :upload) + http_opts = Keyword.put_new(http_opts, :adapter, pool: :upload) case HTTP.request(method, url, body, headers, http_opts) do {:ok, env} -> diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 7dd3352ff..356799aab 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do - options = Keyword.put(options, :adapter, pool: :upload) + options = Keyword.put_new(options, :adapter, pool: :upload) with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} @@ -20,7 +20,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def head(url, headers, options) do - options = Keyword.put(options, :adapter, pool: :upload) + options = Keyword.put_new(options, :adapter, pool: :upload) with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} -- cgit v1.2.3 From 79f65b4374908a32ebf39db176a30a01152a9141 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 2 Sep 2020 09:16:51 +0300 Subject: correct pool and uniform headers format --- lib/mix/tasks/pleroma/frontend.ex | 4 +++- lib/pleroma/instances/instance.ex | 4 +++- lib/pleroma/object/fetcher.ex | 6 +++--- lib/pleroma/web/rich_media/helpers.ex | 2 +- lib/pleroma/web/web_finger/web_finger.ex | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 2adbf8d72..484af6da7 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -124,7 +124,9 @@ defmodule Mix.Tasks.Pleroma.Frontend do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], timeout: 120_000, recv_timeout: 120_000) do + Pleroma.HTTP.get(url, [], + adapter: [pool: :media, timeout: 120_000, recv_timeout: 120_000] + ) do unzip(zip_body, dest) else e -> {:error, e} diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index a1f935232..711c42158 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -150,7 +150,9 @@ defmodule Pleroma.Instances.Instance do defp scrape_favicon(%URI{} = instance_uri) do try do with {:ok, %Tesla.Env{body: html}} <- - Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]), + Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], + adapter: [pool: :media] + ), favicon_rel <- html |> Floki.parse_document!() diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 6fdbc8efd..374d8704a 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -164,12 +164,12 @@ defmodule Pleroma.Object.Fetcher do date: date }) - [{"signature", signature}] + {"signature", signature} end defp sign_fetch(headers, id, date) do if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - headers ++ make_signature(id, date) + [make_signature(id, date) | headers] else headers end @@ -177,7 +177,7 @@ defmodule Pleroma.Object.Fetcher do defp maybe_date_fetch(headers, date) do if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - headers ++ [{"date", date}] + [{"date", date} | headers] else headers end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 6210f2c5a..2fb482b51 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -96,6 +96,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do @rich_media_options end - Pleroma.HTTP.get(url, headers, options) + Pleroma.HTTP.get(url, headers, adapter: options) end end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index c4051e63e..6629f5356 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -136,12 +136,12 @@ defmodule Pleroma.Web.WebFinger do def find_lrdd_template(domain) do with {:ok, %{status: status, body: body}} when status in 200..299 <- - HTTP.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, status: status}} when status in 200..299 <- - HTTP.get("https://#{domain}/.well-known/host-meta", []) do + HTTP.get("https://#{domain}/.well-known/host-meta") do get_template_from_xml(body) else e -> {:error, "Can't find LRDD template: #{inspect(e)}"} -- cgit v1.2.3 From 1c57ef44983e150f3cc016290fe99f159eb79cb0 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 2 Sep 2020 10:33:43 +0300 Subject: default pool for tz_data client --- lib/pleroma/http/tzdata.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 356799aab..4539ac359 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :upload) + options = Keyword.put_new(options, :adapter, pool: :default) with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} @@ -20,7 +20,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def head(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :upload) + options = Keyword.put_new(options, :adapter, pool: :default) with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} -- cgit v1.2.3 From 84fbf1616104c09e0f4f5442d86ca2c573ae4056 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 2 Sep 2020 10:50:51 +0300 Subject: timeout option moved to gun adapter helper --- lib/pleroma/http/adapter_helper.ex | 23 ++--------------------- lib/pleroma/http/adapter_helper/gun.ex | 15 +++++++++++++++ lib/pleroma/uploaders/s3.ex | 14 +++++++++----- 3 files changed, 26 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 740e6e9ff..0728cbaa2 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -10,9 +10,7 @@ defmodule Pleroma.HTTP.AdapterHelper do @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() - @type pool() :: :federation | :upload | :media | :default - alias Pleroma.Config alias Pleroma.HTTP.AdapterHelper require Logger @@ -46,29 +44,12 @@ defmodule Pleroma.HTTP.AdapterHelper do def options(%URI{} = uri, opts \\ []) do @defaults |> Keyword.merge(opts) - |> put_timeout() |> adapter_helper().options(uri) end - @spec pool_timeout(pool()) :: non_neg_integer() - def pool_timeout(pool) do - {config_key, default} = - if adapter() == Tesla.Adapter.Gun do - {:pools, Config.get([:pools, :default, :timeout], 5_000)} - else - {:hackney_pools, 10_000} - end - - Config.get([config_key, pool, :timeout], default) - end - - # For Hackney, this is the time a connection can stay idle in the pool. - # For Gun, this is the timeout to receive a message from Gun. - defp put_timeout(opts) do - Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) - end - + @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) + defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index db0a298b3..02e20f2d1 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -20,6 +20,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do await_up_timeout: 5_000 ] + @type pool() :: :federation | :upload | :media | :default + @spec options(keyword(), URI.t()) :: keyword() def options(incoming_opts \\ [], %URI{} = uri) do proxy = @@ -34,6 +36,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do |> add_scheme_opts(uri) |> AdapterHelper.maybe_add_proxy(proxy) |> Keyword.merge(incoming_opts) + |> put_timeout() end defp add_scheme_opts(opts, %{scheme: "http"}), do: opts @@ -42,6 +45,18 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do Keyword.put(opts, :certificates_verification, true) end + defp put_timeout(opts) do + # this is the timeout to receive a message from Gun + Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) + end + + @spec pool_timeout(pool()) :: non_neg_integer() + def pool_timeout(pool) do + default = Config.get([:pools, :default, :timeout], 5_000) + + Config.get([:pools, pool, :timeout], default) + end + @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} def get_conn(uri, opts) do case ConnectionPool.get_conn(uri, opts) do diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index ed9794ca2..6dbef9085 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -54,11 +54,15 @@ defmodule Pleroma.Uploaders.S3 do {:content_type, upload.content_type} ]) - # set s3 upload timeout to respect :upload pool timeout - # timeout should be slightly larger, so s3 can retry upload on fail - timeout = Pleroma.HTTP.AdapterHelper.pool_timeout(:upload) + 1_000 - opts = Keyword.put(op.opts, :timeout, timeout) - Map.put(op, :opts, opts) + if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do + # set s3 upload timeout to respect :upload pool timeout + # timeout should be slightly larger, so s3 can retry upload on fail + timeout = Pleroma.HTTP.AdapterHelper.Gun.pool_timeout(:upload) + 1_000 + opts = Keyword.put(op.opts, :timeout, timeout) + Map.put(op, :opts, opts) + else + op + end else {:ok, file_data} = File.read(upload.tempfile) -- cgit v1.2.3 From 46236d1d873473d95b11cd7bfdcaa284ea55a9ad Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 2 Sep 2020 12:42:25 +0300 Subject: html.ex: optimize external url extraction By using a :not() selector and only extracting attributes from the first match. --- lib/pleroma/html.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index dc1b9b840..20b02f091 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -109,8 +109,9 @@ defmodule Pleroma.HTML do result = content |> Floki.parse_fragment!() - |> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]") - |> Floki.attribute("a", "href") + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") |> Enum.at(0) {:commit, {:ok, result}} -- cgit v1.2.3 From 19691389b92e617f1edad7d4e3168fe917d0a675 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 2 Sep 2020 14:21:28 +0300 Subject: Rich media: Add failure tracking --- lib/pleroma/web/rich_media/parser.ex | 54 +++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index e9aa2dd03..e98c743ca 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -17,14 +17,25 @@ defmodule Pleroma.Web.RichMedia.Parser do else @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url) do - Cachex.fetch!(:rich_media_cache, url, fn _ -> - with {:ok, data} <- parse_url(url) do - {:commit, {:ok, data}} - else - error -> {:ignore, error} - end - end) - |> set_ttl_based_on_image(url) + with {:ok, data} <- get_cached_or_parse(url), + {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} + else + error -> + Logger.error(fn -> "Rich media error: #{inspect(error)}" end) + end + end + + defp get_cached_or_parse(url) do + case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do + {:ok, _data} = res -> + res + + {:error, _} = e -> + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + Cachex.expire(:rich_media_cache, url, ttl) + e + end end end @@ -50,24 +61,23 @@ defmodule Pleroma.Web.RichMedia.Parser do config :pleroma, :rich_media, ttl_setters: [MyModule] """ - @spec set_ttl_based_on_image({:ok, map()} | {:error, any()}, String.t()) :: - {:ok, map()} | {:error, any()} - def set_ttl_based_on_image({:ok, data}, url) do - with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url), - {:ok, ttl} when is_number(ttl) <- get_ttl_from_image(data, url) do - Cachex.expire_at(:rich_media_cache, url, ttl * 1000) - {:ok, data} - else + @spec set_ttl_based_on_image(map(), String.t()) :: + {:ok, Integer.t() | :noop} | {:error, :no_key} + def set_ttl_based_on_image(data, url) do + case get_ttl_from_image(data, url) do + {:ok, ttl} when is_number(ttl) -> + ttl = ttl * 1000 + + case Cachex.expire_at(:rich_media_cache, url, ttl) do + {:ok, true} -> {:ok, ttl} + {:ok, false} -> {:error, :no_key} + end + _ -> - {:ok, data} + {:ok, :noop} end end - def set_ttl_based_on_image({:error, _} = error, _) do - Logger.error("parsing error: #{inspect(error)}") - error - end - defp get_ttl_from_image(data, url) do [:rich_media, :ttl_setters] |> Pleroma.Config.get() -- cgit v1.2.3 From a11f23c130331d98db941c3edcc4d2dcf139bbc6 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 2 Sep 2020 15:45:47 +0300 Subject: user agent if Endpoint is not started yet --- lib/pleroma/application.ex | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 005aba50a..33b1e3872 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -22,13 +22,18 @@ defmodule Pleroma.Application do def repository, do: @repository def user_agent do - case Config.get([:http, :user_agent], :default) do - :default -> - info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" - named_version() <> "; " <> info - - custom -> - custom + if Process.whereis(Pleroma.Web.Endpoint) do + case Config.get([:http, :user_agent], :default) do + :default -> + info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" + named_version() <> "; " <> info + + custom -> + custom + end + else + # fallback, if endpoint is not started yet + "Pleroma Data Loader" end end -- cgit v1.2.3 From d48fc90978eee46c8fba00a80082d14fc31a0eec Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 2 Sep 2020 13:44:08 +0300 Subject: StatusView: Start fetching rich media cards as soon as possible --- lib/pleroma/web/mastodon_api/views/status_view.ex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 01b8bb6bb..3fe1967be 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -23,6 +23,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] + # This is a naive way to do this, just spawning a process per activity + # to fetch the preview. However it should be fine considering + # pagination is restricted to 40 activities at a time + defp fetch_rich_media_for_activities(activities) do + Enum.each(activities, fn activity -> + spawn(fn -> + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + end) + end) + end + # TODO: Add cached version. defp get_replied_to_activities([]), do: %{} @@ -80,6 +91,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list activities = Enum.filter(opts.activities, & &1) + + # Start fetching rich media before doing anything else, so that later calls to get the cards + # only block for timeout in the worst case, as opposed to + # length(activities_with_links) * timeout + fetch_rich_media_for_activities(activities) replied_to_activities = get_replied_to_activities(activities) parent_activities = -- cgit v1.2.3 From cbf7f0e02943f44a73f4418b8c6a8bada06331d8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Sep 2020 09:09:13 -0500 Subject: Disallow password resets for deactivated accounts. Ensure all responses to password reset events are identical. --- .../web/mastodon_api/controllers/auth_controller.ex | 16 +++++----------- lib/pleroma/web/twitter_api/twitter_api.ex | 13 ++----------- 2 files changed, 7 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 753b3db3e..9f09550e1 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -59,17 +59,11 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] - with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do - conn - |> put_status(:no_content) - |> json("") - else - {:error, "unknown user"} -> - send_resp(conn, :not_found, "") - - {:error, _} -> - send_resp(conn, :bad_request, "") - end + TwitterAPI.password_reset(nickname_or_email) + + conn + |> put_status(:no_content) + |> json("") end defp local_mastodon_root_path(conn) do diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 2294d9d0d..5d7948507 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -72,7 +72,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), - %User{local: true, email: email} = user when is_binary(email) <- + %User{local: true, email: email, deactivated: false} = user when is_binary(email) <- User.get_by_nickname_or_email(nickname_or_email), {:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do user @@ -81,17 +81,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do {:ok, :enqueued} else - false -> - {:error, "bad user identifier"} - - %User{local: true, email: nil} -> + _ -> {:ok, :noop} - - %User{local: false} -> - {:error, "remote user"} - - nil -> - {:error, "unknown user"} end end -- cgit v1.2.3 From 581f382e712dc50fa51d1ab211f13b8843dcb448 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Sep 2020 18:32:00 +0200 Subject: ListController: DRY up stuff. --- .../web/mastodon_api/controllers/list_controller.ex | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 4df13cb81..5daeaa780 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -74,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do # DELETE /api/v1/lists/:id/accounts def remove_from_list( - %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, + %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, _ ) do Enum.each(account_ids, fn account_id -> @@ -86,17 +86,8 @@ defmodule Pleroma.Web.MastodonAPI.ListController do json(conn, %{}) end - def remove_from_list( - %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, - _ - ) do - Enum.each(account_ids, fn account_id -> - with %User{} = followed <- User.get_cached_by_id(account_id) do - Pleroma.List.unfollow(list, followed) - end - end) - - json(conn, %{}) + def remove_from_list(%{body_params: params} = conn, _) do + remove_from_list(%{conn | params: params}, %{}) end defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do -- cgit v1.2.3 From 60c925380da644866836fa4a275f4d57eaaada04 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 3 Sep 2020 20:13:29 +0300 Subject: [#2497] Added support for enforcing output format for media proxy preview, used for avatar_static & header_static (AccountView). --- lib/pleroma/helpers/uri_helper.ex | 1 + lib/pleroma/web/mastodon_api/views/account_view.ex | 4 ++-- lib/pleroma/web/media_proxy/media_proxy.ex | 15 +++++++++------ lib/pleroma/web/media_proxy/media_proxy_controller.ex | 11 ++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex index 6d205a636..9c9e53447 100644 --- a/lib/pleroma/helpers/uri_helper.ex +++ b/lib/pleroma/helpers/uri_helper.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Helpers.UriHelper do uri |> Map.put(:query, URI.encode_query(updated_params)) |> URI.to_string() + |> String.replace_suffix("?", "") end def maybe_add_base("/" <> uri, base), do: Path.join([base, uri]) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 7eb4e86fe..a811f81c2 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -182,9 +182,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do display_name = user.name || user.nickname avatar = User.avatar_url(user) |> MediaProxy.url() - avatar_static = User.avatar_url(user) |> MediaProxy.preview_url() + avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(output_format: "jpeg") header = User.banner_url(user) |> MediaProxy.url() - header_static = User.banner_url(user) |> MediaProxy.preview_url() + header_static = User.banner_url(user) |> MediaProxy.preview_url(output_format: "jpeg") following_count = if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 6695d49ce..4cbe1cf89 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MediaProxy do alias Pleroma.Config + alias Pleroma.Helpers.UriHelper alias Pleroma.Upload alias Pleroma.Web alias Pleroma.Web.MediaProxy.Invalidation @@ -58,9 +59,9 @@ defmodule Pleroma.Web.MediaProxy do # Note: routing all URLs to preview handler (even local and whitelisted). # Preview handler will call url/1 on decoded URLs, and applicable ones will detour media proxy. - def preview_url(url) do + def preview_url(url, preview_params \\ []) do if preview_enabled?() do - encode_preview_url(url) + encode_preview_url(url, preview_params) else url end @@ -116,10 +117,10 @@ defmodule Pleroma.Web.MediaProxy do build_url(sig64, base64, filename(url)) end - def encode_preview_url(url) do + def encode_preview_url(url, preview_params \\ []) do {base64, sig64} = base64_sig64(url) - build_preview_url(sig64, base64, filename(url)) + build_preview_url(sig64, base64, filename(url), preview_params) end def decode_url(sig, url) do @@ -155,8 +156,10 @@ defmodule Pleroma.Web.MediaProxy do proxy_url("proxy", sig_base64, url_base64, filename) end - def build_preview_url(sig_base64, url_base64, filename \\ nil) do - proxy_url("proxy/preview", sig_base64, url_base64, filename) + def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do + uri = proxy_url("proxy/preview", sig_base64, url_base64, filename) + + UriHelper.append_uri_params(uri, preview_params) end def verify_request_path_and_url( diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 961c73666..9dc76e928 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -67,9 +67,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - # TODO: find a workaround so avatar_static and header_static can work. - # Those only permit GIFs for animation, so we have to permit a way to - # allow those to get real static variants. + defp handle_preview( + "image/" <> _ = _content_type, + %{params: %{"output_format" => "jpeg"}} = conn, + url + ) do + handle_jpeg_preview(conn, url) + end + defp handle_preview("image/gif" = _content_type, conn, url) do mediaproxy_url = url |> MediaProxy.url() -- cgit v1.2.3 From 6141eb94ab034b5141a5c60b2814fb45b829c1ac Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Sep 2020 12:40:42 -0500 Subject: Fetch preview requests through the MediaProxy. Separate connection options are not needed. Use a separate pool for preview requests --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 961c73666..b1f00fa0c 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -33,8 +33,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do def preview(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, - {:ok, url} <- MediaProxy.decode_url(sig64, url64), - :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + {:ok, url} <- MediaProxy.decode_url(sig64, url64) do handle_preview(conn, url) else {:enabled, false} -> @@ -50,9 +49,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp handle_preview(conn, url) do with {:ok, %{status: status} = head_response} when status in 200..299 <- - Tesla.head(url, - opts: [adapter: [timeout: preview_head_request_timeout(), follow_redirect: true]] - ) do + Pleroma.HTTP.request("head", MediaProxy.url(url), [], [], [adapter: [pool: :preview]]) do content_type = Tesla.get_header(head_response, "content-type") handle_preview(content_type, conn, url) else @@ -172,17 +169,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do {thumbnail_max_width, thumbnail_max_height} end - defp preview_head_request_timeout do - Keyword.get(media_preview_proxy_opts(), :head_request_max_read_duration) || - Keyword.get(media_proxy_opts(), :max_read_duration) || - ReverseProxy.max_read_duration_default() - end - defp media_proxy_opts do Config.get([:media_proxy, :proxy_opts], []) end - - defp media_preview_proxy_opts do - Config.get([:media_preview_proxy, :proxy_opts], []) - end end -- cgit v1.2.3 From f25b0e87f3dd73e02c954c5baab3c52becdd9c9e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Sep 2020 15:28:57 -0500 Subject: URL passed to helper is already MediaProxy Set :preview pool on the request --- lib/pleroma/helpers/media_helper.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 9bd815c26..cfb091f82 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -12,8 +12,7 @@ defmodule Pleroma.Helpers.MediaHelper do def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), - url = Pleroma.Web.MediaProxy.url(url), - {:ok, env} <- Pleroma.HTTP.get(url), + {:ok, env} <- Pleroma.HTTP.get(url, [], [adapter: [pool: :preview]]), {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) @@ -61,8 +60,7 @@ defmodule Pleroma.Helpers.MediaHelper do def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), - url = Pleroma.Web.MediaProxy.url(url), - {:ok, env} <- Pleroma.HTTP.get(url), + {:ok, env} <- Pleroma.HTTP.get(url, [], [adapter: [pool: :preview]]), {:ok, fifo_path} <- mkfifo(), args = [ "-y", -- cgit v1.2.3 From d34fe2840d969c30b393cfb73e34b6301027c776 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 3 Sep 2020 23:15:22 +0300 Subject: HTTP: radically simplify pool checkin/checkout Use a custom tesla middleware instead of adapter helper function + custom redirect middleware. This will also fix "Client died before releasing the connection" messages when the request pool is overloaded. Since the checkout is now done after passing ConcurrentLimiter. This is technically less efficient, since the connection needs to be checked in/out every time the middleware is left or entered respectively. But I don't think the nanoseconds we might lose on redirects to the same host are worth the complexity. --- lib/pleroma/http/adapter_helper.ex | 4 - lib/pleroma/http/adapter_helper/gun.ex | 9 -- lib/pleroma/http/adapter_helper/hackney.ex | 3 - lib/pleroma/http/http.ex | 39 ++++---- lib/pleroma/tesla/middleware/connection_pool.ex | 35 ++++++++ lib/pleroma/tesla/middleware/follow_redirects.ex | 110 ----------------------- 6 files changed, 51 insertions(+), 149 deletions(-) create mode 100644 lib/pleroma/tesla/middleware/connection_pool.ex delete mode 100644 lib/pleroma/tesla/middleware/follow_redirects.ex (limited to 'lib') diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 0728cbaa2..d72297323 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -19,7 +19,6 @@ defmodule Pleroma.HTTP.AdapterHelper do | {Connection.proxy_type(), Connection.host(), pos_integer()} @callback options(keyword(), URI.t()) :: keyword() - @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil def format_proxy(nil), do: nil @@ -47,9 +46,6 @@ defmodule Pleroma.HTTP.AdapterHelper do |> adapter_helper().options(uri) end - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) - defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 02e20f2d1..4a967d8f2 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -6,7 +6,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @behaviour Pleroma.HTTP.AdapterHelper alias Pleroma.Config - alias Pleroma.Gun.ConnectionPool alias Pleroma.HTTP.AdapterHelper require Logger @@ -57,14 +56,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do Config.get([:pools, pool, :timeout], default) end - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts) do - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)} - err -> err - end - end - @prefix Pleroma.Gun.ConnectionPool def limiter_setup do wait = Config.get([:connections_pool, :connection_acquisition_wait]) diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index cd569422b..f47a671ad 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -23,7 +23,4 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do end defp add_scheme_opts(opts, _), do: opts - - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} - def get_conn(_uri, opts), do: {:ok, opts} end diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index b37b3fa89..7bc73f4a0 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -62,28 +62,21 @@ defmodule Pleroma.HTTP do uri = URI.parse(url) adapter_opts = AdapterHelper.options(uri, options[:adapter] || []) - case AdapterHelper.get_conn(uri, adapter_opts) do - {:ok, adapter_opts} -> - options = put_in(options[:adapter], adapter_opts) - params = options[:params] || [] - request = build_request(method, headers, options, url, body, params) - - adapter = Application.get_env(:tesla, :adapter) - - client = Tesla.client(adapter_middlewares(adapter), adapter) - - maybe_limit( - fn -> - request(client, request) - end, - adapter, - adapter_opts - ) - - # Connection release is handled in a custom FollowRedirects middleware - err -> - err - end + options = put_in(options[:adapter], adapter_opts) + params = options[:params] || [] + request = build_request(method, headers, options, url, body, params) + + adapter = Application.get_env(:tesla, :adapter) + + client = Tesla.client(adapter_middlewares(adapter), adapter) + + maybe_limit( + fn -> + request(client, request) + end, + adapter, + adapter_opts + ) end @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} @@ -110,7 +103,7 @@ defmodule Pleroma.HTTP do end defp adapter_middlewares(Tesla.Adapter.Gun) do - [Pleroma.HTTP.Middleware.FollowRedirects] + [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] end defp adapter_middlewares(_), do: [] diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex new file mode 100644 index 000000000..a435ab4cc --- /dev/null +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Tesla.Middleware.ConnectionPool do + @moduledoc """ + Middleware to get/release connections from `Pleroma.Gun.ConnectionPool` + """ + + @behaviour Tesla.Middleware + + alias Pleroma.Gun.ConnectionPool + + @impl Tesla.Middleware + def call(%Tesla.Env{url: url, opts: opts} = env, next, _) do + uri = URI.parse(url) + + case ConnectionPool.get_conn(uri, opts[:adapter]) do + {:ok, conn_pid} -> + adapter_opts = Keyword.merge(opts[:adapter], conn: conn_pid, close_conn: false) + opts = Keyword.put(opts, :adapter, adapter_opts) + env = %{env | opts: opts} + res = Tesla.run(env, next) + + unless opts[:adapter][:body_as] == :chunks do + ConnectionPool.release_conn(conn_pid) + end + + res + + err -> + err + end + end +end diff --git a/lib/pleroma/tesla/middleware/follow_redirects.ex b/lib/pleroma/tesla/middleware/follow_redirects.ex deleted file mode 100644 index 5a7032215..000000000 --- a/lib/pleroma/tesla/middleware/follow_redirects.ex +++ /dev/null @@ -1,110 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2015-2020 Tymon Tobolski -# Copyright © 2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.Middleware.FollowRedirects do - @moduledoc """ - Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex - - Follow 3xx redirects - ## Options - - `:max_redirects` - limit number of redirects (default: `5`) - """ - - alias Pleroma.Gun.ConnectionPool - - @behaviour Tesla.Middleware - - @max_redirects 5 - @redirect_statuses [301, 302, 303, 307, 308] - - @impl Tesla.Middleware - def call(env, next, opts \\ []) do - max = Keyword.get(opts, :max_redirects, @max_redirects) - - redirect(env, next, max) - end - - defp redirect(env, next, left) do - opts = env.opts[:adapter] - - case Tesla.run(env, next) do - {:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 -> - release_conn(opts) - - case Tesla.get_header(res, "location") do - nil -> - {:ok, res} - - location -> - location = parse_location(location, res) - - case get_conn(location, opts) do - {:ok, opts} -> - %{env | opts: Keyword.put(env.opts, :adapter, opts)} - |> new_request(res.status, location) - |> redirect(next, left - 1) - - e -> - e - end - end - - {:ok, %{status: status}} when status in @redirect_statuses -> - release_conn(opts) - {:error, {__MODULE__, :too_many_redirects}} - - {:error, _} = e -> - release_conn(opts) - e - - other -> - unless opts[:body_as] == :chunks do - release_conn(opts) - end - - other - end - end - - defp get_conn(location, opts) do - uri = URI.parse(location) - - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn} -> - {:ok, Keyword.merge(opts, conn: conn)} - - e -> - e - end - end - - defp release_conn(opts) do - ConnectionPool.release_conn(opts[:conn]) - end - - # The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally - # requested resource is not available, however a related resource (or another redirect) - # available via GET is available at the specified location. - # https://tools.ietf.org/html/rfc7231#section-6.4.4 - defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []} - - # The 307 (Temporary Redirect) status code indicates that the target - # resource resides temporarily under a different URI and the user agent - # MUST NOT change the request method (...) - # https://tools.ietf.org/html/rfc7231#section-6.4.7 - defp new_request(env, 307, location), do: %{env | url: location} - - defp new_request(env, _, location), do: %{env | url: location, query: []} - - defp parse_location("https://" <> _rest = location, _env), do: location - defp parse_location("http://" <> _rest = location, _env), do: location - - defp parse_location(location, env) do - env.url - |> URI.parse() - |> URI.merge(location) - |> URI.to_string() - end -end -- cgit v1.2.3 From 8bd2b6eb138ace3408a03c78ecc339fc35b19f10 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 4 Sep 2020 14:24:15 +0300 Subject: temp hackney fix --- lib/pleroma/http/adapter_helper/hackney.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index f47a671ad..42e3acfec 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -22,5 +22,9 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) end + defp add_scheme_opts(opts, %URI{scheme: "https"}) do + Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1]) + end + defp add_scheme_opts(opts, _), do: opts end -- cgit v1.2.3 From 473458b0fbecb05121b235f525aefcef34f0409e Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 4 Sep 2020 14:45:30 +0300 Subject: fix for ReverseProxy --- lib/pleroma/reverse_proxy/client/hackney.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index e84118a90..ad988fac3 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -7,6 +7,7 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @impl true def request(method, url, headers, body, opts \\ []) do + opts = Keyword.put(opts, :ssl_options, versions: [:"tlsv1.2", :"tlsv1.1", :tlsv1]) :hackney.request(method, url, headers, body, opts) end -- cgit v1.2.3 From 10da13c71343623a5e52beebdc6abc1f400bc40d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 4 Sep 2020 22:10:40 +0300 Subject: ConnectionPool middleware: Fix connection leak on ReverseProxy redirects Requires a patched Tesla due to upstream not saving opts between redirects, patch submitted at https://github.com/teamon/tesla/pull/414 --- lib/pleroma/tesla/middleware/connection_pool.ex | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex index a435ab4cc..5909e98d6 100644 --- a/lib/pleroma/tesla/middleware/connection_pool.ex +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -15,21 +15,32 @@ defmodule Pleroma.Tesla.Middleware.ConnectionPool do def call(%Tesla.Env{url: url, opts: opts} = env, next, _) do uri = URI.parse(url) + # Avoid leaking connections when the middleware is called twice + # with body_as: :chunks. We assume only the middleware can set + # opts[:adapter][:conn] + if opts[:adapter][:conn] do + ConnectionPool.release_conn(opts[:adapter][:conn]) + end + case ConnectionPool.get_conn(uri, opts[:adapter]) do {:ok, conn_pid} -> adapter_opts = Keyword.merge(opts[:adapter], conn: conn_pid, close_conn: false) opts = Keyword.put(opts, :adapter, adapter_opts) env = %{env | opts: opts} - res = Tesla.run(env, next) - unless opts[:adapter][:body_as] == :chunks do - ConnectionPool.release_conn(conn_pid) + case Tesla.run(env, next) do + {:ok, env} -> + unless opts[:adapter][:body_as] == :chunks do + ConnectionPool.release_conn(conn_pid) + {:ok, pop_in(env[:opts][:adapter][:conn])} + else + {:ok, env} + end + + err -> + ConnectionPool.release_conn(conn_pid) + err end - - res - - err -> - err end end end -- cgit v1.2.3 From 0d91f65284f99bded89c0400e976e0ffa5bc202f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Sep 2020 07:52:22 +0200 Subject: Prevent AccountView and instance.get_or_update_favicon fails --- lib/pleroma/instances/instance.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 711c42158..ef5d17de4 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -145,6 +145,8 @@ defmodule Pleroma.Instances.Instance do favicon end + rescue + _ -> nil end defp scrape_favicon(%URI{} = instance_uri) do @@ -159,7 +161,8 @@ defmodule Pleroma.Instances.Instance do |> Floki.attribute("link[rel=icon]", "href") |> List.first(), favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), - true <- is_binary(favicon) do + true <- is_binary(favicon), + true <- String.length(favicon) <= 255 do favicon else _ -> nil -- cgit v1.2.3 From c3b02341bf4ab610e9425d6811dca057e9f811a4 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 5 Sep 2020 16:16:35 +0300 Subject: [#2497] Made media preview proxy fall back to media proxy instead of to source url. Adjusted tests. Refactoring. --- lib/pleroma/helpers/media_helper.ex | 6 ++- lib/pleroma/web/media_proxy/media_proxy.ex | 4 +- .../web/media_proxy/media_proxy_controller.ex | 50 ++++++++++++---------- 3 files changed, 32 insertions(+), 28 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index cfb091f82..bb93d4915 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -7,12 +7,14 @@ defmodule Pleroma.Helpers.MediaHelper do Handles common media-related operations. """ + alias Pleroma.HTTP + @tmp_base "/tmp/pleroma-media_preview-pipe" def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), - {:ok, env} <- Pleroma.HTTP.get(url, [], [adapter: [pool: :preview]]), + {:ok, env} <- HTTP.get(url, [], adapter: [pool: :preview]), {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) @@ -60,7 +62,7 @@ defmodule Pleroma.Helpers.MediaHelper do def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), - {:ok, env} <- Pleroma.HTTP.get(url, [], [adapter: [pool: :preview]]), + {:ok, env} <- HTTP.get(url, [], adapter: [pool: :preview]), {:ok, fifo_path} <- mkfifo(), args = [ "-y", diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 4cbe1cf89..80017cde1 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -57,13 +57,11 @@ defmodule Pleroma.Web.MediaProxy do end end - # Note: routing all URLs to preview handler (even local and whitelisted). - # Preview handler will call url/1 on decoded URLs, and applicable ones will detour media proxy. def preview_url(url, preview_params \\ []) do if preview_enabled?() do encode_preview_url(url, preview_params) else - url + url(url) end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 33daa1e05..469fbae59 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -48,10 +48,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_preview(conn, url) do + media_proxy_url = MediaProxy.url(url) + with {:ok, %{status: status} = head_response} when status in 200..299 <- - Pleroma.HTTP.request("head", MediaProxy.url(url), [], [], [adapter: [pool: :preview]]) do + Pleroma.HTTP.request("head", media_proxy_url, [], [], adapter: [pool: :preview]) do content_type = Tesla.get_header(head_response, "content-type") - handle_preview(content_type, conn, url) + handle_preview(content_type, conn, media_proxy_url) else {_, %{status: status}} -> send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") @@ -67,40 +69,38 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp handle_preview( "image/" <> _ = _content_type, %{params: %{"output_format" => "jpeg"}} = conn, - url + media_proxy_url ) do - handle_jpeg_preview(conn, url) + handle_jpeg_preview(conn, media_proxy_url) end - defp handle_preview("image/gif" = _content_type, conn, url) do - mediaproxy_url = url |> MediaProxy.url() - - redirect(conn, external: mediaproxy_url) + defp handle_preview("image/gif" = _content_type, conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) end - defp handle_preview("image/png" <> _ = _content_type, conn, url) do - handle_png_preview(conn, url) + defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do + handle_png_preview(conn, media_proxy_url) end - defp handle_preview("image/" <> _ = _content_type, conn, url) do - handle_jpeg_preview(conn, url) + defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do + handle_jpeg_preview(conn, media_proxy_url) end - defp handle_preview("video/" <> _ = _content_type, conn, url) do - handle_video_preview(conn, url) + defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do + handle_video_preview(conn, media_proxy_url) end - defp handle_preview(content_type, conn, _url) do + defp handle_preview(content_type, conn, _media_proxy_url) do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp handle_png_preview(%{params: params} = conn, url) do + defp handle_png_preview(%{params: params} = conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), {:ok, thumbnail_binary} <- MediaHelper.image_resize( - url, + media_proxy_url, %{ max_width: thumbnail_max_width, max_height: thumbnail_max_height, @@ -109,7 +109,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do } ) do conn - |> put_preview_response_headers("image/png", "preview.png") + |> put_preview_response_headers(["image/png", "preview.png"]) |> send_resp(200, thumbnail_binary) else _ -> @@ -117,13 +117,13 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp handle_jpeg_preview(%{params: params} = conn, url) do + defp handle_jpeg_preview(%{params: params} = conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), {:ok, thumbnail_binary} <- MediaHelper.image_resize( - url, + media_proxy_url, %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} ) do conn @@ -135,9 +135,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp handle_video_preview(conn, url) do + defp handle_video_preview(conn, media_proxy_url) do with {:ok, thumbnail_binary} <- - MediaHelper.video_framegrab(url) do + MediaHelper.video_framegrab(media_proxy_url) do conn |> put_preview_response_headers() |> send_resp(200, thumbnail_binary) @@ -147,10 +147,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp put_preview_response_headers(conn, content_type \\ "image/jpeg", filename \\ "preview.jpg") do + defp put_preview_response_headers( + conn, + [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"] + ) do conn |> put_resp_header("content-type", content_type) |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") + # TODO: enable caching |> put_resp_header("cache-control", "max-age=0, private, must-revalidate") end -- cgit v1.2.3 From f170d471307ba0082b98351190b3d6b808bdfe1a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 5 Sep 2020 20:19:09 +0300 Subject: [#2497] Adjusted media proxy preview invalidation. Allowed client-side caching for media preview. Adjusted prewarmer to fetch only proxiable URIs. Removed :preview pool in favor of existing :media one. Misc. refactoring. --- lib/pleroma/helpers/media_helper.ex | 4 ++-- lib/pleroma/reverse_proxy/reverse_proxy.ex | 1 + .../activity_pub/mrf/media_proxy_warming_policy.ex | 27 +++++++++++++--------- lib/pleroma/web/media_proxy/invalidation.ex | 4 +++- lib/pleroma/web/media_proxy/media_proxy.ex | 20 ++++++++-------- .../web/media_proxy/media_proxy_controller.ex | 5 ++-- 6 files changed, 34 insertions(+), 27 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index bb93d4915..a1205e10d 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Helpers.MediaHelper do def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), - {:ok, env} <- HTTP.get(url, [], adapter: [pool: :preview]), + {:ok, env} <- HTTP.get(url, [], adapter: [pool: :media]), {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) @@ -62,7 +62,7 @@ defmodule Pleroma.Helpers.MediaHelper do def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), - {:ok, env} <- HTTP.get(url, [], adapter: [pool: :preview]), + {:ok, env} <- HTTP.get(url, [], adapter: [pool: :media]), {:ok, fifo_path} <- mkfifo(), args = [ "-y", diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 35637e934..8ae1157df 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -18,6 +18,7 @@ defmodule Pleroma.ReverseProxy do @methods ~w(GET HEAD) def max_read_duration_default, do: @max_read_duration + def default_cache_control_header, do: @default_cache_control_header @moduledoc """ A reverse proxy. diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 5d8bb72aa..1050b74ba 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -12,23 +12,28 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger - @options [ + @adapter_options [ pool: :media ] def perform(:prefetch, url) do - Logger.debug("Prefetching #{inspect(url)}") + # Fetching only proxiable resources + if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do + # If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests) + prefetch_url = MediaProxy.preview_url(url) - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@options, :recv_timeout, 10_000) - else - @options - end + Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}") - url - |> MediaProxy.preview_url() - |> HTTP.get([], adapter: opts) + HTTP.get(prefetch_url, [], adapter: adapter_options()) + end + end + + defp adapter_options do + if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do + Keyword.put(@adapter_options, :recv_timeout, 10_000) + else + @adapter_options + end end def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex index 5808861e6..4f4340478 100644 --- a/lib/pleroma/web/media_proxy/invalidation.ex +++ b/lib/pleroma/web/media_proxy/invalidation.ex @@ -33,6 +33,8 @@ defmodule Pleroma.Web.MediaProxy.Invalidation do def prepare_urls(urls) do urls |> List.wrap() - |> Enum.map(&MediaProxy.url/1) + |> Enum.map(fn url -> [MediaProxy.url(url), MediaProxy.preview_url(url)] end) + |> List.flatten() + |> Enum.uniq() end end diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 80017cde1..ba553998b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -41,20 +41,16 @@ defmodule Pleroma.Web.MediaProxy do def url("/" <> _ = url), do: url def url(url) do - if not enabled?() or not url_proxiable?(url) do - url - else + if enabled?() and url_proxiable?(url) do encode_url(url) + else + url end end @spec url_proxiable?(String.t()) :: boolean() def url_proxiable?(url) do - if local?(url) or whitelisted?(url) do - false - else - true - end + not local?(url) and not whitelisted?(url) end def preview_url(url, preview_params \\ []) do @@ -69,7 +65,7 @@ defmodule Pleroma.Web.MediaProxy do # Note: media proxy must be enabled for media preview proxy in order to load all # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved. - def preview_enabled?, do: enabled?() and Config.get([:media_preview_proxy, :enabled], false) + def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled]) def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) @@ -138,9 +134,13 @@ defmodule Pleroma.Web.MediaProxy do if path = URI.parse(url_or_path).path, do: Path.basename(path) end + def base_url do + Config.get([:media_proxy, :base_url], Web.base_url()) + end + defp proxy_url(path, sig_base64, url_base64, filename) do [ - Config.get([:media_proxy, :base_url], Web.base_url()), + base_url(), path, sig_base64, url_base64, diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 469fbae59..89f4a23bd 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do media_proxy_url = MediaProxy.url(url) with {:ok, %{status: status} = head_response} when status in 200..299 <- - Pleroma.HTTP.request("head", media_proxy_url, [], [], adapter: [pool: :preview]) do + Pleroma.HTTP.request("head", media_proxy_url, [], [], adapter: [pool: :media]) do content_type = Tesla.get_header(head_response, "content-type") handle_preview(content_type, conn, media_proxy_url) else @@ -154,8 +154,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do conn |> put_resp_header("content-type", content_type) |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") - # TODO: enable caching - |> put_resp_header("cache-control", "max-age=0, private, must-revalidate") + |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header()) end defp thumbnail_max_dimensions(params) do -- cgit v1.2.3 From 88a6ee4a5989036de5c1e82c6111291887597d98 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 5 Sep 2020 20:23:18 +0300 Subject: [#2497] Func defs grouping fix. --- .../web/activity_pub/mrf/media_proxy_warming_policy.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 1050b74ba..6c63fe15c 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -16,6 +16,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do pool: :media ] + defp adapter_options do + if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do + Keyword.put(@adapter_options, :recv_timeout, 10_000) + else + @adapter_options + end + end + def perform(:prefetch, url) do # Fetching only proxiable resources if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do @@ -28,14 +36,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do end end - defp adapter_options do - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@adapter_options, :recv_timeout, 10_000) - else - @adapter_options - end - end - def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do Enum.each(attachments, fn %{"url" => url} when is_list(url) -> -- cgit v1.2.3 From e198ba492e5cb1b6ff81775db08298bfcdf1454a Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 5 Sep 2020 12:37:27 +0300 Subject: Rich Media: Do not cache URLs for preview statuses Closes #1987 --- lib/pleroma/html.ex | 34 ++++++++++++++++++++-------------- lib/pleroma/web/rich_media/helpers.ex | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 20b02f091..43e9145be 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -100,21 +100,27 @@ defmodule Pleroma.HTML do end) end - def extract_first_external_url(_, nil), do: {:error, "No content"} + def extract_first_external_url_from_object(%{data: %{"content" => content}} = object) + when is_binary(content) do + unless object.data["fake"] do + key = "URL|#{object.id}" + + Cachex.fetch!(:scrubber_cache, key, fn _key -> + {:commit, {:ok, extract_first_external_url(content)}} + end) + else + {:ok, extract_first_external_url(content)} + end + end - def extract_first_external_url(object, content) do - key = "URL|#{object.id}" + def extract_first_external_url_from_object(_), do: {:error, :no_content} - Cachex.fetch!(:scrubber_cache, key, fn _key -> - result = - content - |> Floki.parse_fragment!() - |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") - |> Enum.take(1) - |> Floki.attribute("href") - |> Enum.at(0) - - {:commit, {:ok, result}} - end) + def extract_first_external_url(content) do + content + |> Floki.parse_fragment!() + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") + |> Enum.at(0) end end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 2fb482b51..752ca9f81 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -58,7 +58,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do with true <- Config.get([:rich_media, :enabled]), false <- object.data["sensitive"] || false, {:ok, page_url} <- - HTML.extract_first_external_url(object, object.data["content"]), + HTML.extract_first_external_url_from_object(object), :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} -- cgit v1.2.3 From 5298de3be6683022fa53cc011dd567e8b2a706b9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 5 Sep 2020 21:17:03 +0300 Subject: ConnectionPool middleware: fix a crash due to unimplemented behaviour Structs don't implement Access behaviour, so this crashed. Tests didn't catch it and I didn't test that part of the codepath. Very sorry --- lib/pleroma/tesla/middleware/connection_pool.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex index 5909e98d6..049db6eb3 100644 --- a/lib/pleroma/tesla/middleware/connection_pool.ex +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -32,7 +32,7 @@ defmodule Pleroma.Tesla.Middleware.ConnectionPool do {:ok, env} -> unless opts[:adapter][:body_as] == :chunks do ConnectionPool.release_conn(conn_pid) - {:ok, pop_in(env[:opts][:adapter][:conn])} + {:ok, pop_in(env.opts[:adapter][:conn])} else {:ok, env} end -- cgit v1.2.3 From 9d6aca5bee6f90f3c0af5a5353f052108c9def62 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 5 Sep 2020 21:27:06 +0300 Subject: ConnectionPool: fix the previous hotfix I rushed the hotfix and forgot how `pop_in` actually works, I want to die. We need some integration tests for the HTTP client --- lib/pleroma/tesla/middleware/connection_pool.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex index 049db6eb3..2c5a2b53b 100644 --- a/lib/pleroma/tesla/middleware/connection_pool.ex +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -32,7 +32,8 @@ defmodule Pleroma.Tesla.Middleware.ConnectionPool do {:ok, env} -> unless opts[:adapter][:body_as] == :chunks do ConnectionPool.release_conn(conn_pid) - {:ok, pop_in(env.opts[:adapter][:conn])} + {_, res} = pop_in(env.opts[:adapter][:conn]) + {:ok, res} else {:ok, env} end -- cgit v1.2.3 From 129a2f48df95ddd85fceee741a9991a6e092ed3d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 5 Sep 2020 21:36:17 +0300 Subject: ConnectionPool middleware: handle connection opening errors --- lib/pleroma/tesla/middleware/connection_pool.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex index 2c5a2b53b..056e736ce 100644 --- a/lib/pleroma/tesla/middleware/connection_pool.ex +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -42,6 +42,9 @@ defmodule Pleroma.Tesla.Middleware.ConnectionPool do ConnectionPool.release_conn(conn_pid) err end + + err -> + err end end end -- cgit v1.2.3 From 170599c390e7c82bdff0d4180d04b2f0f3906f35 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 5 Sep 2020 22:00:51 +0300 Subject: RichMedia: do not log webpages missing metadata as errors Also fixes the return value of Parser.parse on errors, previously was just `:ok` due to the logger call in the end --- lib/pleroma/web/rich_media/parser.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index e98c743ca..5727fda18 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -21,8 +21,13 @@ defmodule Pleroma.Web.RichMedia.Parser do {:ok, _} <- set_ttl_based_on_image(data, url) do {:ok, data} else + {:error, {:invalid_metadata, data}} = e -> + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + e + error -> - Logger.error(fn -> "Rich media error: #{inspect(error)}" end) + Logger.error(fn -> "Rich media error for #{url}: #{inspect(error)}" end) + error end end @@ -90,7 +95,7 @@ defmodule Pleroma.Web.RichMedia.Parser do end) end - defp parse_url(url) do + def parse_url(url) do with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), {:ok, html} <- Floki.parse_document(html) do html @@ -116,7 +121,7 @@ defmodule Pleroma.Web.RichMedia.Parser do end defp check_parsed_data(data) do - {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} + {:error, {:invalid_metadata, data}} end defp clean_parsed_data(data) do -- cgit v1.2.3 From 5ae56aafb2edc737f7e9fb36e00377815f028ce6 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sun, 6 Sep 2020 21:42:51 +0300 Subject: added import mutes --- lib/pleroma/user.ex | 51 ------------ lib/pleroma/user/import.ex | 91 ++++++++++++++++++++++ .../controllers/user_import_controller.ex | 57 ++++++++++++++ lib/pleroma/web/router.ex | 7 +- .../web/twitter_api/controllers/util_controller.ex | 35 --------- lib/pleroma/workers/background_worker.ex | 11 ++- 6 files changed, 161 insertions(+), 91 deletions(-) create mode 100644 lib/pleroma/user/import.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 94c96de8d..be2ef0d1b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1686,42 +1686,6 @@ defmodule Pleroma.User do def perform(:deactivate_async, user, status), do: deactivate(user, status) - @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} - def perform(:blocks_import, %User{} = blocker, blocked_identifiers) - when is_list(blocked_identifiers) do - Enum.map( - blocked_identifiers, - fn blocked_identifier -> - with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, _block} <- CommonAPI.block(blocker, blocked) do - blocked - else - err -> - Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") - err - end - end - ) - end - - def perform(:follow_import, %User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - Enum.map( - followed_identifiers, - fn followed_identifier -> - with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier), - {:ok, follower} <- maybe_direct_follow(follower, followed), - {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do - followed - else - err -> - Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") - err - end - end - ) - end - @spec external_users_query() :: Ecto.Query.t() def external_users_query do User.Query.build(%{ @@ -1750,21 +1714,6 @@ defmodule Pleroma.User do Repo.all(query) end - def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do - BackgroundWorker.enqueue("blocks_import", %{ - "blocker_id" => blocker.id, - "blocked_identifiers" => blocked_identifiers - }) - end - - def follow_import(%User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - BackgroundWorker.enqueue("follow_import", %{ - "follower_id" => follower.id, - "followed_identifiers" => followed_identifiers - }) - end - def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do Notification |> join(:inner, [n], activity in assoc(n, :activity)) diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex new file mode 100644 index 000000000..de27bdc4c --- /dev/null +++ b/lib/pleroma/user/import.ex @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.Import do + use Ecto.Schema + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.BackgroundWorker + + require Logger + + @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} + def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), + {:ok, _} <- User.mute(user, muted_user) do + muted_user + else + error -> handle_error(:mutes_import, identifier, error) + end + end + ) + end + + def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), + {:ok, _block} <- CommonAPI.block(blocker, blocked) do + blocked + else + error -> handle_error(:blocks_import, identifier, error) + end + end + ) + end + + def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), + {:ok, follower} <- User.maybe_direct_follow(follower, followed), + {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do + followed + else + error -> handle_error(:follow_import, identifier, error) + end + end + ) + end + + def perform(_, _, _), do: :ok + + defp handle_error(op, user_id, error) do + Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") + error + end + + def blocks_import(%User{} = blocker, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "blocks_import", + %{ + "blocker_id" => blocker.id, + "blocked_identifiers" => identifiers + } + ) + end + + def follow_import(%User{} = follower, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "follow_import", + %{ + "follower_id" => follower.id, + "followed_identifiers" => identifiers + } + ) + end + + def mutes_import(%User{} = user, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "mutes_import", + %{"user_id" => user.id, "identifiers" => identifiers} + ) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex new file mode 100644 index 000000000..df6a0f131 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -0,0 +1,57 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) + + def follow(conn, %{"list" => %Plug.Upload{path: path}}) do + follow(conn, %{"list" => File.read!(path)}) + end + + def follow(%{assigns: %{user: follower}} = conn, %{"list" => list}) do + identifiers = + list + |> String.split("\n") + |> Enum.map(&(&1 |> String.split(",") |> List.first())) + |> List.delete("Account address") + |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) + |> Enum.reject(&(&1 == "")) + + User.Import.follow_import(follower, identifiers) + json(conn, "job started") + end + + def blocks(conn, %{"list" => %Plug.Upload{path: path}}) do + blocks(conn, %{"list" => File.read!(path)}) + end + + def blocks(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do + User.Import.blocks_import(blocker, prepare_user_identifiers(list)) + json(conn, "job started") + end + + def mutes(conn, %{"list" => %Plug.Upload{path: path}}) do + mutes(conn, %{"list" => File.read!(path)}) + end + + def mutes(%{assigns: %{user: user}} = conn, %{"list" => list}) do + User.Import.mutes_import(user, prepare_user_identifiers(list)) + json(conn, "job started") + end + + defp prepare_user_identifiers(list) do + list + |> String.split() + |> Enum.map(&String.trim_leading(&1, "@")) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c6433cc53..f69b1545f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -260,14 +260,15 @@ defmodule Pleroma.Web.Router do post("/delete_account", UtilController, :delete_account) put("/notification_settings", UtilController, :update_notificaton_settings) post("/disable_account", UtilController, :disable_account) - - post("/blocks_import", UtilController, :blocks_import) - post("/follow_import", UtilController, :follow_import) end scope "/api/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) + post("/mutes_import", UserImportController, :mutes) + post("/blocks_import", UserImportController, :blocks) + post("/follow_import", UserImportController, :follow) + get("/accounts/mfa", TwoFactorAuthenticationController, :settings) get("/accounts/mfa/backup_codes", TwoFactorAuthenticationController, :backup_codes) get("/accounts/mfa/setup/:method", TwoFactorAuthenticationController, :setup) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index f02c4075c..70b0fbd54 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -18,14 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) - plug( - OAuthScopesPlug, - %{scopes: ["follow", "write:follows"]} - when action == :follow_import - ) - - plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) - plug( OAuthScopesPlug, %{scopes: ["write:accounts"]} @@ -104,33 +96,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - follow_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do - followed_identifiers = - list - |> String.split("\n") - |> Enum.map(&(&1 |> String.split(",") |> List.first())) - |> List.delete("Account address") - |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) - |> Enum.reject(&(&1 == "")) - - User.follow_import(follower, followed_identifiers) - json(conn, "job started") - end - - def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - blocks_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do - blocked_identifiers = list |> String.split() |> Enum.map(&String.trim_leading(&1, "@")) - User.blocks_import(blocker, blocked_identifiers) - json(conn, "job started") - end - def change_password(%{assigns: %{user: user}} = conn, params) do case CommonAPI.Utils.confirm_current_password(user, params["password"]) do {:ok, user} -> diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index cec5a7462..f9c767ee0 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -34,7 +34,7 @@ defmodule Pleroma.Workers.BackgroundWorker do } }) do blocker = User.get_cached_by_id(blocker_id) - {:ok, User.perform(:blocks_import, blocker, blocked_identifiers)} + {:ok, User.Import.perform(:blocks_import, blocker, blocked_identifiers)} end def perform(%Job{ @@ -45,7 +45,14 @@ defmodule Pleroma.Workers.BackgroundWorker do } }) do follower = User.get_cached_by_id(follower_id) - {:ok, User.perform(:follow_import, follower, followed_identifiers)} + {:ok, User.Import.perform(:follow_import, follower, followed_identifiers)} + end + + def perform(%Job{ + args: %{"op" => "mutes_import", "user_id" => user_id, "identifiers" => identifiers} + }) do + user = User.get_cached_by_id(user_id) + {:ok, User.Import.perform(:mutes_import, user, identifiers)} end def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do -- cgit v1.2.3 From 917d325972e3aeb367583c61aaa109d62fcba837 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 7 Sep 2020 07:17:30 +0300 Subject: added api spec --- .../api_spec/operations/user_import_operation.ex | 80 ++++++++++++++++++++++ .../controllers/user_import_controller.ex | 22 +++--- 2 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/user_import_operation.ex (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex new file mode 100644 index 000000000..a50314fb7 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex @@ -0,0 +1,80 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.UserImportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def follow_operation do + %Operation{ + tags: ["follow_import"], + summary: "Imports your follows.", + operationId: "UserImportController.follow", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:follow"]}] + } + end + + def blocks_operation do + %Operation{ + tags: ["blocks_import"], + summary: "Imports your blocks.", + operationId: "UserImportController.blocks", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:blocks"]}] + } + end + + def mutes_operation do + %Operation{ + tags: ["mutes_import"], + summary: "Imports your mutes.", + operationId: "UserImportController.mutes", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:mutes"]}] + } + end + + defp import_request do + %Schema{ + type: :object, + required: [:list], + properties: %{ + list: %Schema{ + description: + "STRING or FILE containing a whitespace-separated list of accounts to import.", + anyOf: [ + %Schema{type: :string, format: :binary}, + %Schema{type: :string} + ] + } + } + } + end + + defp ok_response do + Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"}) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex index df6a0f131..f10c45750 100644 --- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -9,16 +9,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User + alias Pleroma.Web.ApiSpec plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow) plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) - def follow(conn, %{"list" => %Plug.Upload{path: path}}) do - follow(conn, %{"list" => File.read!(path)}) + plug(OpenApiSpex.Plug.CastAndValidate) + defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation + + def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) end - def follow(%{assigns: %{user: follower}} = conn, %{"list" => list}) do + def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do identifiers = list |> String.split("\n") @@ -31,20 +35,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do json(conn, "job started") end - def blocks(conn, %{"list" => %Plug.Upload{path: path}}) do - blocks(conn, %{"list" => File.read!(path)}) + def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) end - def blocks(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do + def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do User.Import.blocks_import(blocker, prepare_user_identifiers(list)) json(conn, "job started") end - def mutes(conn, %{"list" => %Plug.Upload{path: path}}) do - mutes(conn, %{"list" => File.read!(path)}) + def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) end - def mutes(%{assigns: %{user: user}} = conn, %{"list" => list}) do + def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do User.Import.mutes_import(user, prepare_user_identifiers(list)) json(conn, "job started") end -- cgit v1.2.3 From 08aef7dd4e054c5ed02e359b61fe57daad97fbde Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 5 Sep 2020 06:38:07 +0200 Subject: instance: Log catch favicon errors as warnings --- lib/pleroma/instances/instance.ex | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index ef5d17de4..8bf53c090 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -14,6 +14,8 @@ defmodule Pleroma.Instances.Instance do import Ecto.Query import Ecto.Changeset + require Logger + schema "instances" do field(:host, :string) field(:unreachable_since, :naive_datetime_usec) @@ -146,7 +148,9 @@ defmodule Pleroma.Instances.Instance do favicon end rescue - _ -> nil + e -> + Logger.warn("Instance.get_or_update_favicon(\"#{host}\") error: #{inspect(e)}") + nil end defp scrape_favicon(%URI{} = instance_uri) do @@ -161,14 +165,18 @@ defmodule Pleroma.Instances.Instance do |> Floki.attribute("link[rel=icon]", "href") |> List.first(), favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), - true <- is_binary(favicon), - true <- String.length(favicon) <= 255 do + true <- is_binary(favicon) do favicon else _ -> nil end rescue - _ -> nil + e -> + Logger.warn( + "Instance.scrape_favicon(\"#{to_string(instance_uri)}\") error: #{inspect(e)}" + ) + + nil end end end -- cgit v1.2.3 From ee67c98e550310813cfdb9242e5fab2e566e1e2a Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sun, 6 Sep 2020 12:13:26 +0300 Subject: removing Stats worker from Oban cron jobs --- lib/mix/pleroma.ex | 1 + lib/pleroma/application.ex | 1 + lib/pleroma/config/oban.ex | 30 +++++++++++++ lib/pleroma/stats.ex | 76 +++++++++++++++++++++++--------- lib/pleroma/workers/cron/stats_worker.ex | 17 ------- 5 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 lib/pleroma/config/oban.ex delete mode 100644 lib/pleroma/workers/cron/stats_worker.ex (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index fe9b0d16c..49ba2aae4 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -18,6 +18,7 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do Pleroma.Config.Holder.save_default() + Pleroma.Config.Oban.warn() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) if Pleroma.Config.get(:env) != :test do diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 33b1e3872..c39e24919 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -50,6 +50,7 @@ defmodule Pleroma.Application do Pleroma.Telemetry.Logger.attach() Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() + Pleroma.Config.Oban.warn() Config.DeprecationWarnings.warn() Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() Pleroma.ApplicationRequirements.verify!() diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex new file mode 100644 index 000000000..c2d56ebab --- /dev/null +++ b/lib/pleroma/config/oban.ex @@ -0,0 +1,30 @@ +defmodule Pleroma.Config.Oban do + require Logger + + def warn do + oban_config = Pleroma.Config.get(Oban) + + crontab = + [Pleroma.Workers.Cron.StatsWorker] + |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc -> + with acc when is_list(acc) <- acc, + setting when is_tuple(setting) <- + Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do + """ + !!!OBAN CONFIG WARNING!!! + You are using old workers in Oban crontab settings, which were removed. + Please, remove setting from crontab in your config file (prod.secret.exs): #{ + inspect(setting) + } + """ + |> Logger.warn() + + List.delete(acc, setting) + else + _ -> acc + end + end) + + Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab)) + end +end diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 9a03f01db..e7f8d272c 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -3,12 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Stats do + use GenServer + import Ecto.Query + alias Pleroma.CounterCache alias Pleroma.Repo alias Pleroma.User - use GenServer + @interval :timer.seconds(60) def start_link(_) do GenServer.start_link( @@ -18,6 +21,11 @@ defmodule Pleroma.Stats do ) end + @impl true + def init(_args) do + {:ok, nil, {:continue, :calculate_stats}} + end + @doc "Performs update stats" def force_update do GenServer.call(__MODULE__, :force_update) @@ -29,7 +37,11 @@ defmodule Pleroma.Stats do end @doc "Returns stats data" - @spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()} + @spec get_stats() :: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } def get_stats do %{stats: stats} = GenServer.call(__MODULE__, :get_state) @@ -44,25 +56,14 @@ defmodule Pleroma.Stats do peers end - def init(_args) do - {:ok, calculate_stat_data()} - end - - def handle_call(:force_update, _from, _state) do - new_stats = calculate_stat_data() - {:reply, new_stats, new_stats} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - def handle_cast(:run_update, _state) do - new_stats = calculate_stat_data() - - {:noreply, new_stats} - end - + @spec calculate_stat_data() :: %{ + peers: list(), + stats: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } + } def calculate_stat_data do peers = from( @@ -97,6 +98,7 @@ defmodule Pleroma.Stats do } end + @spec get_status_visibility_count(String.t() | nil) :: map() def get_status_visibility_count(instance \\ nil) do if is_nil(instance) do CounterCache.get_sum() @@ -104,4 +106,36 @@ defmodule Pleroma.Stats do CounterCache.get_by_instance(instance) end end + + @impl true + def handle_continue(:calculate_stats, _) do + stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, stats} + end + + @impl true + def handle_call(:force_update, _from, _state) do + new_stats = calculate_stat_data() + {:reply, new_stats, new_stats} + end + + @impl true + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_cast(:run_update, _state) do + new_stats = calculate_stat_data() + + {:noreply, new_stats} + end + + @impl true + def handle_info(:run_update, _) do + new_stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, new_stats} + end end diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex deleted file mode 100644 index 6a79540bc..000000000 --- a/lib/pleroma/workers/cron/stats_worker.ex +++ /dev/null @@ -1,17 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.StatsWorker do - @moduledoc """ - The worker to update peers statistics. - """ - - use Oban.Worker, queue: "background" - - @impl Oban.Worker - def perform(_job) do - Pleroma.Stats.do_collect() - :ok - end -end -- cgit v1.2.3 From a83916fdacac7b11ca478ef9a61b32dd269c8fd2 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 4 Sep 2020 19:05:08 +0300 Subject: adapter options unification not needed options deletion --- lib/mix/tasks/pleroma/frontend.ex | 4 +--- lib/pleroma/gun/conn.ex | 8 ++++---- lib/pleroma/http/adapter_helper.ex | 2 +- lib/pleroma/http/adapter_helper/gun.ex | 14 ++++++-------- lib/pleroma/http/adapter_helper/hackney.ex | 14 ++++++++++---- .../activity_pub/mrf/media_proxy_warming_policy.ex | 12 +++--------- lib/pleroma/web/rel_me.ex | 15 +++------------ lib/pleroma/web/rich_media/helpers.ex | 19 +++++-------------- 8 files changed, 33 insertions(+), 55 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 1957b1d84..73df67439 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -124,9 +124,7 @@ defmodule Mix.Tasks.Pleroma.Frontend do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], - adapter: [pool: :media, timeout: 120_000, recv_timeout: 120_000] - ) do + Pleroma.HTTP.get(url, [], adapter: [pool: :media, recv_timeout: 120_000]) do unzip(zip_body, dest) else e -> {:error, e} diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index a3f75a4bb..75b1ffc0a 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Gun.Conn do opts = opts |> Enum.into(%{}) - |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000) + |> Map.put_new(:connect_timeout, pool_opts[:connect_timeout] || 5_000) |> Map.put_new(:supervise, false) |> maybe_add_tls_opts(uri) @@ -50,7 +50,7 @@ defmodule Pleroma.Gun.Conn do with open_opts <- Map.delete(opts, :tls_opts), {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]), + {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]), stream <- Gun.connect(conn, connect_opts), {:response, :fin, 200, _} <- Gun.await(conn, stream) do {:ok, conn} @@ -88,7 +88,7 @@ defmodule Pleroma.Gun.Conn do |> Map.put(:socks_opts, socks_opts) with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do + {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]) do {:ok, conn} else error -> @@ -106,7 +106,7 @@ defmodule Pleroma.Gun.Conn do host = Pleroma.HTTP.AdapterHelper.parse_host(host) with {:ok, conn} <- Gun.open(host, port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do + {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]) do {:ok, conn} else error -> diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index d72297323..08b51578a 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do @moduledoc """ Configure Tesla.Client with default and customized adapter options. """ - @defaults [pool: :federation] + @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000] @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 4a967d8f2..1dbb71362 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -11,12 +11,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do require Logger @defaults [ - connect_timeout: 5_000, - domain_lookup_timeout: 5_000, - tls_handshake_timeout: 5_000, retry: 1, - retry_timeout: 1000, - await_up_timeout: 5_000 + retry_timeout: 1_000 ] @type pool() :: :federation | :upload | :media | :default @@ -45,15 +41,17 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do end defp put_timeout(opts) do + {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool])) # this is the timeout to receive a message from Gun - Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) + # `:timeout` key is used in Tesla + Keyword.put(opts, :timeout, recv_timeout) end @spec pool_timeout(pool()) :: non_neg_integer() def pool_timeout(pool) do - default = Config.get([:pools, :default, :timeout], 5_000) + default = Config.get([:pools, :default, :recv_timeout], 5_000) - Config.get([:pools, pool, :timeout], default) + Config.get([:pools, pool, :recv_timeout], default) end @prefix Pleroma.Gun.ConnectionPool diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index 42e3acfec..ef84553c1 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -2,11 +2,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper @defaults [ - connect_timeout: 10_000, - recv_timeout: 20_000, follow_redirect: true, - force_redirect: true, - pool: :federation + force_redirect: true ] @spec options(keyword(), URI.t()) :: keyword() @@ -19,6 +16,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do |> Keyword.merge(config_opts) |> Keyword.merge(connection_opts) |> add_scheme_opts(uri) + |> maybe_add_with_body() |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) end @@ -27,4 +25,12 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do end defp add_scheme_opts(opts, _), do: opts + + defp maybe_add_with_body(opts) do + if opts[:max_body] do + Keyword.put(opts, :with_body, true) + else + opts + end + end end diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index dfab105a3..a203405a0 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -13,22 +13,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger @options [ - pool: :media + pool: :media, + recv_timeout: 10_000 ] def perform(:prefetch, url) do Logger.debug("Prefetching #{inspect(url)}") - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@options, :recv_timeout, 10_000) - else - @options - end - url |> MediaProxy.url() - |> HTTP.get([], adapter: opts) + |> HTTP.get([], adapter: @options) end def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 8e2b51508..32bce3c1b 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.RelMe do @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] if Pleroma.Config.get(:env) == :test do @@ -23,18 +24,8 @@ defmodule Pleroma.Web.RelMe do def parse(_), do: {:error, "No URL provided"} defp parse_url(url) do - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@options, - recv_timeout: 2_000, - with_body: true - ) - else - @options - end - with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <- - Pleroma.HTTP.get(url, [], adapter: opts), + Pleroma.HTTP.get(url, [], adapter: @options), {:ok, html_tree} <- Floki.parse_document(html), data <- Floki.attribute(html_tree, "link[rel~=me]", "href") ++ diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 752ca9f81..084a66466 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -9,14 +9,15 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser - @rich_media_options [ + @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] @spec validate_page_url(URI.t() | binary()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld]) + validate_tld = Config.get([Pleroma.Formatter, :validate_tld]) page_url |> Linkify.Parser.url?(validate_tld: validate_tld) @@ -86,16 +87,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] - options = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@rich_media_options, - recv_timeout: 2_000, - with_body: true - ) - else - @rich_media_options - end - - Pleroma.HTTP.get(url, headers, adapter: options) + Pleroma.HTTP.get(url, headers, adapter: @options) end end -- cgit v1.2.3 From 696bf09433aa7f33cf580c71cb7f1f3367d4c124 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 7 Sep 2020 16:57:42 +0300 Subject: passing adapter options directly without adapter key --- lib/mix/tasks/pleroma/benchmark.ex | 11 ++++------- lib/mix/tasks/pleroma/frontend.ex | 2 +- lib/pleroma/http/ex_aws.ex | 2 +- lib/pleroma/http/http.ex | 2 +- lib/pleroma/http/tzdata.ex | 4 ++-- .../web/activity_pub/mrf/media_proxy_warming_policy.ex | 2 +- lib/pleroma/web/rel_me.ex | 2 +- lib/pleroma/web/rich_media/helpers.ex | 2 +- 8 files changed, 12 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index dd2b9c8f2..a607d5d4f 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -91,20 +91,17 @@ defmodule Mix.Tasks.Pleroma.Benchmark do "Without conn and without pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool, receive_conn: false] + pool: :no_pool, + receive_conn: false ) end, "Without conn and with pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [receive_conn: false] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], receive_conn: false) end, "With reused conn and without pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], pool: :no_pool) end, "With reused conn and with pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500") diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 73df67439..cbce81ab9 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -124,7 +124,7 @@ defmodule Mix.Tasks.Pleroma.Frontend do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], adapter: [pool: :media, recv_timeout: 120_000]) do + Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do unzip(zip_body, dest) else e -> {:error, e} diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex index c3f335c73..5cac3532f 100644 --- a/lib/pleroma/http/ex_aws.ex +++ b/lib/pleroma/http/ex_aws.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.ExAws do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do - http_opts = Keyword.put_new(http_opts, :adapter, pool: :upload) + http_opts = Keyword.put_new(http_opts, :pool, :upload) case HTTP.request(method, url, body, headers, http_opts) do {:ok, env} -> diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index 7bc73f4a0..052597191 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -60,7 +60,7 @@ defmodule Pleroma.HTTP do {:ok, Env.t()} | {:error, any()} def request(method, url, body, headers, options) when is_binary(url) do uri = URI.parse(url) - adapter_opts = AdapterHelper.options(uri, options[:adapter] || []) + adapter_opts = AdapterHelper.options(uri, options || []) options = put_in(options[:adapter], adapter_opts) params = options[:params] || [] diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 4539ac359..09cfdadf7 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} @@ -20,7 +20,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def head(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index a203405a0..98d595469 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do url |> MediaProxy.url() - |> HTTP.get([], adapter: @options) + |> HTTP.get([], @options) end def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 32bce3c1b..28f75b18d 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Web.RelMe do defp parse_url(url) do with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <- - Pleroma.HTTP.get(url, [], adapter: @options), + Pleroma.HTTP.get(url, [], @options), {:ok, html_tree} <- Floki.parse_document(html), data <- Floki.attribute(html_tree, "link[rel~=me]", "href") ++ diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 084a66466..bd7f03cbe 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -87,6 +87,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] - Pleroma.HTTP.get(url, headers, adapter: @options) + Pleroma.HTTP.get(url, headers, @options) end end -- cgit v1.2.3 From 18d21aed00dcbdaabd7db25b8b7d0c88141ec98a Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 7 Sep 2020 19:04:16 +0300 Subject: deprecation warnings --- lib/pleroma/config/deprecation_warnings.ex | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 0f52eb210..2bfe4ddba 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -56,6 +56,7 @@ defmodule Pleroma.Config.DeprecationWarnings do check_old_mrf_config() check_media_proxy_whitelist_config() check_welcome_message_config() + check_gun_pool_options() end def check_welcome_message_config do @@ -115,4 +116,46 @@ defmodule Pleroma.Config.DeprecationWarnings do """) end end + + def check_gun_pool_options do + pool_config = Config.get(:connections_pool) + + if timeout = pool_config[:await_up_timeout] do + Logger.warn(""" + !!!DEPRECATION WARNING!!! + Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """) + + Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout)) + end + + pools_configs = Config.get(:pools) + + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """ + + updated_config = + Enum.reduce(pools_configs, [], fn {pool_name, config}, acc -> + if timeout = config[:timeout] do + Keyword.put(acc, pool_name, Keyword.put_new(config, :recv_timeout, timeout)) + else + acc + end + end) + + if updated_config != [] do + pool_warnings = + updated_config + |> Keyword.keys() + |> Enum.map(fn pool_name -> + "\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`" + end) + + Logger.warn(Enum.join([warning_preface | pool_warnings])) + + Config.put(:pools, updated_config) + end + end end -- cgit v1.2.3 From 699224a900d54b6d32e0bd3f2abd9eccc523df11 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 7 Sep 2020 22:14:40 +0200 Subject: ForceBotUnlistedPolicy: initial add, tiny clean up from my previous version --- .../activity_pub/mrf/force_bot_unlisted_policy.ex | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex new file mode 100644 index 000000000..31fd90586 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do + alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF + @moduledoc "Remove bot posts from federated timeline" + + require Pleroma.Constants + + defp check_by_actor_type(user) do + if user.actor_type in ["Application", "Service"], do: 1.0, else: 0.0 + end + + defp check_by_nickname(user) do + if Regex.match?(~r/bot@|ebooks@/i, user.nickname), do: 1.0, else: 0.0 + end + + defp botness_score(user), do: check_by_actor_type(user) + check_by_nickname(user) + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message + ) do + user = User.get_cached_by_ap_id(actor) + isbot = 0.8 < botness_score(user) + + if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do + to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()] + + object = + object + |> Map.put("to", to) + |> Map.put("cc", cc) + + message = + message + |> Map.put("to", to) + |> Map.put("cc", cc) + |> Map.put("object", object) + + {:ok, message} + else + {:ok, message} + end + end + + @impl true + def filter(message), do: {:ok, message} + + @impl true + def describe, do: {:ok, %{}} +end -- cgit v1.2.3 From 8b695c3eeb6ee7a91fc5a8a4293fb3cb53212818 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 7 Sep 2020 22:53:45 +0200 Subject: ForceBotUnlistedPolicy: format --- .../web/activity_pub/mrf/force_bot_unlisted_policy.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex index 31fd90586..7290f444b 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -21,14 +21,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do @impl true def filter( - %{ - "type" => "Create", - "to" => to, - "cc" => cc, - "actor" => actor, - "object" => object - } = message - ) do + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message + ) do user = User.get_cached_by_ap_id(actor) isbot = 0.8 < botness_score(user) -- cgit v1.2.3 From 95688c90ad9cd6438a764b4ea6e0f2e3b594b5c8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 8 Sep 2020 01:13:49 +0200 Subject: ForceBotUnlistedPolicy: simplify code --- .../web/activity_pub/mrf/force_bot_unlisted_policy.ex | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex index 7290f444b..ea9c3d3f5 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -9,15 +9,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do require Pleroma.Constants - defp check_by_actor_type(user) do - if user.actor_type in ["Application", "Service"], do: 1.0, else: 0.0 - end - - defp check_by_nickname(user) do - if Regex.match?(~r/bot@|ebooks@/i, user.nickname), do: 1.0, else: 0.0 - end + defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"] + defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname) - defp botness_score(user), do: check_by_actor_type(user) + check_by_nickname(user) + defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user) @impl true def filter( @@ -30,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do } = message ) do user = User.get_cached_by_ap_id(actor) - isbot = 0.8 < botness_score(user) + isbot = check_if_bot(user) if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] -- cgit v1.2.3 From fa347b9c2f416cd2c402e3e6eebb561dfc0ee8a8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 26 Aug 2020 13:32:03 -0500 Subject: Fix uploading webp image files when Exiftool Upload Filter is enabled --- lib/pleroma/upload/filter/exiftool.ex | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex index ea8798fe3..a91bd5e24 100644 --- a/lib/pleroma/upload/filter/exiftool.ex +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -10,9 +10,20 @@ defmodule Pleroma.Upload.Filter.Exiftool do @behaviour Pleroma.Upload.Filter @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()} - def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do + def filter(%Pleroma.Upload{name: file, tempfile: path, content_type: "image" <> _}) do + # webp is not compatible with exiftool at this time + if Regex.match?(~r/\.(webp)$/i, file) do + :ok + else + strip_exif(path) + end + end + + def filter(_), do: :ok + + defp strip_exif(path) do try do - case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do + case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", path], parallelism: true) do {_response, 0} -> :ok {error, 1} -> {:error, error} end @@ -21,6 +32,4 @@ defmodule Pleroma.Upload.Filter.Exiftool do {:error, "exiftool command not found"} end end - - def filter(_), do: :ok end -- cgit v1.2.3 From 2165a249744a1ad4386a9d237871abe88e298942 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Sep 2020 17:40:59 -0500 Subject: Improve upload filter return values so we can identify when filters make no changes to the input --- lib/pleroma/upload/filter.ex | 13 ++++++++++--- lib/pleroma/upload/filter/anonymize_filename.ex | 4 +++- lib/pleroma/upload/filter/dedupe.ex | 4 ++-- lib/pleroma/upload/filter/exiftool.ex | 8 ++++---- lib/pleroma/upload/filter/mogrifun.ex | 6 +++--- lib/pleroma/upload/filter/mogrify.ex | 6 +++--- 6 files changed, 25 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex index dbdadc97f..661135634 100644 --- a/lib/pleroma/upload/filter.ex +++ b/lib/pleroma/upload/filter.ex @@ -15,7 +15,11 @@ defmodule Pleroma.Upload.Filter do require Logger - @callback filter(Pleroma.Upload.t()) :: :ok | {:ok, Pleroma.Upload.t()} | {:error, any()} + @callback filter(Pleroma.Upload.t()) :: + {:ok, :filtered} + | {:ok, :noop} + | {:ok, :filtered, Pleroma.Upload.t()} + | {:error, any()} @spec filter([module()], Pleroma.Upload.t()) :: {:ok, Pleroma.Upload.t()} | {:error, any()} @@ -25,10 +29,13 @@ defmodule Pleroma.Upload.Filter do def filter([filter | rest], upload) do case filter.filter(upload) do - :ok -> + {:ok, :filtered} -> filter(rest, upload) - {:ok, upload} -> + {:ok, :filtered, upload} -> + filter(rest, upload) + + {:ok, :noop} -> filter(rest, upload) error -> diff --git a/lib/pleroma/upload/filter/anonymize_filename.ex b/lib/pleroma/upload/filter/anonymize_filename.ex index 07ead8203..fc456e4f4 100644 --- a/lib/pleroma/upload/filter/anonymize_filename.ex +++ b/lib/pleroma/upload/filter/anonymize_filename.ex @@ -16,9 +16,11 @@ defmodule Pleroma.Upload.Filter.AnonymizeFilename do def filter(%Upload{name: name} = upload) do extension = List.last(String.split(name, ".")) name = predefined_name(extension) || random(extension) - {:ok, %Upload{upload | name: name}} + {:ok, :filtered, %Upload{upload | name: name}} end + def filter(_), do: {:ok, :noop} + @spec predefined_name(String.t()) :: String.t() | nil defp predefined_name(extension) do with name when not is_nil(name) <- Config.get([__MODULE__, :text]), diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex index 41218a918..86cbc8996 100644 --- a/lib/pleroma/upload/filter/dedupe.ex +++ b/lib/pleroma/upload/filter/dedupe.ex @@ -17,8 +17,8 @@ defmodule Pleroma.Upload.Filter.Dedupe do |> Base.encode16(case: :lower) filename = shasum <> "." <> extension - {:ok, %Upload{upload | id: shasum, path: filename}} + {:ok, :filtered, %Upload{upload | id: shasum, path: filename}} end - def filter(_), do: :ok + def filter(_), do: {:ok, :noop} end diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex index a91bd5e24..94d12c01b 100644 --- a/lib/pleroma/upload/filter/exiftool.ex +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -9,22 +9,22 @@ defmodule Pleroma.Upload.Filter.Exiftool do """ @behaviour Pleroma.Upload.Filter - @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()} + @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()} def filter(%Pleroma.Upload{name: file, tempfile: path, content_type: "image" <> _}) do # webp is not compatible with exiftool at this time if Regex.match?(~r/\.(webp)$/i, file) do - :ok + {:ok, :noop} else strip_exif(path) end end - def filter(_), do: :ok + def filter(_), do: {:ok, :noop} defp strip_exif(path) do try do case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", path], parallelism: true) do - {_response, 0} -> :ok + {_response, 0} -> {:ok, :filtered} {error, 1} -> {:error, error} end rescue diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex index c8fa7b190..363e5cf0f 100644 --- a/lib/pleroma/upload/filter/mogrifun.ex +++ b/lib/pleroma/upload/filter/mogrifun.ex @@ -38,16 +38,16 @@ defmodule Pleroma.Upload.Filter.Mogrifun do [{"fill", "yellow"}, {"tint", "40"}] ] - @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()} + @spec filter(Pleroma.Upload.t()) :: {:ok, atom()} | {:error, String.t()} def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do try do Filter.Mogrify.do_filter(file, [Enum.random(@filters)]) - :ok + {:ok, :filtered} rescue _e in ErlangError -> {:error, "mogrify command not found"} end end - def filter(_), do: :ok + def filter(_), do: {:ok, :noop} end diff --git a/lib/pleroma/upload/filter/mogrify.ex b/lib/pleroma/upload/filter/mogrify.ex index 7a45add5a..71968fd9c 100644 --- a/lib/pleroma/upload/filter/mogrify.ex +++ b/lib/pleroma/upload/filter/mogrify.ex @@ -8,18 +8,18 @@ defmodule Pleroma.Upload.Filter.Mogrify do @type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()} @type conversions :: conversion() | [conversion()] - @spec filter(Pleroma.Upload.t()) :: :ok | {:error, String.t()} + @spec filter(Pleroma.Upload.t()) :: {:ok, :atom} | {:error, String.t()} def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do try do do_filter(file, Pleroma.Config.get!([__MODULE__, :args])) - :ok + {:ok, :filtered} rescue _e in ErlangError -> {:error, "mogrify command not found"} end end - def filter(_), do: :ok + def filter(_), do: {:ok, :noop} def do_filter(file, filters) do file -- cgit v1.2.3 From 216c84a8f4d82649110ffaa2bc9d02b879805c5f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Sep 2020 17:56:05 -0500 Subject: Bypass the filter based on content-type as well in case a webp image is uploaded with the wrong file extension. --- lib/pleroma/upload/filter/exiftool.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex index 94d12c01b..b07a671ac 100644 --- a/lib/pleroma/upload/filter/exiftool.ex +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -10,8 +10,11 @@ defmodule Pleroma.Upload.Filter.Exiftool do @behaviour Pleroma.Upload.Filter @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()} + + # webp is not compatible with exiftool at this time + def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop} + def filter(%Pleroma.Upload{name: file, tempfile: path, content_type: "image" <> _}) do - # webp is not compatible with exiftool at this time if Regex.match?(~r/\.(webp)$/i, file) do {:ok, :noop} else -- cgit v1.2.3 From 4ea07f74e9416da8f97a12cfdc24da82e1c00d91 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Sep 2020 22:18:01 -0500 Subject: Revert/simplify. We only need to check the content-type. There's no chance a webp file will get mismatched as another image type. --- lib/pleroma/upload/filter/exiftool.ex | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex index b07a671ac..1fd0cfdaa 100644 --- a/lib/pleroma/upload/filter/exiftool.ex +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -14,19 +14,9 @@ defmodule Pleroma.Upload.Filter.Exiftool do # webp is not compatible with exiftool at this time def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop} - def filter(%Pleroma.Upload{name: file, tempfile: path, content_type: "image" <> _}) do - if Regex.match?(~r/\.(webp)$/i, file) do - {:ok, :noop} - else - strip_exif(path) - end - end - - def filter(_), do: {:ok, :noop} - - defp strip_exif(path) do + def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do try do - case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", path], parallelism: true) do + case System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true) do {_response, 0} -> {:ok, :filtered} {error, 1} -> {:error, error} end @@ -35,4 +25,6 @@ defmodule Pleroma.Upload.Filter.Exiftool do {:error, "exiftool command not found"} end end + + def filter(_), do: {:ok, :noop} end -- cgit v1.2.3 From 87d2805791e1dd6746009e8c1445719e8cbfd31d Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 8 Sep 2020 17:29:28 +0300 Subject: combo fixes --- lib/pleroma/stats.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index e7f8d272c..e5c9c668b 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Stats do @impl true def init(_args) do + if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo) {:ok, nil, {:continue, :calculate_stats}} end -- cgit v1.2.3 From ee0e05f9301e149c769f36bfd0fc8527ec7b6326 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 8 Sep 2020 10:43:57 +0200 Subject: Drop unused "inReplyToAtomUri" in objects --- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 0831efadc..af4384213 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -168,7 +168,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) when not is_nil(in_reply_to) do in_reply_to_id = prepare_in_reply_to(in_reply_to) - object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) depth = (options[:depth] || 0) + 1 if Federator.allowed_thread_distance?(depth) do @@ -176,9 +175,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do object |> Map.put("inReplyTo", replied_object.data["id"]) - |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("context", replied_object.data["context"] || object["conversation"]) - |> Map.drop(["conversation"]) + |> Map.drop(["conversation", "inReplyToAtomUri"]) else e -> Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") -- cgit v1.2.3 From 921f926e96fd07131d4b79f5a29caed17ae2cc56 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 8 Sep 2020 09:13:11 +0200 Subject: Remove OStatus in testsuite --- lib/pleroma/object/containment.ex | 7 ------- 1 file changed, 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index bc88e8a0c..29cb3d672 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -44,13 +44,6 @@ defmodule Pleroma.Object.Containment do nil end - # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus - # objects being present in the test suite environment. Once these objects are - # removed, please also remove this. - if Mix.env() == :test do - defp compare_uris(_, %URI{scheme: "tag"}), do: :ok - end - defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok defp compare_uris(_id_uri, _other_uri), do: :error -- cgit v1.2.3 From a781ac6ca5b7ab23eea795331db0a3fff406630e Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 10 Jun 2020 15:37:43 +0400 Subject: Fix atom leak in AdminAPIController --- lib/pleroma/web/admin_api/controllers/admin_api_controller.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index aa2af1ab5..f5e4d49f9 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -379,8 +379,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do filters |> String.split(",") |> Enum.filter(&Enum.member?(@filters, &1)) - |> Enum.map(&String.to_atom/1) - |> Map.new(&{&1, true}) + |> Map.new(&{String.to_existing_atom(&1), true}) end def right_add_multiple(%{assigns: %{user: admin}} = conn, %{ -- cgit v1.2.3 From 10ef532c63431811b3998ed7b14aea21755a2b57 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 7 Jul 2020 07:06:29 +0200 Subject: AP C2S: Restrict character limit on Note --- .../web/activity_pub/activity_pub_controller.ex | 37 ++++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 220c4fe52..732c44271 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -399,21 +399,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do defp handle_user_activity( %User{} = user, - %{"type" => "Create", "object" => %{"type" => "Note"}} = params + %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params ) do - object = - params["object"] - |> Map.merge(Map.take(params, ["to", "cc"])) - |> Map.put("attributedTo", user.ap_id()) - |> Transmogrifier.fix_object() - - ActivityPub.create(%{ - to: params["to"], - actor: user, - context: object["context"], - object: object, - additional: Map.take(params, ["cc"]) - }) + content = if is_binary(object["content"]), do: object["content"], else: "" + name = if is_binary(object["name"]), do: object["name"], else: "" + summary = if is_binary(object["summary"]), do: object["summary"], else: "" + length = String.length(content <> name <> summary) + + if length > Pleroma.Config.get([:instance, :limit]) do + {:error, dgettext("errors", "Note is over the character limit")} + else + object = + object + |> Map.merge(Map.take(params, ["to", "cc"])) + |> Map.put("attributedTo", user.ap_id()) + |> Transmogrifier.fix_object() + + ActivityPub.create(%{ + to: params["to"], + actor: user, + context: object["context"], + object: object, + additional: Map.take(params, ["cc"]) + }) + end end defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do -- cgit v1.2.3 From 16c451f8f15b1b2907fb6fc40925b47821650f31 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 2 Sep 2020 20:11:24 +0200 Subject: search: Apply following filter only when user is usable --- lib/pleroma/user/search.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index adbef7fb8..7babd47ea 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -115,8 +115,8 @@ defmodule Pleroma.User.Search do ) end - defp base_query(_user, false), do: User - defp base_query(user, true), do: User.get_friends_query(user) + defp base_query(%User{} = user, true), do: User.get_friends_query(user) + defp base_query(_user, _following), do: User defp filter_invisible_users(query) do from(q in query, where: q.invisible == false) -- cgit v1.2.3 From 947ee55ae298a42c2667800c1aac96f637e31969 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 2 Sep 2020 20:24:03 +0200 Subject: user: harden get_friends_query(), get_followers_query() and their wrappers --- lib/pleroma/user.ex | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 94c96de8d..f323fc6ed 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1125,31 +1125,31 @@ defmodule Pleroma.User do User.Query.build(%{followers: user, deactivated: false}) end - def get_followers_query(user, page) do + def get_followers_query(%User{} = user, page) do user |> get_followers_query(nil) |> User.Query.paginate(page, 20) end @spec get_followers_query(User.t()) :: Ecto.Query.t() - def get_followers_query(user), do: get_followers_query(user, nil) + def get_followers_query(%User{} = user), do: get_followers_query(user, nil) @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} - def get_followers(user, page \\ nil) do + def get_followers(%User{} = user, page \\ nil) do user |> get_followers_query(page) |> Repo.all() end @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} - def get_external_followers(user, page \\ nil) do + def get_external_followers(%User{} = user, page \\ nil) do user |> get_followers_query(page) |> User.Query.build(%{external: true}) |> Repo.all() end - def get_followers_ids(user, page \\ nil) do + def get_followers_ids(%User{} = user, page \\ nil) do user |> get_followers_query(page) |> select([u], u.id) @@ -1161,29 +1161,29 @@ defmodule Pleroma.User do User.Query.build(%{friends: user, deactivated: false}) end - def get_friends_query(user, page) do + def get_friends_query(%User{} = user, page) do user |> get_friends_query(nil) |> User.Query.paginate(page, 20) end @spec get_friends_query(User.t()) :: Ecto.Query.t() - def get_friends_query(user), do: get_friends_query(user, nil) + def get_friends_query(%User{} = user), do: get_friends_query(user, nil) - def get_friends(user, page \\ nil) do + def get_friends(%User{} = user, page \\ nil) do user |> get_friends_query(page) |> Repo.all() end - def get_friends_ap_ids(user) do + def get_friends_ap_ids(%User{} = user) do user |> get_friends_query(nil) |> select([u], u.ap_id) |> Repo.all() end - def get_friends_ids(user, page \\ nil) do + def get_friends_ids(%User{} = user, page \\ nil) do user |> get_friends_query(page) |> select([u], u.id) -- cgit v1.2.3 From 630444ee0819ad5b58c5f9030758fe41e6fed530 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 31 Aug 2020 14:19:48 -0500 Subject: Do not make RelMe metadata provider optional. There's really no sound reason to turn this off anyway. --- lib/pleroma/web/metadata.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index a9f70c43e..e45e74e7b 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -7,7 +7,8 @@ defmodule Pleroma.Web.Metadata do def build_tags(params) do providers = [ - Pleroma.Web.Metadata.Providers.RestrictIndexing + Pleroma.Web.Metadata.Providers.RestrictIndexing, + Pleroma.Web.Metadata.Providers.RelMe, | Pleroma.Config.get([__MODULE__, :providers], []) ] -- cgit v1.2.3 From ff07014b2657730101e826d7e82716989d43214c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 31 Aug 2020 14:35:22 -0500 Subject: Disable providers of user and status metadata when instance is private --- lib/pleroma/web/metadata.ex | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index e45e74e7b..0f0b56321 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.Metadata do def build_tags(params) do providers = [ Pleroma.Web.Metadata.Providers.RestrictIndexing, - Pleroma.Web.Metadata.Providers.RelMe, - | Pleroma.Config.get([__MODULE__, :providers], []) + Pleroma.Web.Metadata.Providers.RelMe + | activated_providers() ] Enum.reduce(providers, "", fn parser, acc -> @@ -43,4 +43,12 @@ defmodule Pleroma.Web.Metadata do def activity_nsfw?(_) do false end + + defp activated_providers do + if Pleroma.Config.get!([:instance, :public]) do + Pleroma.Config.get([__MODULE__, :providers], []) + else + [] + end + end end -- cgit v1.2.3 From 14d07081fd82211071eafb3c31d8c756fe9af9f5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 31 Aug 2020 14:48:22 -0500 Subject: Feed provider only generates a redirect, so always activate it. Making this configurable is misleading. --- lib/pleroma/web/metadata.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 0f0b56321..926b5b187 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -7,8 +7,9 @@ defmodule Pleroma.Web.Metadata do def build_tags(params) do providers = [ - Pleroma.Web.Metadata.Providers.RestrictIndexing, - Pleroma.Web.Metadata.Providers.RelMe + Pleroma.Web.Metadata.Providers.Feed, + Pleroma.Web.Metadata.Providers.RelMe, + Pleroma.Web.Metadata.Providers.RestrictIndexing | activated_providers() ] -- cgit v1.2.3 From a85ed6defbd2cec71d9a5594ef1de18d5333c7c7 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 31 Aug 2020 15:58:21 -0500 Subject: Do not serve RSS/Atom feeds when instance is private --- lib/pleroma/web/feed/tag_controller.ex | 10 +++++++++- lib/pleroma/web/feed/user_controller.ex | 10 +++++++++- lib/pleroma/web/metadata/feed.ex | 20 ++++++++++++-------- 3 files changed, 30 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index 39b2a766a..e090dd625 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -9,7 +9,15 @@ defmodule Pleroma.Web.Feed.TagController do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Feed.FeedView - def feed(conn, %{"tag" => raw_tag} = params) do + def feed(conn, params) do + if Pleroma.Config.get!([:instance, :public]) do + render_feed(conn, params) + else + render_error(conn, :not_found, "Not found") + end + end + + def render_feed(conn, %{"tag" => raw_tag} = params) do {format, tag} = parse_tag(raw_tag) activities = diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 9cd334a33..595889b9d 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -37,7 +37,15 @@ defmodule Pleroma.Web.Feed.UserController do end end - def feed(conn, %{"nickname" => nickname} = params) do + def feed(conn, params) do + if Pleroma.Config.get!([:instance, :public]) do + render_feed(conn, params) + else + errors(conn, {:error, :not_found}) + end + end + + def render_feed(conn, %{"nickname" => nickname} = params) do format = get_format(conn) format = diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex index bd1459a17..dfe391b56 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/feed.ex @@ -11,13 +11,17 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do @impl Provider def build_tags(%{user: user}) do - [ - {:link, - [ - rel: "alternate", - type: "application/atom+xml", - href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" - ], []} - ] + if Pleroma.Config.get!([:instance, :public]) do + [ + {:link, + [ + rel: "alternate", + type: "application/atom+xml", + href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" + ], []} + ] + else + [] + end end end -- cgit v1.2.3 From 2011142ed9ae45f53496b3682da7114255c814a5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 1 Sep 2020 10:43:44 -0500 Subject: Use :restrict_unauthenticated testing for more granular control --- lib/pleroma/web/feed/tag_controller.ex | 2 +- lib/pleroma/web/feed/user_controller.ex | 2 +- lib/pleroma/web/metadata.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index e090dd625..93a8294b7 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.Feed.TagController do alias Pleroma.Web.Feed.FeedView def feed(conn, params) do - if Pleroma.Config.get!([:instance, :public]) do + unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do render_feed(conn, params) else render_error(conn, :not_found, "Not found") diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 595889b9d..71eb1ea7e 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -38,7 +38,7 @@ defmodule Pleroma.Web.Feed.UserController do end def feed(conn, params) do - if Pleroma.Config.get!([:instance, :public]) do + unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do render_feed(conn, params) else errors(conn, {:error, :not_found}) diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 926b5b187..68835c826 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -46,7 +46,7 @@ defmodule Pleroma.Web.Metadata do end defp activated_providers do - if Pleroma.Config.get!([:instance, :public]) do + unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do Pleroma.Config.get([__MODULE__, :providers], []) else [] -- cgit v1.2.3 From 0d2814ec8e41942cd5b056d9c1ed902be1e1773c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 7 Sep 2020 15:06:06 +0300 Subject: Metadata: Move restriction check from Feed provider to activated_providers --- lib/pleroma/web/metadata.ex | 3 +-- lib/pleroma/web/metadata/feed.ex | 20 ++++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 68835c826..0f2d8d1e7 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.Metadata do def build_tags(params) do providers = [ - Pleroma.Web.Metadata.Providers.Feed, Pleroma.Web.Metadata.Providers.RelMe, Pleroma.Web.Metadata.Providers.RestrictIndexing | activated_providers() @@ -47,7 +46,7 @@ defmodule Pleroma.Web.Metadata do defp activated_providers do unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do - Pleroma.Config.get([__MODULE__, :providers], []) + [Pleroma.Web.Metadata.Providers.Feed | Pleroma.Config.get([__MODULE__, :providers], [])] else [] end diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex index dfe391b56..bd1459a17 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/feed.ex @@ -11,17 +11,13 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do @impl Provider def build_tags(%{user: user}) do - if Pleroma.Config.get!([:instance, :public]) do - [ - {:link, - [ - rel: "alternate", - type: "application/atom+xml", - href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" - ], []} - ] - else - [] - end + [ + {:link, + [ + rel: "alternate", + type: "application/atom+xml", + href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" + ], []} + ] end end -- cgit v1.2.3 From 23ca5f75afa7369ff52772c39dc3324e9402b230 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 8 Sep 2020 16:39:08 -0500 Subject: Make it possible to bulk send confirmation emails to all unconfirmed users --- lib/mix/tasks/pleroma/email.ex | 19 ++++++++++++++++++- lib/pleroma/user/query.ex | 4 ++++ 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index d3fac6ec8..61d431971 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -2,7 +2,7 @@ defmodule Mix.Tasks.Pleroma.Email do use Mix.Task import Mix.Pleroma - @shortdoc "Simple Email test" + @shortdoc "Email administrative tasks" @moduledoc File.read!("docs/administration/CLI_tasks/email.md") def run(["test" | args]) do @@ -21,4 +21,21 @@ defmodule Mix.Tasks.Pleroma.Email do shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}") end + + def run(["resend_confirmation_emails"]) do + start_pleroma() + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + confirmation_pending: true, + invisible: false + }) + |> Pleroma.RepoStreamer.chunk_stream(500) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> Pleroma.User.send_confirmation_email(user) end) + end) + |> Stream.run() + end end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index d618432ff..f59ca6f9c 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -148,6 +148,10 @@ defmodule Pleroma.User.Query do |> where([u], not is_nil(u.nickname)) end + defp compose_query({:confirmation_pending, bool}, query) do + where(query, [u], u.confirmation_pending == ^bool) + end + defp compose_query({:need_approval, _}, query) do where(query, [u], u.approval_pending) end -- cgit v1.2.3 From 75b6fef25dd81c81cd5709739f97182045eec5b8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 8 Sep 2020 16:39:41 -0500 Subject: Add mix task for bulk [un]confirming the local instance users --- lib/mix/tasks/pleroma/user.ex | 66 +++++++++++++++++++++++++++++++++++++++---- lib/pleroma/user.ex | 7 +++++ lib/pleroma/user/query.ex | 8 +++--- 3 files changed, 71 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 01824aa18..4073fe5a4 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -196,17 +196,24 @@ defmodule Mix.Tasks.Pleroma.User do OptionParser.parse( rest, strict: [ - moderator: :boolean, admin: :boolean, - locked: :boolean + confirmed: :boolean, + locked: :boolean, + moderator: :boolean ] ) with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do user = - case Keyword.get(options, :moderator) do + case Keyword.get(options, :admin) do nil -> user - value -> set_moderator(user, value) + value -> set_admin(user, value) + end + + user = + case Keyword.get(options, :confirmed) do + nil -> user + value -> set_confirmed(user, value) end user = @@ -216,9 +223,9 @@ defmodule Mix.Tasks.Pleroma.User do end _user = - case Keyword.get(options, :admin) do + case Keyword.get(options, :moderator) do nil -> user - value -> set_admin(user, value) + value -> set_moderator(user, value) end else _ -> @@ -353,6 +360,42 @@ defmodule Mix.Tasks.Pleroma.User do end end + def run(["confirm_all"]) do + start_pleroma() + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + is_moderator: false, + is_admin: false, + invisible: false + }) + |> Pleroma.RepoStreamer.chunk_stream(500) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> User.need_confirmation(user, false) end) + end) + |> Stream.run() + end + + def run(["unconfirm_all"]) do + start_pleroma() + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + is_moderator: false, + is_admin: false, + invisible: false + }) + |> Pleroma.RepoStreamer.chunk_stream(500) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> User.need_confirmation(user, true) end) + end) + |> Stream.run() + end + def run(["sign_out", nickname]) do start_pleroma() @@ -410,4 +453,15 @@ defmodule Mix.Tasks.Pleroma.User do shell_info("Locked status of #{user.nickname}: #{user.locked}") user end + + defp set_confirmed(user, value) do + {:ok, user} = + case value do + true -> User.need_confirmation(user, false) + false -> User.need_confirmation(user, true) + end + + shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}") + user + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f323fc6ed..603fc3b44 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2123,6 +2123,13 @@ defmodule Pleroma.User do Enum.map(users, &toggle_confirmation/1) end + @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + def need_confirmation(%User{} = user, bool) do + user + |> confirmation_changeset(need_confirmation: bool) + |> update_and_set_cache() + end + def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do mascot end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index f59ca6f9c..64bb24c0e 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -107,12 +107,12 @@ defmodule Pleroma.User.Query do where(query, [u], fragment("? && ?", u.tags, ^tags)) end - defp compose_query({:is_admin, _}, query) do - where(query, [u], u.is_admin) + defp compose_query({:is_admin, bool}, query) do + where(query, [u], u.is_admin == ^bool) end - defp compose_query({:is_moderator, _}, query) do - where(query, [u], u.is_moderator) + defp compose_query({:is_moderator, bool}, query) do + where(query, [u], u.is_moderator == ^bool) end defp compose_query({:super_users, _}, query) do -- cgit v1.2.3 From d23d0c27c25e3ce7b39cae6e504062b4cb389ea4 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 8 Sep 2020 16:48:54 -0500 Subject: Handle possibility of user account in a bulk operation not having an email address --- lib/mix/tasks/pleroma/email.ex | 2 +- lib/pleroma/user.ex | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index 61d431971..c0bef0386 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -34,7 +34,7 @@ defmodule Mix.Tasks.Pleroma.Email do |> Pleroma.RepoStreamer.chunk_stream(500) |> Stream.each(fn users -> users - |> Enum.each(fn user -> Pleroma.User.send_confirmation_email(user) end) + |> Enum.each(fn user -> Pleroma.User.try_send_confirmation_email(user) end) end) |> Stream.run() end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 603fc3b44..9dea39619 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -814,7 +814,8 @@ defmodule Pleroma.User do def send_welcome_email(_), do: {:ok, :noop} @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} - def try_send_confirmation_email(%User{confirmation_pending: true} = user) do + def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user) + when is_binary(email) do if Config.get([:instance, :account_activation_required]) do send_confirmation_email(user) {:ok, :enqueued} -- cgit v1.2.3 From 68a74d66596f0e35f0e080de25e4679d2c8b1b76 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 9 Sep 2020 19:30:42 +0300 Subject: [#2497] Added missing alias, removed legacy `:adapter` option specification for HTTP.get/_. --- lib/pleroma/helpers/media_helper.ex | 4 ++-- lib/pleroma/instances/instance.ex | 2 +- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index a1205e10d..d834b4a07 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Helpers.MediaHelper do def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), - {:ok, env} <- HTTP.get(url, [], adapter: [pool: :media]), + {:ok, env} <- HTTP.get(url, [], pool: :media), {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) @@ -62,7 +62,7 @@ defmodule Pleroma.Helpers.MediaHelper do def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), - {:ok, env} <- HTTP.get(url, [], adapter: [pool: :media]), + {:ok, env} <- HTTP.get(url, [], pool: :media), {:ok, fifo_path} <- mkfifo(), args = [ "-y", diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 8bf53c090..4fe4b198d 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -157,7 +157,7 @@ defmodule Pleroma.Instances.Instance do try do with {:ok, %Tesla.Env{body: html}} <- Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], - adapter: [pool: :media] + pool: :media ), favicon_rel <- html diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 89f4a23bd..acb581459 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do media_proxy_url = MediaProxy.url(url) with {:ok, %{status: status} = head_response} when status in 200..299 <- - Pleroma.HTTP.request("head", media_proxy_url, [], [], adapter: [pool: :media]) do + Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do content_type = Tesla.get_header(head_response, "content-type") handle_preview(content_type, conn, media_proxy_url) else -- cgit v1.2.3 From b4860c57a63b48ded8eaa37b9f40cc0851c78882 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 9 Sep 2020 19:43:36 +0300 Subject: [#2497] Formatting fix. --- lib/pleroma/instances/instance.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 4fe4b198d..ad7764f05 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -156,9 +156,7 @@ defmodule Pleroma.Instances.Instance do defp scrape_favicon(%URI{} = instance_uri) do try do with {:ok, %Tesla.Env{body: html}} <- - Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], - pool: :media - ), + Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media), favicon_rel <- html |> Floki.parse_document!() -- cgit v1.2.3 From cad69669fc692da360929a5961e96550de1f1fe1 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 9 Sep 2020 22:44:38 +0300 Subject: [#2130] Fixed OAuth OOB authentication for users with enabled MFA. --- lib/pleroma/web/oauth/oauth_controller.ex | 5 ++++- .../web/templates/o_auth/o_auth/oob_authorization_created.html.eex | 2 +- lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index dd00600ea..06b116368 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -145,7 +145,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ "authorization" => %{"redirect_uri" => @oob_token_redirect_uri} }) do - render(conn, "oob_authorization_created.html", %{auth: auth}) + # Enforcing the view to reuse the template when calling from other controllers + conn + |> put_view(OAuthView) + |> render("oob_authorization_created.html", %{auth: auth}) end def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex index 8443d906b..ffabe29a6 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex @@ -1,2 +1,2 @@

Successfully authorized

-

Token code is <%= @auth.token %>

+

Token code is
<%= @auth.token %>

diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex index 961aad976..82785c4b9 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex @@ -1,2 +1,2 @@

Authorization exists

-

Access token is <%= @token.token %>

+

Access token is
<%= @token.token %>

-- cgit v1.2.3 From ab56dd54e787eae82cf00fddc90eab4c5cbac4a9 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 10 Sep 2020 11:23:39 +0300 Subject: use Pleroma.HTTP in emoji packs tasks --- lib/mix/tasks/pleroma/emoji.ex | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 8f52ee98d..1750373f9 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -183,7 +183,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do IO.puts("Downloading the pack and generating SHA256") - binary_archive = Tesla.get!(client(), src).body + {:ok, %{body: binary_archive}} = Pleroma.HTTP.get(src) archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() IO.puts("SHA256 is #{archive_sha}") @@ -252,7 +252,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do end defp fetch("http" <> _ = from) do - with {:ok, %{body: body}} <- Tesla.get(client(), from) do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(from) do {:ok, body} end end @@ -271,13 +271,5 @@ defmodule Mix.Tasks.Pleroma.Emoji do ) end - defp client do - middleware = [ - {Tesla.Middleware.FollowRedirects, [max_redirects: 3]} - ] - - Tesla.client(middleware) - end - defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest]) end -- cgit v1.2.3 From 148bc244359e70c87ec2812c65da83fe87efbc68 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 10 Sep 2020 11:54:10 +0300 Subject: [#2497] Removed Hackney-specific code (no longer needed due to adapter options unification). --- .../web/activity_pub/mrf/media_proxy_warming_policy.ex | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 6c63fe15c..0fb05d3c4 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -13,17 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger @adapter_options [ - pool: :media + pool: :media, + recv_timeout: 10_000 ] - defp adapter_options do - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@adapter_options, :recv_timeout, 10_000) - else - @adapter_options - end - end - def perform(:prefetch, url) do # Fetching only proxiable resources if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do @@ -32,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}") - HTTP.get(prefetch_url, [], adapter: adapter_options()) + HTTP.get(prefetch_url, [], @adapter_options) end end -- cgit v1.2.3 From 9853c90abba213bdc87dccf5620cb0d9eb19c049 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 10 Sep 2020 12:24:44 +0300 Subject: added paginate links to headers for /chats/:id/messages --- lib/pleroma/chat.ex | 24 +++++++-- .../web/api_spec/operations/chat_operation.ex | 3 +- lib/pleroma/web/controller_helper.ex | 20 +++---- .../web/pleroma_api/controllers/chat_controller.ex | 62 +++++++++------------- 4 files changed, 55 insertions(+), 54 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 24a86371e..202fffb8a 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Chat do use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Pleroma.Repo alias Pleroma.User @@ -16,6 +17,7 @@ defmodule Pleroma.Chat do It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages. """ + @type t :: %__MODULE__{} @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} schema "chats" do @@ -39,16 +41,28 @@ defmodule Pleroma.Chat do |> unique_constraint(:user_id, name: :chats_user_id_recipient_index) end + @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) :: + {:ok, t()} | {:error, :not_found} + def get_by_user_and_id(%User{id: user_id}, id) do + from(c in __MODULE__, + where: c.id == ^id, + where: c.user_id == ^user_id + ) + |> Repo.find_resource() + end + + @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil def get_by_id(id) do - __MODULE__ - |> Repo.get(id) + Repo.get(__MODULE__, id) end + @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil def get(user_id, recipient) do - __MODULE__ - |> Repo.get_by(user_id: user_id, recipient: recipient) + Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient) end + @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + {:ok, t()} | {:error, Ecto.Changeset.t()} def get_or_create(user_id, recipient) do %__MODULE__{} |> changeset(%{user_id: user_id, recipient: recipient}) @@ -60,6 +74,8 @@ defmodule Pleroma.Chat do ) end + @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + {:ok, t()} | {:error, Ecto.Changeset.t()} def bump_or_create(user_id, recipient) do %__MODULE__{} |> changeset(%{user_id: user_id, recipient: recipient}) diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index b1a0d26ab..8cbea9ec4 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -158,7 +158,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do "The messages in the chat", "application/json", chat_messages_response() - ) + ), + 404 => Operation.response("Not Found", "application/json", ApiError) }, security: [ %{ diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 6445966e0..69188a882 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -48,13 +48,13 @@ defmodule Pleroma.Web.ControllerHelper do defp param_to_integer(_, default), do: default - def add_link_headers(conn, activities, extra_params \\ %{}) + def add_link_headers(conn, entries, extra_params \\ %{}) - def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params), + def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params), do: conn - def add_link_headers(conn, activities, extra_params) do - case get_pagination_fields(conn, activities, extra_params) do + def add_link_headers(conn, entries, extra_params) do + case get_pagination_fields(conn, entries, extra_params) do %{"next" => next_url, "prev" => prev_url} -> put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") @@ -78,19 +78,15 @@ defmodule Pleroma.Web.ControllerHelper do } end - def get_pagination_fields(conn, activities, extra_params \\ %{}) do - case List.last(activities) do + def get_pagination_fields(conn, entries, extra_params \\ %{}) do + case List.last(entries) do %{pagination_id: max_id} when not is_nil(max_id) -> - %{pagination_id: min_id} = - activities - |> List.first() + %{pagination_id: min_id} = List.first(entries) build_pagination_fields(conn, min_id, max_id, extra_params) %{id: max_id} -> - %{id: min_id} = - activities - |> List.first() + %{id: min_id} = List.first(entries) build_pagination_fields(conn, min_id, max_id, extra_params) diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index e8a1746d4..7b5f3daf9 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -4,6 +4,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + alias Pleroma.Activity alias Pleroma.Chat alias Pleroma.Chat.MessageReference @@ -47,7 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do }) do with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), - ^chat_id <- cm_ref.chat_id |> to_string(), + ^chat_id <- to_string(cm_ref.chat_id), %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), {:ok, _} <- remove_or_delete(cm_ref, user) do conn @@ -68,18 +70,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end end - defp remove_or_delete(cm_ref, _) do - cm_ref - |> MessageReference.delete() - end + defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref) def post_chat_message( - %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn, - %{ - id: id - } + %{body_params: params, assigns: %{user: user}} = conn, + %{id: id} ) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient), {:ok, activity} <- CommonAPI.post_chat_message(user, recipient, params[:content], @@ -93,13 +90,12 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end end - def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ - id: chat_id, - message_id: message_id - }) do - with %MessageReference{} = cm_ref <- - MessageReference.get_by_id(message_id), - ^chat_id <- cm_ref.chat_id |> to_string(), + def mark_message_as_read( + %{assigns: %{user: %{id: user_id}}} = conn, + %{id: chat_id, message_id: message_id} + ) do + with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do conn @@ -109,36 +105,28 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end def mark_as_read( - %{ - body_params: %{last_read_id: last_read_id}, - assigns: %{user: %{id: user_id}} - } = conn, + %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn, %{id: id} ) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), - {_n, _} <- - MessageReference.set_all_seen_for_chat(chat, last_read_id) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), + {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do conn |> put_view(ChatView) |> render("show.json", chat: chat) end end - def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do - cm_refs = + def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do + chat_message_refs = chat |> MessageReference.for_chat_query() |> Pagination.fetch_paginated(params) conn + |> add_link_headers(chat_message_refs) |> put_view(MessageReferenceView) - |> render("index.json", chat_message_references: cm_refs) - else - _ -> - conn - |> put_status(:not_found) - |> json(%{error: "not found"}) + |> render("index.json", chat_message_references: chat_message_refs) end end @@ -158,8 +146,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do |> render("index.json", chats: chats) end - def create(%{assigns: %{user: user}} = conn, params) do - with %User{ap_id: recipient} <- User.get_by_id(params[:id]), + def create(%{assigns: %{user: user}} = conn, %{id: id}) do + with %User{ap_id: recipient} <- User.get_cached_by_id(id), {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do conn |> put_view(ChatView) @@ -167,8 +155,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do end end - def show(%{assigns: %{user: user}} = conn, params) do - with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do conn |> put_view(ChatView) |> render("show.json", chat: chat) -- cgit v1.2.3 From 3ce658b93098551792a69f2455e6e9339a1722e2 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 25 Aug 2020 19:17:51 +0300 Subject: schedule expired oauth tokens deletion with Oban --- lib/pleroma/web/oauth/token.ex | 24 ++++++++++++------- lib/pleroma/web/oauth/token/clean_worker.ex | 2 -- lib/pleroma/web/oauth/token/query.ex | 6 ----- .../web/oauth/token/strategy/refresh_token.ex | 2 +- .../workers/cron/clear_oauth_token_worker.ex | 23 ------------------ lib/pleroma/workers/purge_expired_token.ex | 28 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 40 deletions(-) delete mode 100644 lib/pleroma/workers/cron/clear_oauth_token_worker.ex create mode 100644 lib/pleroma/workers/purge_expired_token.ex (limited to 'lib') diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 08bb7326d..4d00fcb1c 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -50,7 +50,7 @@ defmodule Pleroma.Web.OAuth.Token do true <- auth.app_id == app.id do user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{} - create_token( + create( app, user, %{scopes: auth.scopes} @@ -83,8 +83,21 @@ defmodule Pleroma.Web.OAuth.Token do |> validate_required([:valid_until]) end - @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} - def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do + @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} + def create(%App{} = app, %User{} = user, attrs \\ %{}) do + with {:ok, token} <- do_create(app, user, attrs) do + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + Pleroma.Workers.PurgeExpiredOAuthToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC") + }) + end + + {:ok, token} + end + end + + defp do_create(app, user, attrs) do %__MODULE__{user_id: user.id, app_id: app.id} |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes]) |> validate_required([:scopes, :app_id]) @@ -105,11 +118,6 @@ defmodule Pleroma.Web.OAuth.Token do |> 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 Query.get_by_user(user_id) |> Query.preload([:app]) diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index e3aa4eb7e..2f51bdb75 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -12,7 +12,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @one_day 86_400_000 alias Pleroma.MFA - alias Pleroma.Web.OAuth alias Pleroma.Workers.BackgroundWorker def start_link(_), do: GenServer.start_link(__MODULE__, %{}) @@ -32,7 +31,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do end def perform(:clean) do - OAuth.Token.delete_expired_tokens() MFA.Token.delete_expired_tokens() end end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex index 93d6e26ed..fd6d9b112 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/oauth/token/query.ex @@ -33,12 +33,6 @@ defmodule Pleroma.Web.OAuth.Token.Query 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) diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex index debc29b0b..625b0fde2 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex @@ -46,7 +46,7 @@ defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do defp create_access_token({:error, error}, _), do: {:error, error} defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do - Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token)) + Token.create(app, user, add_refresh_token(token_params, token.refresh_token)) end defp add_refresh_token(params, token) do diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex deleted file mode 100644 index 276f47efc..000000000 --- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do - @moduledoc """ - The worker to cleanup expired oAuth tokens. - """ - - use Oban.Worker, queue: "background" - - alias Pleroma.Config - alias Pleroma.Web.OAuth.Token - - @impl Oban.Worker - def perform(_job) do - if Config.get([:oauth2, :clean_expired_tokens], false) do - Token.delete_expired_tokens() - end - - :ok - end -end diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex new file mode 100644 index 000000000..6068e43bf --- /dev/null +++ b/lib/pleroma/workers/purge_expired_token.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredOAuthToken do + @moduledoc """ + Worker which purges expired OAuth tokens + """ + + use Oban.Worker, queue: :oauth_token_expiration, max_attempts: 1 + + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t()}) :: + {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + def enqueue(args) do + {scheduled_at, args} = Map.pop(args, :valid_until) + + args + |> __MODULE__.new(scheduled_at: scheduled_at) + |> Oban.insert() + end + + @impl true + def perform(%Oban.Job{args: %{"token_id" => id}}) do + Pleroma.Web.OAuth.Token + |> Pleroma.Repo.get(id) + |> Pleroma.Repo.delete() + end +end -- cgit v1.2.3 From 7dd986a563545cb63e8404d9b107f1d29c499940 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 5 Sep 2020 18:35:01 +0300 Subject: expire mfa tokens through Oban --- lib/pleroma/mfa/token.ex | 71 +++++++++++----------- lib/pleroma/web/oauth/oauth_controller.ex | 4 +- lib/pleroma/web/oauth/token.ex | 5 +- lib/pleroma/web/oauth/token/clean_worker.ex | 36 ----------- .../controllers/remote_follow_controller.ex | 2 +- lib/pleroma/workers/purge_expired_token.ex | 11 ++-- 6 files changed, 47 insertions(+), 82 deletions(-) delete mode 100644 lib/pleroma/web/oauth/token/clean_worker.ex (limited to 'lib') diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex index 0b2449971..69b64c0e8 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -10,10 +10,11 @@ defmodule Pleroma.MFA.Token do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token, as: OAuthToken @expires 300 + @type t() :: %__MODULE__{} + schema "mfa_tokens" do field(:token, :string) field(:valid_until, :naive_datetime_usec) @@ -24,6 +25,7 @@ defmodule Pleroma.MFA.Token do timestamps() end + @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(token) do from( t in __MODULE__, @@ -33,33 +35,40 @@ defmodule Pleroma.MFA.Token do |> Repo.find_resource() end - def validate(token) do - with {:fetch_token, {:ok, token}} <- {:fetch_token, get_by_token(token)}, - {:expired, false} <- {:expired, is_expired?(token)} do + @spec validate(String.t()) :: {:ok, t()} | {:error, :not_found} | {:error, :expired_token} + def validate(token_str) do + with {:ok, token} <- get_by_token(token_str), + false <- expired?(token) do {:ok, token} - else - {:expired, _} -> {:error, :expired_token} - {:fetch_token, _} -> {:error, :not_found} - error -> {:error, error} end end - def create_token(%User{} = user) do - %__MODULE__{} - |> change - |> assign_user(user) - |> put_token - |> put_valid_until - |> Repo.insert() + defp expired?(%__MODULE__{valid_until: valid_until}) do + with true <- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 do + {:error, :expired_token} + end + end + + @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def create(user, authorization \\ nil) do + with {:ok, token} <- do_create(user, authorization) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + + {:ok, token} + end end - def create_token(user, authorization) do + defp do_create(user, authorization) do %__MODULE__{} - |> change + |> change() |> assign_user(user) - |> assign_authorization(authorization) - |> put_token - |> put_valid_until + |> maybe_assign_authorization(authorization) + |> put_token() + |> put_valid_until() |> Repo.insert() end @@ -69,15 +78,19 @@ defmodule Pleroma.MFA.Token do |> validate_required([:user]) end - defp assign_authorization(changeset, authorization) do + defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do changeset |> put_assoc(:authorization, authorization) |> validate_required([:authorization]) end + defp maybe_assign_authorization(changeset, _), do: changeset + defp put_token(changeset) do + token = Pleroma.Web.OAuth.Token.Utils.generate_token() + changeset - |> change(%{token: OAuthToken.Utils.generate_token()}) + |> change(%{token: token}) |> validate_required([:token]) |> unique_constraint(:token) end @@ -89,18 +102,4 @@ defmodule Pleroma.MFA.Token do |> change(%{valid_until: expires_in}) |> validate_required([:valid_until]) end - - def is_expired?(%__MODULE__{valid_until: valid_until}) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 - end - - def is_expired?(_), do: false - - def delete_expired_tokens do - from( - q in __MODULE__, - where: fragment("?", q.valid_until) < ^Timex.now() - ) - |> Repo.delete_all() - end end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index dd00600ea..bbe7aa8a0 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -197,7 +197,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:mfa_required, user, auth, _}, params ) do - {:ok, token} = MFA.Token.create_token(user, auth) + {:ok, token} = MFA.Token.create(user, auth) data = %{ "mfa_token" => token.token, @@ -579,7 +579,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do do: put_session(conn, :registration_id, registration_id) defp build_and_response_mfa_token(user, auth) do - with {:ok, token} <- MFA.Token.create_token(user, auth) do + with {:ok, token} <- MFA.Token.create(user, auth) do MFAView.render("mfa_response.json", %{token: token, user: user}) end end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 4d00fcb1c..de37998f2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -87,9 +87,10 @@ defmodule Pleroma.Web.OAuth.Token do def create(%App{} = app, %User{} = user, attrs \\ %{}) do with {:ok, token} <- do_create(app, user, attrs) do if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do - Pleroma.Workers.PurgeExpiredOAuthToken.enqueue(%{ + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ token_id: token.id, - valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC") + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ }) end diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex deleted file mode 100644 index 2f51bdb75..000000000 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ /dev/null @@ -1,36 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.CleanWorker do - @moduledoc """ - The module represents functions to clean an expired OAuth and MFA tokens. - """ - use GenServer - - @ten_seconds 10_000 - @one_day 86_400_000 - - alias Pleroma.MFA - alias Pleroma.Workers.BackgroundWorker - - def start_link(_), do: GenServer.start_link(__MODULE__, %{}) - - def init(_) do - Process.send_after(self(), :perform, @ten_seconds) - {:ok, nil} - end - - @doc false - def handle_info(:perform, state) do - BackgroundWorker.enqueue("clean_expired_tokens", %{}) - interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) - - Process.send_after(self(), :perform, interval) - {:noreply, state} - end - - def perform(:clean) do - MFA.Token.delete_expired_tokens() - end -end diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 521dc9322..072d889e2 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -135,7 +135,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do end defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false}) end diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex index 6068e43bf..a81e0cd28 100644 --- a/lib/pleroma/workers/purge_expired_token.ex +++ b/lib/pleroma/workers/purge_expired_token.ex @@ -2,14 +2,14 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Workers.PurgeExpiredOAuthToken do +defmodule Pleroma.Workers.PurgeExpiredToken do @moduledoc """ Worker which purges expired OAuth tokens """ - use Oban.Worker, queue: :oauth_token_expiration, max_attempts: 1 + use Oban.Worker, queue: :token_expiration, max_attempts: 1 - @spec enqueue(%{token_id: integer(), valid_until: DateTime.t()}) :: + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) :: {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} def enqueue(args) do {scheduled_at, args} = Map.pop(args, :valid_until) @@ -20,8 +20,9 @@ defmodule Pleroma.Workers.PurgeExpiredOAuthToken do end @impl true - def perform(%Oban.Job{args: %{"token_id" => id}}) do - Pleroma.Web.OAuth.Token + def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do + module + |> String.to_existing_atom() |> Pleroma.Repo.get(id) |> Pleroma.Repo.delete() end -- cgit v1.2.3 From 8af1fd32234df7d0cdb74d78bcca9f68587b70f2 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 7 Sep 2020 20:06:28 +0300 Subject: oban warning --- lib/pleroma/config/oban.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index c2d56ebab..81758c93d 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Config.Oban do oban_config = Pleroma.Config.get(Oban) crontab = - [Pleroma.Workers.Cron.StatsWorker] + [Pleroma.Workers.Cron.StatsWorker, Pleroma.Workers.Cron.ClearOauthTokenWorker] |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc -> with acc when is_list(acc) <- acc, setting when is_tuple(setting) <- -- cgit v1.2.3 From e8bfb50fa3c16f98845e326b153c8a89505e8a55 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 10 Sep 2020 20:09:44 +0300 Subject: pass options without adapter key --- lib/pleroma/reverse_proxy/client/tesla.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex index d5a339681..4b118eec2 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -28,7 +28,7 @@ defmodule Pleroma.ReverseProxy.Client.Tesla do url, body, headers, - Keyword.put(opts, :adapter, opts) + opts ) do if is_map(response.body) and method != :head do {:ok, response.status, response.headers, response.body} -- cgit v1.2.3 From cb06e98da27994ac8034f3ba387b6eeaf8a2c48f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 10 Sep 2020 13:47:53 +0300 Subject: websocket handler: Do not log client ping frames as errors --- lib/pleroma/web/mastodon_api/websocket_handler.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 94e4595d8..e6010bb4a 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -64,7 +64,9 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {:ok, %{state | timer: timer()}} end - # We never receive messages. + # We only receive pings for now + def websocket_handle(:ping, state), do: {:ok, state} + def websocket_handle(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} -- cgit v1.2.3 From e16e8f98169f822416c18778abfa8495a486c8f2 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 10 Sep 2020 13:48:24 +0300 Subject: Websocket handler: do not raise if handler is terminated before switching protocols Closes #2131 --- lib/pleroma/web/mastodon_api/websocket_handler.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index e6010bb4a..5090d9622 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -100,6 +100,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} end + # State can be `[]` only in case we terminate before switching to websocket, + # we already log errors for these cases in `init/1`, so just do nothing here + def terminate(_reason, _req, []), do: :ok + def terminate(reason, _req, state) do Logger.debug( "#{__MODULE__} terminating websocket connection for user #{ -- cgit v1.2.3 From 01fa68fe4542286519e3520793c6b59103b050ff Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 10 Sep 2020 21:26:52 +0300 Subject: Websocket handler: fix never matching code on failed auth `:cowboy_req.reply` does not return tuples since 2.0, see https://ninenines.eu/docs/en/cowboy/2.4/manual/cowboy_req.reply/ --- lib/pleroma/web/mastodon_api/websocket_handler.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 5090d9622..cf923ded8 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -37,12 +37,12 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do else {:error, :bad_topic} -> Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") - {:ok, req} = :cowboy_req.reply(404, req) + req = :cowboy_req.reply(404, req) {:ok, req, state} {:error, :unauthorized} -> Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") - {:ok, req} = :cowboy_req.reply(401, req) + req = :cowboy_req.reply(401, req) {:ok, req, state} end end -- cgit v1.2.3 From dc4e06e1991379f9f1b64774c5bdaacec96639b7 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 10 Sep 2020 21:28:07 +0300 Subject: [#2497] Removed support for thumbnail_max_* params for media preview proxy (per https://git.pleroma.social/pleroma/pleroma/-/merge_requests/2497#note_70771) --- .../web/media_proxy/media_proxy_controller.ex | 38 ++++++++-------------- 1 file changed, 14 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index acb581459..5621f72dc 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Helpers.MediaHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy + alias Plug.Conn def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, @@ -18,29 +19,29 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do ReverseProxy.call(conn, url, media_proxy_opts()) else {:enabled, false} -> - send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + send_resp(conn, 404, Conn.Status.reason_phrase(404)) {:in_banned_urls, true} -> - send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + send_resp(conn, 404, Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> - send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403)) + send_resp(conn, 403, Conn.Status.reason_phrase(403)) {:wrong_filename, filename} -> redirect(conn, external: MediaProxy.build_url(sig64, url64, filename)) end end - def preview(conn, %{"sig" => sig64, "url" => url64}) do + def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64) do handle_preview(conn, url) else {:enabled, false} -> - send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + send_resp(conn, 404, Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> - send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403)) + send_resp(conn, 403, Conn.Status.reason_phrase(403)) {:wrong_filename, filename} -> redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename)) @@ -94,10 +95,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") end - defp handle_png_preview(%{params: params} = conn, media_proxy_url) do + defp handle_png_preview(conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) - with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(), {:ok, thumbnail_binary} <- MediaHelper.image_resize( media_proxy_url, @@ -117,10 +118,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp handle_jpeg_preview(%{params: params} = conn, media_proxy_url) do + defp handle_jpeg_preview(conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) - with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(params), + with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(), {:ok, thumbnail_binary} <- MediaHelper.image_resize( media_proxy_url, @@ -157,22 +158,11 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header()) end - defp thumbnail_max_dimensions(params) do + defp thumbnail_max_dimensions() do config = Config.get([:media_preview_proxy], []) - thumbnail_max_width = - if w = params["thumbnail_max_width"] do - String.to_integer(w) - else - Keyword.fetch!(config, :thumbnail_max_width) - end - - thumbnail_max_height = - if h = params["thumbnail_max_height"] do - String.to_integer(h) - else - Keyword.fetch!(config, :thumbnail_max_height) - end + thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width) + thumbnail_max_height = Keyword.fetch!(config, :thumbnail_max_height) {thumbnail_max_width, thumbnail_max_height} end -- cgit v1.2.3 From 9bf1065a06837b4c753549d89afe23a636a20972 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 22 Aug 2020 20:46:01 +0300 Subject: schedule activity expiration in Oban --- lib/mix/tasks/pleroma/database.ex | 18 ++++-- lib/pleroma/activity.ex | 3 - lib/pleroma/activity_expiration.ex | 74 ---------------------- lib/pleroma/web/activity_pub/activity_pub.ex | 7 +- .../activity_pub/mrf/activity_expiration_policy.ex | 4 +- lib/pleroma/web/activity_pub/side_effects.ex | 6 +- lib/pleroma/web/common_api/activity_draft.ex | 2 +- lib/pleroma/web/common_api/common_api.ex | 5 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 5 +- .../cron/purge_expired_activities_worker.ex | 48 -------------- lib/pleroma/workers/purge_expired_activity.ex | 72 +++++++++++++++++++++ 11 files changed, 100 insertions(+), 144 deletions(-) delete mode 100644 lib/pleroma/activity_expiration.ex delete mode 100644 lib/pleroma/workers/cron/purge_expired_activities_worker.ex create mode 100644 lib/pleroma/workers/purge_expired_activity.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 7d8f00b08..aab4b5e9a 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -133,8 +133,7 @@ defmodule Mix.Tasks.Pleroma.Database do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) Pleroma.Activity - |> join(:left, [a], u in assoc(a, :expiration)) - |> join(:inner, [a, _u], o in Object, + |> join(:inner, [a], o in Object, on: fragment( "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')", @@ -144,14 +143,21 @@ defmodule Mix.Tasks.Pleroma.Database do ) ) |> where(local: true) - |> where([a, u], is_nil(u)) |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) - |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data)) + |> where([_a, o], fragment("?->>'type' = 'Note'", o.data)) |> Pleroma.RepoStreamer.chunk_stream(100) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> - expires_at = Timex.shift(activity.inserted_at, days: days) - Pleroma.ActivityExpiration.create(activity, expires_at, false) + expires_at = + activity.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: days) + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at, + validate: false + }) end) end) |> Stream.run() diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 97feebeaa..03cd3b8c0 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Activity do alias Pleroma.Activity alias Pleroma.Activity.Queries - alias Pleroma.ActivityExpiration alias Pleroma.Bookmark alias Pleroma.Notification alias Pleroma.Object @@ -60,8 +59,6 @@ defmodule Pleroma.Activity do # typical case. has_one(:object, Object, on_delete: :nothing, foreign_key: :id) - has_one(:expiration, ActivityExpiration, on_delete: :delete_all) - timestamps() end diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex deleted file mode 100644 index 955f0578e..000000000 --- a/lib/pleroma/activity_expiration.ex +++ /dev/null @@ -1,74 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpiration do - use Ecto.Schema - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Repo - - import Ecto.Changeset - import Ecto.Query - - @type t :: %__MODULE__{} - @min_activity_lifetime :timer.hours(1) - - schema "activity_expirations" do - belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - field(:scheduled_at, :naive_datetime) - end - - def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do - expiration - |> cast(attrs, [:scheduled_at]) - |> validate_required([:scheduled_at]) - |> validate_scheduled_at(validate_scheduled_at) - end - - def get_by_activity_id(activity_id) do - ActivityExpiration - |> where([exp], exp.activity_id == ^activity_id) - |> Repo.one() - end - - def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do - %ActivityExpiration{activity_id: activity.id} - |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at) - |> Repo.insert() - end - - def due_expirations(offset \\ 0) do - naive_datetime = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(offset, :millisecond) - - ActivityExpiration - |> where([exp], exp.scheduled_at < ^naive_datetime) - |> limit(50) - |> preload(:activity) - |> Repo.all() - |> Enum.reject(fn %{activity: activity} -> - Activity.pinned_by_actor?(activity) - end) - end - - def validate_scheduled_at(changeset, false), do: changeset - - def validate_scheduled_at(changeset, true) do - validate_change(changeset, :scheduled_at, fn _, scheduled_at -> - if not expires_late_enough?(scheduled_at) do - [scheduled_at: "an ephemeral activity must live for at least one hour"] - else - [] - end - end) - end - - def expires_late_enough?(scheduled_at) do - now = NaiveDateTime.utc_now() - diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) - diff > @min_activity_lifetime - end -end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 333621413..c33848277 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Config alias Pleroma.Constants alias Pleroma.Conversation @@ -165,7 +164,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do - with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do + with {:ok, _job} <- + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) do {:ok, activity} end end diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex index 7b4c78e0f..bee47b4ed 100644 --- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex @@ -31,10 +31,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do defp maybe_add_expiration(activity) do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) - expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days) + expires_at = DateTime.utc_now() |> Timex.shift(days: days) with %{"expires_at" => existing_expires_at} <- activity, - :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do + :lt <- DateTime.compare(existing_expires_at, expires_at) do activity else _ -> Map.put(activity, "expires_at", expires_at) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a5e2323bd..b30ca1bd7 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do """ alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Chat alias Pleroma.Chat.MessageReference alias Pleroma.FollowingRelationship @@ -189,7 +188,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end if expires_at = activity.data["expires_at"] do - ActivityExpiration.create(activity, expires_at) + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) end BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index f849b2e01..548f76609 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -202,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do additional = case draft.expires_at do - %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) + %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) _ -> additional end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 4ab533658..500c3883e 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object @@ -381,9 +380,9 @@ defmodule Pleroma.Web.CommonAPI do def check_expiry_date({:ok, nil} = res), do: res def check_expiry_date({:ok, in_seconds}) do - expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds) + expiry = DateTime.add(DateTime.utc_now(), in_seconds) - if ActivityExpiration.expires_late_enough?(expiry) do + if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do {:ok, expiry} else {:error, "Expiry date is too soon"} diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3fe1967be..ca42917fc 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do require Pleroma.Constants alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -245,8 +244,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do expires_at = with true <- client_posted_this_activity, - %ActivityExpiration{scheduled_at: scheduled_at} <- - ActivityExpiration.get_by_activity_id(activity.id) do + %Oban.Job{scheduled_at: scheduled_at} <- + Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do scheduled_at else _ -> nil diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex deleted file mode 100644 index 6549207fc..000000000 --- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do - @moduledoc """ - The worker to purge expired activities. - """ - - use Oban.Worker, queue: "activity_expiration" - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Config - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - require Logger - - @interval :timer.minutes(1) - - @impl Oban.Worker - def perform(_job) do - if Config.get([ActivityExpiration, :enabled]) do - Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1) - end - after - :ok - end - - def delete_activity(%ActivityExpiration{activity_id: activity_id}) do - with {:activity, %Activity{} = activity} <- - {:activity, Activity.get_by_id_with_object(activity_id)}, - {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do - CommonAPI.delete(activity.id, user) - else - {:activity, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}" - ) - - {:user, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}" - ) - end - end -end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex new file mode 100644 index 000000000..016b000c1 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -0,0 +1,72 @@ +defmodule Pleroma.Workers.PurgeExpiredActivity do + @moduledoc """ + Worker which purges expired activity. + """ + + use Oban.Worker, queue: :activity_expiration, max_attempts: 1 + + import Ecto.Query + + def enqueue(args) do + with true <- enabled?(), + args when is_map(args) <- validate_expires_at(args) do + {scheduled_at, args} = Map.pop(args, :expires_at) + + args + |> __MODULE__.new(scheduled_at: scheduled_at) + |> Oban.insert() + end + end + + @impl true + def perform(%Oban.Job{args: %{"activity_id" => id}}) do + with %Pleroma.Activity{} = activity <- find_activity(id), + %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do + Pleroma.Web.CommonAPI.delete(activity.id, user) + end + end + + defp enabled? do + with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do + {:error, :expired_activities_disabled} + end + end + + defp validate_expires_at(%{validate: false} = args), do: Map.delete(args, :validate) + + defp validate_expires_at(args) do + if expires_late_enough?(args[:expires_at]) do + args + else + {:error, :expiration_too_close} + end + end + + defp find_activity(id) do + with nil <- Pleroma.Activity.get_by_id_with_object(id) do + {:error, :activity_not_found} + end + end + + defp find_user(ap_id) do + with nil <- Pleroma.User.get_by_ap_id(ap_id) do + {:error, :user_not_found} + end + end + + def get_expiration(id) do + from(j in Oban.Job, + where: j.state == "scheduled", + where: j.queue == "activity_expiration", + where: fragment("?->>'activity_id' = ?", j.args, ^id) + ) + |> Pleroma.Repo.one() + end + + @spec expires_late_enough?(DateTime.t()) :: boolean() + def expires_late_enough?(scheduled_at) do + now = DateTime.utc_now() + diff = DateTime.diff(scheduled_at, now, :millisecond) + diff > :timer.hours(1) + end +end -- cgit v1.2.3 From de4c935071a47c78d873484b202e09dce5399570 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 24 Aug 2020 13:43:02 +0300 Subject: don't expire pinned posts --- lib/pleroma/activity.ex | 9 +++++++-- lib/pleroma/workers/purge_expired_activity.ex | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 03cd3b8c0..84aba9572 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -301,14 +301,14 @@ defmodule Pleroma.Activity do |> Repo.all() end - def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def follow_requests_for_actor(%User{ap_id: ap_id}) do ap_id |> Queries.by_object_id() |> Queries.by_type("Follow") |> where([a], fragment("? ->> 'state' = 'pending'", a.data)) end - def following_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def following_requests_for_actor(%User{ap_id: ap_id}) do Queries.by_type("Follow") |> where([a], fragment("?->>'state' = 'pending'", a.data)) |> where([a], a.actor == ^ap_id) @@ -343,4 +343,9 @@ defmodule Pleroma.Activity do actor = user_actor(activity) activity.id in actor.pinned_activities end + + @spec pinned_by_actor?(Activity.t(), User.t()) :: boolean() + def pinned_by_actor?(%Activity{id: id}, %User{} = user) do + id in user.pinned_activities + end end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index 016b000c1..ba0053008 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -21,8 +21,18 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do @impl true def perform(%Oban.Job{args: %{"activity_id" => id}}) do with %Pleroma.Activity{} = activity <- find_activity(id), - %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do + %Pleroma.User{} = user <- find_user(activity.object.data["actor"]), + false <- pinned_by_actor?(activity, user) do Pleroma.Web.CommonAPI.delete(activity.id, user) + else + :pinned_by_actor -> + # if activity is pinned, schedule deletion on next day + enqueue(%{activity_id: id, expires_at: DateTime.add(DateTime.utc_now(), 24 * 3600)}) + + :ok + + error -> + error end end @@ -54,6 +64,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end end + defp pinned_by_actor?(activity, user) do + with true <- Pleroma.Activity.pinned_by_actor?(activity, user) do + :pinned_by_actor + end + end + def get_expiration(id) do from(j in Oban.Job, where: j.state == "scheduled", -- cgit v1.2.3 From 629a8de9cb2ba2cc2d09679862a24031f34abc2f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 25 Aug 2020 09:10:45 +0300 Subject: deprecation warning changed namespace for activity expiration configuration --- lib/pleroma/config/deprecation_warnings.ex | 19 ++++++++++++++++++- lib/pleroma/workers/purge_expired_activity.ex | 8 +++++--- 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 2bfe4ddba..412d55a77 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Config.DeprecationWarnings do require Logger alias Pleroma.Config - @type config_namespace() :: [atom()] + @type config_namespace() :: atom() | [atom()] @type config_map() :: {config_namespace(), config_namespace(), String.t()} @mrf_config_map [ @@ -57,6 +57,7 @@ defmodule Pleroma.Config.DeprecationWarnings do check_media_proxy_whitelist_config() check_welcome_message_config() check_gun_pool_options() + check_activity_expiration_config() end def check_welcome_message_config do @@ -158,4 +159,20 @@ defmodule Pleroma.Config.DeprecationWarnings do Config.put(:pools, updated_config) end end + + @spec check_activity_expiration_config() :: :ok | nil + def check_activity_expiration_config do + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old namespace for activity expiration configuration. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later: + """ + + move_namespace_and_warn( + [ + {Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity, + "\n* `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"} + ], + warning_preface + ) + end end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index ba0053008..44a8ad0b9 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -7,6 +7,8 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do import Ecto.Query + alias Pleroma.Activity + def enqueue(args) do with true <- enabled?(), args when is_map(args) <- validate_expires_at(args) do @@ -20,7 +22,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do @impl true def perform(%Oban.Job{args: %{"activity_id" => id}}) do - with %Pleroma.Activity{} = activity <- find_activity(id), + with %Activity{} = activity <- find_activity(id), %Pleroma.User{} = user <- find_user(activity.object.data["actor"]), false <- pinned_by_actor?(activity, user) do Pleroma.Web.CommonAPI.delete(activity.id, user) @@ -53,7 +55,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end defp find_activity(id) do - with nil <- Pleroma.Activity.get_by_id_with_object(id) do + with nil <- Activity.get_by_id_with_object(id) do {:error, :activity_not_found} end end @@ -65,7 +67,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end defp pinned_by_actor?(activity, user) do - with true <- Pleroma.Activity.pinned_by_actor?(activity, user) do + with true <- Activity.pinned_by_actor?(activity, user) do :pinned_by_actor end end -- cgit v1.2.3 From 4981b5a1a3c097ca849552c3c6f650efd22c7451 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 25 Aug 2020 12:45:06 +0300 Subject: copyright header --- lib/pleroma/workers/purge_expired_activity.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index 44a8ad0b9..42e2ae79c 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Workers.PurgeExpiredActivity do @moduledoc """ Worker which purges expired activity. -- cgit v1.2.3 From 93e1c8df9dca697e7bdb822a8a5b3848b7870f53 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 3 Sep 2020 13:30:39 +0300 Subject: reject activity creation if passed expires_at option and expiring activities are not configured --- lib/pleroma/web/activity_pub/activity_pub.ex | 44 ++++++++++++++++++--------- lib/pleroma/workers/purge_expired_activity.ex | 4 +++ 2 files changed, 33 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c33848277..ee6dcf58a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -110,23 +110,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), - true <- bypass_actor_check || check_actor_is_active(map["actor"]), - {_, true} <- {:remote_limit_error, check_remote_limit(map)}, + {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, + {_, true} <- {:remote_limit_pass, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, - {:ok, map, object} <- insert_full_object(map) do - {:ok, activity} = - %Activity{ - data: map, - local: local, - actor: map["actor"], - recipients: recipients - } - |> Repo.insert() - |> maybe_create_activity_expiration() - + {:ok, map, object} <- insert_full_object(map), + {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) @@ -137,6 +128,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do %Activity{} = activity -> {:ok, activity} + {:actor_check, _} -> + {:error, false} + + {:containment, _} = error -> + error + + {:error, _} = error -> + error + {:fake, true, map, recipients} -> activity = %Activity{ data: map, @@ -149,11 +149,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) {:ok, activity} - error -> - {:error, error} + {:remote_limit_pass, _} -> + {:error, :remote_limit} + + {:reject, reason} -> + {:error, reason} end end + defp insert_activity_with_expiration(data, local, recipients) do + %Activity{ + data: data, + local: local, + actor: data["actor"], + recipients: recipients + } + |> Repo.insert() + |> maybe_create_activity_expiration() + end + def notify_and_stream(activity) do Notification.create_notifications(activity) diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index 42e2ae79c..c70587b47 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -13,6 +13,10 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do alias Pleroma.Activity + @spec enqueue(map()) :: + {:ok, Oban.Job.t()} + | {:error, :expired_activities_disabled} + | {:error, :expiration_too_close} def enqueue(args) do with true <- enabled?(), args when is_map(args) <- validate_expires_at(args) do -- cgit v1.2.3 From 357d971a10c28780795af4d19b37b0ac80d6ad09 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 3 Sep 2020 17:56:20 +0300 Subject: expiration for new pipeline --- lib/pleroma/web/activity_pub/activity_pub.ex | 18 ++++++++++++------ lib/pleroma/web/activity_pub/side_effects.ex | 7 ------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index ee6dcf58a..66a9f78a3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -101,7 +101,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do local: local, recipients: recipients, actor: object["actor"] - }) do + }), + # TODO: add tests for expired activities, when Note type will be supported in new pipeline + {:ok, _} <- maybe_create_activity_expiration(activity) do {:ok, activity, meta} end end @@ -158,14 +160,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp insert_activity_with_expiration(data, local, recipients) do - %Activity{ + struct = %Activity{ data: data, local: local, actor: data["actor"], recipients: recipients } - |> Repo.insert() - |> maybe_create_activity_expiration() + + with {:ok, activity} <- Repo.insert(struct) do + maybe_create_activity_expiration(activity) + end end def notify_and_stream(activity) do @@ -177,7 +181,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do stream_out_participations(participations) end - defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do + defp maybe_create_activity_expiration( + %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity + ) do with {:ok, _job} <- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ activity_id: activity.id, @@ -187,7 +193,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - defp maybe_create_activity_expiration(result), do: result + defp maybe_create_activity_expiration(activity), do: {:ok, activity} defp create_or_bump_conversation(activity, actor) do with {:ok, conversation} <- Conversation.create_or_bump_for(activity), diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b30ca1bd7..46a8be767 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -187,13 +187,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Object.increase_replies_count(in_reply_to) end - if expires_at = activity.data["expires_at"] do - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: activity.id, - expires_at: expires_at - }) - end - BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) meta = -- cgit v1.2.3 From b3485a6dbfb1a16dd5604294074ef5139fbf3ce9 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 3 Sep 2020 19:02:22 +0300 Subject: little clean up --- lib/pleroma/workers/purge_expired_activity.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index c70587b47..4be146194 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do {scheduled_at, args} = Map.pop(args, :expires_at) args - |> __MODULE__.new(scheduled_at: scheduled_at) + |> new(scheduled_at: scheduled_at) |> Oban.insert() end end -- cgit v1.2.3 From eb5ff715f7917e174b9ae104a5d82779ff925301 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 4 Sep 2020 11:40:32 +0300 Subject: pin/unpin for activities with expires_at option --- lib/pleroma/activity.ex | 5 ----- lib/pleroma/user.ex | 17 ++++++++++++++++- lib/pleroma/workers/purge_expired_activity.ex | 18 +----------------- 3 files changed, 17 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 84aba9572..17af04257 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -343,9 +343,4 @@ defmodule Pleroma.Activity do actor = user_actor(activity) activity.id in actor.pinned_activities end - - @spec pinned_by_actor?(Activity.t(), User.t()) :: boolean() - def pinned_by_actor?(%Activity{id: id}, %User{} = user) do - id in user.pinned_activities - end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f323fc6ed..e73d19964 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2315,6 +2315,11 @@ defmodule Pleroma.User do max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) params = %{pinned_activities: user.pinned_activities ++ [id]} + # if pinned activity was scheduled for deletion, we remove job + if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do + Oban.cancel_job(expiration.id) + end + user |> cast(params, [:pinned_activities]) |> validate_length(:pinned_activities, @@ -2327,9 +2332,19 @@ defmodule Pleroma.User do |> update_and_set_cache() end - def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do + def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do params = %{pinned_activities: List.delete(user.pinned_activities, id)} + # if pinned activity was scheduled for deletion, we reschedule it for deletion + if data["expires_at"] do + {:ok, expires_at, _} = DateTime.from_iso8601(data["expires_at"]) + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: id, + expires_at: expires_at + }) + end + user |> cast(params, [:pinned_activities]) |> update_and_set_cache() diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index 4be146194..f981eda8e 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -31,18 +31,8 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do @impl true def perform(%Oban.Job{args: %{"activity_id" => id}}) do with %Activity{} = activity <- find_activity(id), - %Pleroma.User{} = user <- find_user(activity.object.data["actor"]), - false <- pinned_by_actor?(activity, user) do + %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do Pleroma.Web.CommonAPI.delete(activity.id, user) - else - :pinned_by_actor -> - # if activity is pinned, schedule deletion on next day - enqueue(%{activity_id: id, expires_at: DateTime.add(DateTime.utc_now(), 24 * 3600)}) - - :ok - - error -> - error end end @@ -74,12 +64,6 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end end - defp pinned_by_actor?(activity, user) do - with true <- Activity.pinned_by_actor?(activity, user) do - :pinned_by_actor - end - end - def get_expiration(id) do from(j in Oban.Job, where: j.state == "scheduled", -- cgit v1.2.3 From f24828a3e848e6ce3bcdd254e8c6e451898cfdf7 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 7 Sep 2020 20:21:32 +0300 Subject: oban warning --- lib/pleroma/config/oban.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index 81758c93d..9f601b1a3 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -5,7 +5,11 @@ defmodule Pleroma.Config.Oban do oban_config = Pleroma.Config.get(Oban) crontab = - [Pleroma.Workers.Cron.StatsWorker, Pleroma.Workers.Cron.ClearOauthTokenWorker] + [ + Pleroma.Workers.Cron.StatsWorker, + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker, + Pleroma.Workers.Cron.ClearOauthTokenWorker + ] |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc -> with acc when is_list(acc) <- acc, setting when is_tuple(setting) <- -- cgit v1.2.3 From 2c2094d4b2722cf511e3db8288c3754a48038f05 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 7 Sep 2020 20:57:38 +0300 Subject: configurable lifetime for ephemeral activities --- lib/pleroma/workers/purge_expired_activity.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index f981eda8e..ffcb89dc3 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -77,6 +77,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do def expires_late_enough?(scheduled_at) do now = DateTime.utc_now() diff = DateTime.diff(scheduled_at, now, :millisecond) - diff > :timer.hours(1) + min_lifetime = Pleroma.Config.get([__MODULE__, :min_lifetime], 600) + diff > :timer.seconds(min_lifetime) end end -- cgit v1.2.3 From 15aece72382fe1862a58728b9d02990147f91365 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 8 Sep 2020 15:11:18 +0300 Subject: remove validate_expires_at from enqueue method --- lib/mix/tasks/pleroma/database.ex | 3 +-- lib/pleroma/workers/purge_expired_activity.ex | 13 +------------ 2 files changed, 2 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index aab4b5e9a..7f1108dcf 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -155,8 +155,7 @@ defmodule Mix.Tasks.Pleroma.Database do Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ activity_id: activity.id, - expires_at: expires_at, - validate: false + expires_at: expires_at }) end) end) diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index ffcb89dc3..c168890a2 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -18,8 +18,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do | {:error, :expired_activities_disabled} | {:error, :expiration_too_close} def enqueue(args) do - with true <- enabled?(), - args when is_map(args) <- validate_expires_at(args) do + with true <- enabled?() do {scheduled_at, args} = Map.pop(args, :expires_at) args @@ -42,16 +41,6 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end end - defp validate_expires_at(%{validate: false} = args), do: Map.delete(args, :validate) - - defp validate_expires_at(args) do - if expires_late_enough?(args[:expires_at]) do - args - else - {:error, :expiration_too_close} - end - end - defp find_activity(id) do with nil <- Activity.get_by_id_with_object(id) do {:error, :activity_not_found} -- cgit v1.2.3 From 4d18a50f3c4b6654339a6a8df71160e23b45cac0 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 10 Sep 2020 21:54:26 +0300 Subject: [#2497] Formatting fix. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 5621f72dc..ff7fd2409 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -158,7 +158,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header()) end - defp thumbnail_max_dimensions() do + defp thumbnail_max_dimensions do config = Config.get([:media_preview_proxy], []) thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width) -- cgit v1.2.3 From da876d09e89bcfec6f2d1eaddb396f68ce48e12a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 10 Sep 2020 23:13:51 +0200 Subject: federator: normalize only actor, catch actor error --- lib/pleroma/web/federator/federator.ex | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f5803578d..e4ab9ba32 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -66,14 +66,17 @@ defmodule Pleroma.Web.Federator do def perform(:incoming_ap_doc, params) do Logger.debug("Handling incoming AP activity") - params = Utils.normalize_params(params) + actor = + params + |> Map.get("actor") + |> Utils.get_ap_id() # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {:ok, _user} <- ap_enabled_actor(params["actor"]), + with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- - {:correct_origin?, Containment.contain_origin_from_id(params["actor"], params)}, + {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, {:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, activity} else @@ -85,10 +88,13 @@ defmodule Pleroma.Web.Federator do Logger.debug("Already had #{params["id"]}") {:error, :already_present} + {:actor, e} -> + Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") + {:error, e} + e -> # Just drop those for now - Logger.debug("Unhandled activity") - Logger.debug(Jason.encode!(params, pretty: true)) + Logger.debug("Unhandled activity\n" <> Jason.encode!(params, pretty: true)) {:error, e} end end -- cgit v1.2.3 From b73e9ef68689a7094e80e2affa0af9b05e86effb Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 25 Aug 2020 09:19:53 +0200 Subject: transmogrifier: Call strip_internal_fields on pipeline ingestion --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index af4384213..ec3b24206 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -550,6 +550,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do _options ) when objtype in ~w{Question Answer ChatMessage Audio Event} do + data = Map.put(data, "object", strip_internal_fields(data["object"])) + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} -- cgit v1.2.3 From 846b59ccb09681bda0f54bed43f5b82883228e33 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 20 Aug 2020 02:00:04 +0200 Subject: Pipeline Ingestion: Video --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/object_validator.ex | 16 +-- .../object_validators/audio_validator.ex | 107 ---------------- .../object_validators/audio_video_validator.ex | 134 +++++++++++++++++++++ .../activity_pub/object_validators/common_fixes.ex | 9 ++ .../object_validators/create_generic_validator.ex | 8 +- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- lib/pleroma/web/activity_pub/transmogrifier.ex | 37 +----- 8 files changed, 162 insertions(+), 153 deletions(-) delete mode 100644 lib/pleroma/web/activity_pub/object_validators/audio_validator.ex create mode 100644 lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 66a9f78a3..bceec8bd1 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -84,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Event] + @object_types ~w[ChatMessage Question Answer Audio Video Event] @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index b77c06395..081f96389 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,11 +12,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Object.Containment alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator @@ -149,10 +150,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end - def validate(%{"type" => "Audio"} = object, meta) do + def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do with {:ok, object} <- object - |> AudioValidator.cast_and_validate() + |> AudioVideoValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} @@ -198,7 +199,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Event] do + when objtype in ~w[Question Answer Audio Video Event] do with {:ok, object_data} <- cast_and_apply(object), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), {:ok, create_activity} <- @@ -232,8 +233,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do AnswerValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => "Audio"} = object) do - AudioValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do + AudioVideoValidator.cast_and_apply(object) end def cast_and_apply(%{"type" => "Event"} = object) do @@ -262,7 +263,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def stringify_keys(object), do: object def fetch_actor(object) do - with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do + with actor <- Containment.get_actor(object), + {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do User.get_or_fetch_by_ap_id(actor) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex deleted file mode 100644 index 1a97c504a..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex +++ /dev/null @@ -1,107 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes - alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations - alias Pleroma.Web.ActivityPub.Transmogrifier - - import Ecto.Changeset - - @primary_key false - @derive Jason.Encoder - - embedded_schema do - field(:id, ObjectValidators.ObjectID, primary_key: true) - field(:to, ObjectValidators.Recipients, default: []) - field(:cc, ObjectValidators.Recipients, default: []) - field(:bto, ObjectValidators.Recipients, default: []) - field(:bcc, ObjectValidators.Recipients, default: []) - # TODO: Write type - field(:tag, {:array, :map}, default: []) - field(:type, :string) - field(:content, :string) - field(:context, :string) - - # TODO: Remove actor on objects - field(:actor, ObjectValidators.ObjectID) - - field(:attributedTo, ObjectValidators.ObjectID) - field(:summary, :string) - field(:published, ObjectValidators.DateTime) - field(:emoji, ObjectValidators.Emoji, default: %{}) - field(:sensitive, :boolean, default: false) - embeds_many(:attachment, AttachmentValidator) - field(:replies_count, :integer, default: 0) - field(:like_count, :integer, default: 0) - field(:announcement_count, :integer, default: 0) - field(:inReplyTo, :string) - field(:url, ObjectValidators.Uri) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) - end - - def cast_and_apply(data) do - data - |> cast_data - |> apply_action(:insert) - end - - def cast_and_validate(data) do - data - |> cast_data() - |> validate_data() - end - - def cast_data(data) do - %__MODULE__{} - |> changeset(data) - end - - defp fix_url(%{"url" => url} = data) when is_list(url) do - attachment = - Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end) - - link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end) - - data - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) - end - - defp fix_url(data), do: data - - defp fix(data) do - data - |> CommonFixes.fix_defaults() - |> CommonFixes.fix_attribution() - |> Transmogrifier.fix_emoji() - |> fix_url() - end - - def changeset(struct, data) do - data = fix(data) - - struct - |> cast(data, __schema__(:fields) -- [:attachment]) - |> cast_embed(:attachment) - end - - def validate_data(data_cng) do - data_cng - |> validate_inclusion(:type, ["Audio"]) - |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) - |> CommonValidations.validate_any_presence([:cc, :to]) - |> CommonValidations.validate_fields_match([:actor, :attributedTo]) - |> CommonValidations.validate_actor_presence() - |> CommonValidations.validate_host_match() - end -end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex new file mode 100644 index 000000000..a6119e627 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex @@ -0,0 +1,134 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do + use Ecto.Schema + + alias Pleroma.EarmarkRenderer + alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Ecto.Changeset + + @primary_key false + @derive Jason.Encoder + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:bto, ObjectValidators.Recipients, default: []) + field(:bcc, ObjectValidators.Recipients, default: []) + # TODO: Write type + field(:tag, {:array, :map}, default: []) + field(:type, :string) + + field(:name, :string) + field(:summary, :string) + field(:content, :string) + + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) + + # TODO: Remove actor on objects + field(:actor, ObjectValidators.ObjectID) + + field(:attributedTo, ObjectValidators.ObjectID) + field(:published, ObjectValidators.DateTime) + field(:emoji, ObjectValidators.Emoji, default: %{}) + field(:sensitive, :boolean, default: false) + embeds_many(:attachment, AttachmentValidator) + field(:replies_count, :integer, default: 0) + field(:like_count, :integer, default: 0) + field(:announcement_count, :integer, default: 0) + field(:inReplyTo, ObjectValidators.ObjectID) + field(:url, ObjectValidators.Uri) + + field(:likes, {:array, :string}, default: []) + field(:announcements, {:array, :string}, default: []) + end + + def cast_and_apply(data) do + data + |> cast_data + |> apply_action(:insert) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + defp fix_url(%{"url" => url} = data) when is_list(url) do + attachment = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"]) + end) + + link_element = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and mime_type == "text/html" + end) + + data + |> Map.put("attachment", [attachment]) + |> Map.put("url", link_element["href"]) + end + + defp fix_url(data), do: data + + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data) + when is_binary(content) do + content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.put(data, "content", content) + end + + defp fix_content(data), do: data + + defp fix(data) do + data + |> CommonFixes.fix_defaults() + |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() + |> Transmogrifier.fix_emoji() + |> fix_url() + |> fix_content() + end + + def changeset(struct, data) do + data = fix(data) + + struct + |> cast(data, __schema__(:fields) -- [:attachment]) + |> cast_embed(:attachment) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Audio", "Video"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) + |> CommonValidations.validate_any_presence([:cc, :to]) + |> CommonValidations.validate_fields_match([:actor, :attributedTo]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_host_match() + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 720213d73..b3638cfc7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do + alias Pleroma.Object.Containment alias Pleroma.Web.ActivityPub.Utils # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults @@ -19,4 +20,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do data |> Map.put_new("actor", data["attributedTo"]) end + + def fix_actor(data) do + actor = Containment.get_actor(data) + + data + |> Map.put("actor", actor) + |> Map.put("attributedTo", actor) + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex index b3dbeea57..422ee07be 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex @@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations import Ecto.Changeset - import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @primary_key false @@ -75,14 +76,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do data |> fix_context(meta) |> fix_addressing(meta) + |> CommonFixes.fix_actor() end def validate_data(cng, meta \\ []) do cng |> validate_required([:actor, :type, :object]) |> validate_inclusion(:type, ["Create"]) - |> validate_actor_presence() - |> validate_any_presence([:to, :cc]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_any_presence([:to, :cc]) |> validate_actors_match(meta) |> validate_context_match(meta) |> validate_object_nonexistence() diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 46a8be767..b5c720c7a 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -336,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end def handle_object_creation(%{"type" => objtype} = object, meta) - when objtype in ~w[Audio Question Event] do + when objtype in ~w[Audio Video Question Event] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ec3b24206..e14936c10 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity - alias Pleroma.EarmarkRenderer alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Maps alias Pleroma.Object @@ -45,7 +44,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_addressing |> fix_summary |> fix_type(options) - |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -274,24 +272,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Map.put(object, "url", url["href"]) end - def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do - attachment = - Enum.find(url, fn x -> - media_type = x["mediaType"] || x["mimeType"] || "" - - is_map(x) and String.starts_with?(media_type, "video/") - end) - - link_element = - Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end) - - object - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) - end - - def fix_url(%{"type" => object_type, "url" => url} = object) - when object_type != "Video" and is_list(url) do + def fix_url(%{"url" => url} = object) when is_list(url) do first_element = Enum.at(url, 0) url_string = @@ -371,18 +352,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(object, _), do: object - defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) - when is_binary(content) do - html_content = - content - |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) - |> Pleroma.HTML.filter_tags() - - Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) - end - - defp fix_content(object), do: object - # Reduce the object list to find the reported user. defp get_reported(objects) do Enum.reduce_while(objects, nil, fn ap_id, _ -> @@ -455,7 +424,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, options ) - when objtype in ~w{Article Note Video Page} do + when objtype in ~w{Article Note Page} do actor = Containment.get_actor(data) with nil <- Activity.get_create_by_object_ap_id(object["id"]), @@ -549,7 +518,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype}} = data, _options ) - when objtype in ~w{Question Answer ChatMessage Audio Event} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event} do data = Map.put(data, "object", strip_internal_fields(data["object"])) with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), -- cgit v1.2.3 From 2132b24a9df8116e12abc8c458cff4c3850aeda0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 20 Aug 2020 04:27:59 +0200 Subject: object_validators: likes & announcements as [ObjectID] --- .../web/activity_pub/object_validators/audio_video_validator.ex | 4 ++-- lib/pleroma/web/activity_pub/object_validators/note_validator.ex | 4 ++-- lib/pleroma/web/activity_pub/object_validators/question_validator.ex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex index a6119e627..16973e5db 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex @@ -49,8 +49,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do field(:inReplyTo, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end def cast_and_apply(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex index ab4469a59..e47cbaaea 100644 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -43,8 +43,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do field(:inReplyTo, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end def cast_and_validate(data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 934d3c1ea..9310485dc 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do # short identifier for PleromaFE to group statuses by context field(:context_id, :integer) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) -- cgit v1.2.3 From 1b3d5956b1be7faac4e1230d788307650acce991 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 20 Aug 2020 20:03:07 +0200 Subject: Pipeline Ingestion: Article --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/object_validator.ex | 17 +++- .../object_validators/article_note_validator.ex | 106 +++++++++++++++++++++ .../object_validators/note_validator.ex | 73 -------------- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 +- 6 files changed, 126 insertions(+), 78 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex delete mode 100644 lib/pleroma/web/activity_pub/object_validators/note_validator.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index bceec8bd1..3ab045737 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -84,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Video Event] + @object_types ~w[ChatMessage Question Answer Audio Video Event Article] @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 081f96389..bd0a2a8dc 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator @@ -160,6 +161,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end + def validate(%{"type" => "Article"} = object, meta) do + with {:ok, object} <- + object + |> ArticleNoteValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + def validate(%{"type" => "Answer"} = object, meta) do with {:ok, object} <- object @@ -199,7 +210,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Video Event] do + when objtype in ~w[Question Answer Audio Video Event Article] do with {:ok, object_data} <- cast_and_apply(object), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), {:ok, create_activity} <- @@ -241,6 +252,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do EventValidator.cast_and_apply(object) end + def cast_and_apply(%{"type" => "Article"} = object) do + ArticleNoteValidator.cast_and_apply(object) + end + def cast_and_apply(o), do: {:error, {:validator_not_set, o}} # is_struct/1 isn't present in Elixir 1.8.x diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex new file mode 100644 index 000000000..5b7dad517 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do + use Ecto.Schema + + alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Ecto.Changeset + + @primary_key false + @derive Jason.Encoder + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:bto, ObjectValidators.Recipients, default: []) + field(:bcc, ObjectValidators.Recipients, default: []) + # TODO: Write type + field(:tag, {:array, :map}, default: []) + field(:type, :string) + + field(:name, :string) + field(:summary, :string) + field(:content, :string) + + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) + + # TODO: Remove actor on objects + field(:actor, ObjectValidators.ObjectID) + + field(:attributedTo, ObjectValidators.ObjectID) + field(:published, ObjectValidators.DateTime) + field(:emoji, ObjectValidators.Emoji, default: %{}) + field(:sensitive, :boolean, default: false) + embeds_many(:attachment, AttachmentValidator) + field(:replies_count, :integer, default: 0) + field(:like_count, :integer, default: 0) + field(:announcement_count, :integer, default: 0) + field(:inReplyTo, ObjectValidators.ObjectID) + field(:url, ObjectValidators.Uri) + + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) + end + + def cast_and_apply(data) do + data + |> cast_data + |> apply_action(:insert) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + data = fix(data) + + %__MODULE__{} + |> changeset(data) + end + + defp fix_url(%{"url" => url} = data) when is_map(url) do + Map.put(data, "url", url["href"]) + end + + defp fix_url(data), do: data + + defp fix(data) do + data + |> CommonFixes.fix_defaults() + |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() + |> fix_url() + |> Transmogrifier.fix_emoji() + end + + def changeset(struct, data) do + data = fix(data) + + struct + |> cast(data, __schema__(:fields) -- [:attachment]) + |> cast_embed(:attachment) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Article", "Note"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) + |> CommonValidations.validate_any_presence([:cc, :to]) + |> CommonValidations.validate_fields_match([:actor, :attributedTo]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_host_match() + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex deleted file mode 100644 index e47cbaaea..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ /dev/null @@ -1,73 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - alias Pleroma.Web.ActivityPub.Transmogrifier - - import Ecto.Changeset - - @primary_key false - - embedded_schema do - field(:id, ObjectValidators.ObjectID, primary_key: true) - field(:to, ObjectValidators.Recipients, default: []) - field(:cc, ObjectValidators.Recipients, default: []) - field(:bto, ObjectValidators.Recipients, default: []) - field(:bcc, ObjectValidators.Recipients, default: []) - # TODO: Write type - field(:tag, {:array, :map}, default: []) - field(:type, :string) - - field(:name, :string) - field(:summary, :string) - field(:content, :string) - - field(:context, :string) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - - field(:actor, ObjectValidators.ObjectID) - field(:attributedTo, ObjectValidators.ObjectID) - field(:published, ObjectValidators.DateTime) - field(:emoji, ObjectValidators.Emoji, default: %{}) - field(:sensitive, :boolean, default: false) - # TODO: Write type - field(:attachment, {:array, :map}, default: []) - field(:replies_count, :integer, default: 0) - field(:like_count, :integer, default: 0) - field(:announcement_count, :integer, default: 0) - field(:inReplyTo, ObjectValidators.ObjectID) - field(:url, ObjectValidators.Uri) - - field(:likes, {:array, ObjectValidators.ObjectID}, default: []) - field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) - end - - def cast_and_validate(data) do - data - |> cast_data() - |> validate_data() - end - - defp fix(data) do - data - |> Transmogrifier.fix_emoji() - end - - def cast_data(data) do - data = fix(data) - - %__MODULE__{} - |> cast(data, __schema__(:fields)) - end - - def validate_data(data_cng) do - data_cng - |> validate_inclusion(:type, ["Note"]) - |> validate_required([:id, :actor, :to, :cc, :type, :content, :context]) - end -end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b5c720c7a..b9a83a544 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -336,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end def handle_object_creation(%{"type" => objtype} = object, meta) - when objtype in ~w[Audio Video Question Event] do + when objtype in ~w[Audio Video Question Event Article] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index e14936c10..80f529704 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -424,7 +424,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, options ) - when objtype in ~w{Article Note Page} do + when objtype in ~w{Note Page} do actor = Containment.get_actor(data) with nil <- Activity.get_create_by_object_ap_id(object["id"]), @@ -518,7 +518,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype}} = data, _options ) - when objtype in ~w{Question Answer ChatMessage Audio Video Event} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do data = Map.put(data, "object", strip_internal_fields(data["object"])) with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), -- cgit v1.2.3 From f18178cb096b9a00ed12ff0fe36893f118ec6649 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 22 Aug 2020 02:01:33 +0200 Subject: AttachmentValidator: directly embed url schema and pass it fix_media_type --- .../object_validators/attachment_validator.ex | 21 +++++++++++++++++-- .../object_validators/url_object_validator.ex | 24 ---------------------- 2 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index c8b148280..df102a134 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do use Ecto.Schema + alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator import Ecto.Changeset @@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:mediaType, :string, default: "application/octet-stream") field(:name, :string) - embeds_many(:url, UrlObjectValidator) + embeds_many :url, UrlObjectValidator, primary_key: false do + field(:type, :string) + field(:href, ObjectValidators.Uri) + field(:mediaType, :string, default: "application/octet-stream") + end end def cast_and_validate(data) do @@ -37,7 +42,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do struct |> cast(data, [:type, :mediaType, :name]) - |> cast_embed(:url, required: true) + |> cast_embed(:url, with: &url_changeset/2) + |> validate_inclusion(:type, ~w[Link Document Audio Image Video]) + |> validate_required([:type, :mediaType, :url]) + end + + def url_changeset(struct, data) do + data = fix_media_type(data) + + struct + |> cast(data, [:type, :href, :mediaType]) + |> validate_inclusion(:type, ["Link"]) + |> validate_required([:type, :href, :mediaType]) end def fix_media_type(data) do @@ -75,6 +91,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do def validate_data(cng) do cng + |> validate_inclusion(:type, ~w[Document Audio Image Video]) |> validate_required([:mediaType, :url, :type]) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex deleted file mode 100644 index 881030f38..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - - import Ecto.Changeset - @primary_key false - - embedded_schema do - field(:type, :string) - field(:href, ObjectValidators.Uri) - field(:mediaType, :string, default: "application/octet-stream") - end - - def changeset(struct, data) do - struct - |> cast(data, __schema__(:fields)) - |> validate_required([:type, :href, :mediaType]) - end -end -- cgit v1.2.3 From dbc013f24c3885960714425f201e372335d22345 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 11 Sep 2020 11:22:50 +0200 Subject: instance: Handle not getting a favicon --- lib/pleroma/instances/instance.ex | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 8bf53c090..6948651c7 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -159,13 +159,11 @@ defmodule Pleroma.Instances.Instance do Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], adapter: [pool: :media] ), - favicon_rel <- - html - |> Floki.parse_document!() - |> Floki.attribute("link[rel=icon]", "href") - |> List.first(), - favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), - true <- is_binary(favicon) do + {_, [favicon_rel | _]} when is_binary(favicon_rel) <- + {:parse, + html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")}, + {_, favicon} when is_binary(favicon) <- + {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do favicon else _ -> nil -- cgit v1.2.3 From 36c9197ac36707cdfe3d679bbd64972b4b03ea84 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Fri, 11 Sep 2020 10:46:16 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/pleroma/web/federator/federator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index e4ab9ba32..130654145 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -94,7 +94,7 @@ defmodule Pleroma.Web.Federator do e -> # Just drop those for now - Logger.debug("Unhandled activity\n" <> Jason.encode!(params, pretty: true)) + Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) {:error, e} end end -- cgit v1.2.3 From 89a7efab69d905cc3521388b1e1cf43851848627 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 11 Sep 2020 14:22:54 +0300 Subject: ConnectionPool: Log possible HTTP1 blocks --- lib/pleroma/gun/conn.ex | 12 ++++++------ lib/pleroma/gun/connection_pool/worker.ex | 22 ++++++++++++++++------ lib/pleroma/telemetry/logger.ex | 18 ++++++++++++++++-- 3 files changed, 38 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index 75b1ffc0a..477e19c6e 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -50,10 +50,10 @@ defmodule Pleroma.Gun.Conn do with open_opts <- Map.delete(opts, :tls_opts), {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts), - {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]), + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]), stream <- Gun.connect(conn, connect_opts), {:response, :fin, 200, _} <- Gun.await(conn, stream) do - {:ok, conn} + {:ok, conn, protocol} else error -> Logger.warn( @@ -88,8 +88,8 @@ defmodule Pleroma.Gun.Conn do |> Map.put(:socks_opts, socks_opts) with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( @@ -106,8 +106,8 @@ defmodule Pleroma.Gun.Conn do host = Pleroma.HTTP.AdapterHelper.parse_host(host) with {:ok, conn} <- Gun.open(host, port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:connect_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index c36332817..49d41e4c7 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do @impl true def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do - with {:ok, conn_pid} <- Gun.Conn.open(uri, opts), + with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts), Process.link(conn_pid) do time = :erlang.monotonic_time(:millisecond) @@ -27,8 +27,12 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do send(client_pid, {:conn_pid, conn_pid}) {:noreply, - %{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}}, - :hibernate} + %{ + key: key, + timer: nil, + client_monitors: %{client_pid => Process.monitor(client_pid)}, + protocol: protocol + }, :hibernate} else err -> {:stop, {:shutdown, err}, nil} @@ -53,14 +57,20 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do end @impl true - def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do + def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do time = :erlang.monotonic_time(:millisecond) - {{conn_pid, _, _, _}, _} = + {{conn_pid, used_by, _, _}, _} = Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time} end) + :telemetry.execute( + [:pleroma, :connection_pool, :client, :add], + %{client_pid: client_pid, clients: used_by}, + %{key: state.key, protocol: protocol} + ) + state = if state.timer != nil do Process.cancel_timer(state[:timer]) @@ -131,7 +141,7 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do @impl true def handle_info({:DOWN, _ref, :process, pid, reason}, state) do :telemetry.execute( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: pid, reason: reason}, %{key: state.key} ) diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 4cacae02f..197b1d091 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -7,7 +7,8 @@ defmodule Pleroma.Telemetry.Logger do [:pleroma, :connection_pool, :reclaim, :start], [:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :provision_failure], - [:pleroma, :connection_pool, :client_death] + [:pleroma, :connection_pool, :client, :dead], + [:pleroma, :connection_pool, :client, :add] ] def attach do :telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) @@ -62,7 +63,7 @@ defmodule Pleroma.Telemetry.Logger do end def handle_event( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: client_pid, reason: reason}, %{key: key}, _ @@ -73,4 +74,17 @@ defmodule Pleroma.Telemetry.Logger do }" end) end + + def handle_event( + [:pleroma, :connection_pool, :client, :add], + %{clients: [_, _ | _] = clients}, + %{key: key, protocol: :http}, + _ + ) do + Logger.info(fn -> + "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." + end) + end + + def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok end -- cgit v1.2.3 From f1f44069ae525fd21127e5ceccc61016c12f4427 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 11 Sep 2020 19:58:58 +0200 Subject: Fetcher: Correctly return MRF reject reason --- lib/pleroma/object/fetcher.ex | 4 ++-- lib/pleroma/web/activity_pub/activity_pub.ex | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 1de2ce6c3..24dc7cb95 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -98,8 +98,8 @@ defmodule Pleroma.Object.Fetcher do {:containment, _} -> {:error, "Object containment failed."} - {:transmogrifier, {:error, {:reject, nil}}} -> - {:reject, nil} + {:transmogrifier, {:error, {:reject, e}}} -> + {:reject, e} {:transmogrifier, _} = e -> {:error, e} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 66a9f78a3..b2205bff7 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -154,8 +154,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:remote_limit_pass, _} -> {:error, :remote_limit} - {:reject, reason} -> - {:error, reason} + {:reject, _} = e -> + {:error, e} end end -- cgit v1.2.3 From f88dc1937e5aa4208143fa68400a5c38a1b9eddf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 31 Aug 2020 16:48:24 -0500 Subject: MastodonAPI.StatusView.get_user/1 --> CommonAPI.get_user/1 --- lib/pleroma/web/admin_api/views/status_view.ex | 3 ++- lib/pleroma/web/common_api/common_api.ex | 17 +++++++++++++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 25 ++++------------------ lib/pleroma/web/pleroma_api/views/scrobble_view.ex | 4 ++-- 4 files changed, 25 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 500800be2..6042a22b6 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do require Pleroma.Constants alias Pleroma.Web.AdminAPI + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -17,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do end def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do - user = MastodonAPI.StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) MastodonAPI.StatusView.render("show.json", opts) |> Map.merge(%{account: merge_account_views(user)}) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5ad2b91c2..d6e9d3d67 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -550,4 +550,21 @@ defmodule Pleroma.Web.CommonAPI do def show_reblogs(%User{} = user, %User{} = target) do UserRelationship.delete_reblog_mute(user, target) end + + def get_user(ap_id, fake_record_fallback \\ true) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + fake_record_fallback -> + # TODO: refactor (fake records is never a good idea) + User.error_user(ap_id) + + true -> + nil + end + end end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3fe1967be..66732d09e 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -56,23 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end) end - def get_user(ap_id, fake_record_fallback \\ true) do - cond do - user = User.get_cached_by_ap_id(ap_id) -> - user - - user = User.get_by_guessed_nickname(ap_id) -> - user - - fake_record_fallback -> - # TODO: refactor (fake records is never a good idea) - User.error_user(ap_id) - - true -> - nil - end - end - defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id), do: context_id @@ -120,7 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Note: unresolved users are filtered out actors = (activities ++ parent_activities) - |> Enum.map(&get_user(&1.data["actor"], false)) + |> Enum.map(&CommonAPI.get_user(&1.data["actor"], false)) |> Enum.filter(& &1) UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes) @@ -139,7 +122,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do "show.json", %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts ) do - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) activity_object = Object.normalize(activity) @@ -212,7 +195,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do object = Object.normalize(activity) - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) user_follower_address = user.follower_address like_count = object.data["like_count"] || 0 @@ -266,7 +249,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do reply_to = get_reply_to(activity, opts) - reply_to_user = reply_to && get_user(reply_to.data["actor"]) + reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) content = object diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index bbff93abe..95bd4c368 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.Object + alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.StatusView def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do object = Object.normalize(activity) - user = StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) %{ -- cgit v1.2.3 From b40a627ab02f9f63eac42ce6fc65282fc6cb6b92 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 31 Aug 2020 19:56:05 -0500 Subject: AdminAPI: delete a chat message --- lib/pleroma/moderation_log.ex | 24 ++++++++++++ .../web/admin_api/controllers/chat_controller.ex | 37 ++++++++++++++++++ .../api_spec/operations/admin/chat_operation.ex | 44 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 2 + 4 files changed, 107 insertions(+) create mode 100644 lib/pleroma/web/admin_api/controllers/chat_controller.ex create mode 100644 lib/pleroma/web/api_spec/operations/admin/chat_operation.ex (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 31c9afe2a..47036a6f6 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -320,6 +320,19 @@ defmodule Pleroma.ModerationLog do |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: + {:ok, ModerationLog} | {:error, any} + def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor.nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + } + |> insert_log_entry_with_message() + end + @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any} defp insert_log_entry_with_message(entry) do entry.data["message"] @@ -627,6 +640,17 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}" end + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + }) do + "@#{actor_nickname} deleted chat message ##{subject_id}" + end + defp nicknames_to_string(nicknames) do nicknames |> Enum.map(&"@#{&1}") diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex new file mode 100644 index 000000000..bcce824d2 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.ModerationLog + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.CommonAPI + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["write:chats"], admin: true} when action in [:delete_message] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation + + def delete_message(%{assigns: %{user: user}} = conn, %{message_id: id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + ModerationLog.insert_log(%{ + action: "chat_message_delete", + actor: user, + subject_id: id + }) + + json(conn, %{}) + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex new file mode 100644 index 000000000..7045fd7ce --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do + alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def delete_message_operation do + %Operation{ + tags: ["Admin", "Chats"], + summary: "Delete an individual chat message", + operationId: "AdminAPI.ChatController.delete", + parameters: [id_param(), message_id_param()] ++ admin_api_params(), + security: [%{"oAuth" => ["write:chats"]}], + responses: %{ + 200 => empty_object_response(), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def id_param do + Operation.parameter(:id, :path, FlakeID, "Chat ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end + + def message_id_param do + Operation.parameter(:message_id, :path, FlakeID, "Chat message ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c6433cc53..e438768ed 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -214,6 +214,8 @@ defmodule Pleroma.Web.Router do get("/media_proxy_caches", MediaProxyCacheController, :index) post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) + + delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do -- cgit v1.2.3 From fb0de073439b5e3be823e736b44608e80f1027f1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 31 Aug 2020 20:23:33 -0500 Subject: AdminAPI: list chats for a user --- .../admin_api/controllers/admin_api_controller.ex | 27 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 3 +++ 2 files changed, 30 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index f5e4d49f9..9b66c2f10 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller + import Ecto.Query import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Pleroma.Config @@ -21,6 +22,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.Endpoint + alias Pleroma.Web.PleromaAPI alias Pleroma.Web.Router require Logger @@ -68,6 +70,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do when action in [:list_user_statuses, :list_instance_statuses] ) + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} + when action in [:list_user_chats] + ) + plug( OAuthScopesPlug, %{scopes: ["read"], admin: true} @@ -256,6 +264,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end + def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do + with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do + chats = + from(c in Pleroma.Chat, + where: c.user_id == ^user_id, + order_by: [desc: c.updated_at], + inner_join: u in User, + on: u.ap_id == c.recipient + ) + |> Pleroma.Repo.all() + + conn + |> put_view(PleromaAPI.ChatView) + |> render("index.json", chats: chats) + else + _ -> {:error, :not_found} + end + end + def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do user = User.get_cached_by_nickname(nickname) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e438768ed..ad3282df4 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -178,6 +178,7 @@ defmodule Pleroma.Web.Router do get("/users", AdminAPIController, :list_users) get("/users/:nickname", AdminAPIController, :user_show) get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) + get("/users/:nickname/chats", AdminAPIController, :list_user_chats) get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses) @@ -215,6 +216,8 @@ defmodule Pleroma.Web.Router do post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) + # get("/chats/:id", ChatController, :show) + # get("/chats/:id/messages", ChatController, :messages) delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end -- cgit v1.2.3 From c41430b23eaf3fd15b227e66215aa2a4ff31dfdb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 1 Sep 2020 19:05:24 -0500 Subject: Refactor with Chat.for_user_query/1 --- lib/pleroma/chat.ex | 12 ++++++++++++ .../web/admin_api/controllers/admin_api_controller.ex | 8 +------- lib/pleroma/web/pleroma_api/controllers/chat_controller.ex | 9 ++------- 3 files changed, 15 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 24a86371e..b38c5c3dd 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -6,7 +6,9 @@ defmodule Pleroma.Chat do use Ecto.Schema import Ecto.Changeset + import Ecto.Query + alias Pleroma.Chat alias Pleroma.Repo alias Pleroma.User @@ -69,4 +71,14 @@ defmodule Pleroma.Chat do conflict_target: [:user_id, :recipient] ) end + + @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() + def for_user_query(user_id) do + from(c in Chat, + where: c.user_id == ^user_id, + order_by: [desc: c.updated_at], + inner_join: u in User, + on: u.ap_id == c.recipient + ) + end end diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 9b66c2f10..fccdbabb4 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller - import Ecto.Query import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Pleroma.Config @@ -267,12 +266,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do chats = - from(c in Pleroma.Chat, - where: c.user_id == ^user_id, - order_by: [desc: c.updated_at], - inner_join: u in User, - on: u.ap_id == c.recipient - ) + Pleroma.Chat.for_user_query(user_id) |> Pleroma.Repo.all() conn diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index 1f2e953f7..27c9a2e0f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -146,13 +146,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do blocked_ap_ids = User.blocked_users_ap_ids(user) chats = - from(c in Chat, - where: c.user_id == ^user_id, - where: c.recipient not in ^blocked_ap_ids, - order_by: [desc: c.updated_at], - inner_join: u in User, - on: u.ap_id == c.recipient - ) + Chat.for_user_query(user_id) + |> where([c], c.recipient not in ^blocked_ap_ids) |> Repo.all() conn -- cgit v1.2.3 From f13b52a703d5c60cf12b2fff69f458e5c467c783 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 1 Sep 2020 19:39:34 -0500 Subject: AdminAPI: list messages in a chat --- .../web/admin_api/controllers/chat_controller.ex | 27 ++++++++++++++++++++++ .../api_spec/operations/admin/chat_operation.ex | 26 ++++++++++++++++++++- lib/pleroma/web/router.ex | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex index bcce824d2..b423188d7 100644 --- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -6,14 +6,23 @@ defmodule Pleroma.Web.AdminAPI.ChatController do use Pleroma.Web, :controller alias Pleroma.Activity + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference alias Pleroma.ModerationLog + alias Pleroma.Pagination alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.CommonAPI + alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView require Logger plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} when action in [:messages] + ) + plug( OAuthScopesPlug, %{scopes: ["write:chats"], admin: true} when action in [:delete_message] @@ -34,4 +43,22 @@ defmodule Pleroma.Web.AdminAPI.ChatController do json(conn, %{}) end end + + def messages(conn, %{id: id} = params) do + with %Chat{} = chat <- Chat.get_by_id(id) do + cm_refs = + chat + |> MessageReference.for_chat_query() + |> Pagination.fetch_paginated(params) + + conn + |> put_view(MessageReferenceView) + |> render("index.json", chat_message_references: cm_refs) + else + _ -> + conn + |> put_status(:not_found) + |> json(%{error: "not found"}) + end + end end diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex index 7045fd7ce..a382bd35a 100644 --- a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do def delete_message_operation do %Operation{ - tags: ["Admin", "Chats"], + tags: ["admin", "chat"], summary: "Delete an individual chat message", operationId: "AdminAPI.ChatController.delete", parameters: [id_param(), message_id_param()] ++ admin_api_params(), @@ -28,6 +28,30 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do } end + def messages_operation do + %Operation{ + tags: ["admin", "chat"], + summary: "Get the most recent messages of the chat", + operationId: "AdminAPI.ChatController.messages", + parameters: + [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++ + pagination_params(), + responses: %{ + 200 => + Operation.response( + "The messages in the chat", + "application/json", + Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response() + ) + }, + security: [ + %{ + "oAuth" => ["read:chats"] + } + ] + } + end + def id_param do Operation.parameter(:id, :path, FlakeID, "Chat ID", example: "9umDrYheeY451cQnEe", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ad3282df4..02836114a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -217,7 +217,7 @@ defmodule Pleroma.Web.Router do post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) # get("/chats/:id", ChatController, :show) - # get("/chats/:id/messages", ChatController, :messages) + get("/chats/:id/messages", ChatController, :messages) delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end -- cgit v1.2.3 From 9dd0b23da424c380a37897d8bf69ab241efa6f91 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 1 Sep 2020 19:49:46 -0500 Subject: AdminAPI: show chat --- .../web/admin_api/controllers/chat_controller.ex | 11 +++++++- .../api_spec/operations/admin/chat_operation.ex | 32 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex index b423188d7..ac362c430 100644 --- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.AdminAPI.ChatController do alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.CommonAPI alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView + alias Pleroma.Web.PleromaAPI.ChatView require Logger @@ -20,7 +21,7 @@ defmodule Pleroma.Web.AdminAPI.ChatController do plug( OAuthScopesPlug, - %{scopes: ["read:chats"], admin: true} when action in [:messages] + %{scopes: ["read:chats"], admin: true} when action in [:show, :messages] ) plug( @@ -61,4 +62,12 @@ defmodule Pleroma.Web.AdminAPI.ChatController do |> json(%{error: "not found"}) end end + + def show(conn, %{id: id}) do + with %Chat{} = chat <- Chat.get_by_id(id) do + conn + |> put_view(ChatView) + |> render("show.json", chat: chat) + end + end end diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex index a382bd35a..3550d531e 100644 --- a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do alias OpenApiSpex.Operation alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.Chat alias Pleroma.Web.ApiSpec.Schemas.FlakeID import Pleroma.Web.ApiSpec.Helpers @@ -52,6 +53,37 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do } end + def show_operation do + %Operation{ + tags: ["chat"], + summary: "Create a chat", + operationId: "AdminAPI.ChatController.show", + parameters: [ + Operation.parameter( + :id, + :path, + :string, + "The id of the chat", + required: true, + example: "1234" + ) + ], + responses: %{ + 200 => + Operation.response( + "The existing chat", + "application/json", + Chat + ) + }, + security: [ + %{ + "oAuth" => ["read"] + } + ] + } + end + def id_param do Operation.parameter(:id, :path, FlakeID, "Chat ID", example: "9umDrYheeY451cQnEe", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 02836114a..e4440d442 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -216,7 +216,7 @@ defmodule Pleroma.Web.Router do post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) - # get("/chats/:id", ChatController, :show) + get("/chats/:id", ChatController, :show) get("/chats/:id/messages", ChatController, :messages) delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end -- cgit v1.2.3 From 02d70228b566d5de2cbdd6d1f9958caf2db173f1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 1 Sep 2020 20:40:36 -0500 Subject: AdminAPI: fix delete chat message --- .../web/admin_api/controllers/chat_controller.ex | 20 ++++++++--- .../api_spec/operations/admin/chat_operation.ex | 40 ++++++++++------------ 2 files changed, 34 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex index ac362c430..61d45b970 100644 --- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -33,15 +33,27 @@ defmodule Pleroma.Web.AdminAPI.ChatController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation - def delete_message(%{assigns: %{user: user}} = conn, %{message_id: id}) do - with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + def delete_message(%{assigns: %{user: user}} = conn, %{ + message_id: message_id, + id: chat_id + }) do + with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <- + MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), + %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id), + {:ok, _} <- CommonAPI.delete(activity_id, user) do ModerationLog.insert_log(%{ action: "chat_message_delete", actor: user, - subject_id: id + subject_id: message_id }) - json(conn, %{}) + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + else + _e -> + {:error, :could_not_delete} end end diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex index 3550d531e..d3e5dfc1c 100644 --- a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -4,9 +4,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do alias OpenApiSpex.Operation - alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.Chat - alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.ChatMessage import Pleroma.Web.ApiSpec.Helpers @@ -19,13 +18,24 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do %Operation{ tags: ["admin", "chat"], summary: "Delete an individual chat message", - operationId: "AdminAPI.ChatController.delete", - parameters: [id_param(), message_id_param()] ++ admin_api_params(), - security: [%{"oAuth" => ["write:chats"]}], + operationId: "AdminAPI.ChatController.delete_message", + parameters: [ + Operation.parameter(:id, :path, :string, "The ID of the Chat"), + Operation.parameter(:message_id, :path, :string, "The ID of the message") + ], responses: %{ - 200 => empty_object_response(), - 404 => Operation.response("Not Found", "application/json", ApiError) - } + 200 => + Operation.response( + "The deleted ChatMessage", + "application/json", + ChatMessage + ) + }, + security: [ + %{ + "oAuth" => ["write:chats"] + } + ] } end @@ -83,18 +93,4 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do ] } end - - def id_param do - Operation.parameter(:id, :path, FlakeID, "Chat ID", - example: "9umDrYheeY451cQnEe", - required: true - ) - end - - def message_id_param do - Operation.parameter(:message_id, :path, FlakeID, "Chat message ID", - example: "9umDrYheeY451cQnEe", - required: true - ) - end end -- cgit v1.2.3 From e229536e5cca65d811f85d25c86bf3c92b3d8c45 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 10 Sep 2020 01:44:32 -0500 Subject: Chat Moderation: use explicit `sender` and `recipient` fields --- .../admin_api/controllers/admin_api_controller.ex | 5 +--- .../web/admin_api/controllers/chat_controller.ex | 4 +-- lib/pleroma/web/admin_api/views/chat_view.ex | 30 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/web/admin_api/views/chat_view.ex (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index fccdbabb4..d5713c3dd 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -21,11 +21,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.Endpoint - alias Pleroma.Web.PleromaAPI alias Pleroma.Web.Router - require Logger - @users_page_size 50 plug( @@ -270,7 +267,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> Pleroma.Repo.all() conn - |> put_view(PleromaAPI.ChatView) + |> put_view(AdminAPI.ChatView) |> render("index.json", chats: chats) else _ -> {:error, :not_found} diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex index 61d45b970..967600d69 100644 --- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -11,9 +11,9 @@ defmodule Pleroma.Web.AdminAPI.ChatController do alias Pleroma.ModerationLog alias Pleroma.Pagination alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.AdminAPI alias Pleroma.Web.CommonAPI alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView - alias Pleroma.Web.PleromaAPI.ChatView require Logger @@ -78,7 +78,7 @@ defmodule Pleroma.Web.AdminAPI.ChatController do def show(conn, %{id: id}) do with %Chat{} = chat <- Chat.get_by_id(id) do conn - |> put_view(ChatView) + |> put_view(AdminAPI.ChatView) |> render("show.json", chat: chat) end end diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex new file mode 100644 index 000000000..847df1423 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/chat_view.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatView do + use Pleroma.Web, :view + + alias Pleroma.Chat + alias Pleroma.User + alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.PleromaAPI + + def render("index.json", %{chats: chats} = opts) do + render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats)) + end + + def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do + user = User.get_by_id(user_id) + sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true) + + serialized_chat = PleromaAPI.ChatView.render("show.json", opts) + + serialized_chat + |> Map.put(:sender, sender) + |> Map.put(:receiver, serialized_chat[:account]) + |> Map.delete(:account) + end + + def render(view, opts), do: PleromaAPI.ChatView.render(view, opts) +end -- cgit v1.2.3 From c0b36621f1149734e97f268e267202cc53700abb Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 11 Sep 2020 16:59:45 -0500 Subject: Ensure we only apply NSFW Simple policy on parsable objects --- lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index bb193475a..161177727 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -66,7 +66,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do "type" => "Create", "object" => child_object } = object - ) do + ) + when is_map(child_object) do media_nsfw = Config.get([:mrf_simple, :media_nsfw]) |> MRF.subdomains_regex() -- cgit v1.2.3 From 32831f371ff426ac0c6f5d6c1381313f5f92af42 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 12 Sep 2020 10:33:42 +0300 Subject: [#2497] Media preview proxy: redirecting to media proxy url in case of preview error or unsupported content type. --- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index ff7fd2409..08d62a51a 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -91,8 +91,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do handle_video_preview(conn, media_proxy_url) end - defp handle_preview(content_type, conn, _media_proxy_url) do - send_resp(conn, :unprocessable_entity, "Unsupported content type: #{content_type}.") + defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do + fallback_on_preview_error(conn, media_proxy_url) end defp handle_png_preview(conn, media_proxy_url) do @@ -114,7 +114,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> send_resp(200, thumbnail_binary) else _ -> - send_resp(conn, :failed_dependency, "Can't handle preview.") + fallback_on_preview_error(conn, media_proxy_url) end end @@ -132,7 +132,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> send_resp(200, thumbnail_binary) else _ -> - send_resp(conn, :failed_dependency, "Can't handle preview.") + fallback_on_preview_error(conn, media_proxy_url) end end @@ -144,10 +144,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do |> send_resp(200, thumbnail_binary) else _ -> - send_resp(conn, :failed_dependency, "Can't handle preview.") + fallback_on_preview_error(conn, media_proxy_url) end end + defp fallback_on_preview_error(conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) + end + defp put_preview_response_headers( conn, [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"] -- cgit v1.2.3 From cd234a5321b9d33146b90be95d84fa67aa4f7707 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 12 Sep 2020 11:20:41 +0300 Subject: [#2497] Media preview proxy: preview bypass for small images (basing on Content-Length and Content-Type). --- .../web/media_proxy/media_proxy_controller.ex | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 08d62a51a..78df7763e 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn + @min_content_length_for_preview 100 * 1024 + def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), @@ -54,8 +56,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do with {:ok, %{status: status} = head_response} when status in 200..299 <- Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do content_type = Tesla.get_header(head_response, "content-type") - handle_preview(content_type, conn, media_proxy_url) + content_length = Tesla.get_header(head_response, "content-length") + content_length = content_length && String.to_integer(content_length) + + handle_preview(content_type, content_length, conn, media_proxy_url) else + # If HEAD failed, redirecting to media proxy URI doesn't make much sense; returning an error {_, %{status: status}} -> send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") @@ -69,29 +75,36 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp handle_preview( "image/" <> _ = _content_type, + _content_length, %{params: %{"output_format" => "jpeg"}} = conn, media_proxy_url ) do handle_jpeg_preview(conn, media_proxy_url) end - defp handle_preview("image/gif" = _content_type, conn, media_proxy_url) do + defp handle_preview("image/gif" = _content_type, _content_length, conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) + end + + defp handle_preview("image/" <> _ = _content_type, content_length, conn, media_proxy_url) + when is_integer(content_length) and content_length > 0 and + content_length < @min_content_length_for_preview do redirect(conn, external: media_proxy_url) end - defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do + defp handle_preview("image/png" <> _ = _content_type, _content_length, conn, media_proxy_url) do handle_png_preview(conn, media_proxy_url) end - defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do + defp handle_preview("image/" <> _ = _content_type, _content_length, conn, media_proxy_url) do handle_jpeg_preview(conn, media_proxy_url) end - defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do + defp handle_preview("video/" <> _ = _content_type, _content_length, conn, media_proxy_url) do handle_video_preview(conn, media_proxy_url) end - defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do + defp handle_preview(_unsupported_content_type, _content_length, conn, media_proxy_url) do fallback_on_preview_error(conn, media_proxy_url) end -- cgit v1.2.3 From 489a107cf449a10c7f6ac9a4b8d4a7f9f7314c5c Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 13 Sep 2020 11:54:15 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/pleroma/emoji/pack.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 930bbb422..0b3f8f00b 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -84,7 +84,7 @@ defmodule Pleroma.Emoji.Pack do end) end - @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t()) :: + @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) :: {:ok, t()} | {:error, File.posix() | atom()} def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do -- cgit v1.2.3 From 3e53ab4e98e6294f593f2185998f555ccd6fee73 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 14 Sep 2020 14:08:12 +0300 Subject: added notification constraints --- lib/pleroma/migration_helper/notification_backfill.ex | 15 +++++++-------- lib/pleroma/repo.ex | 9 +++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex index d260e62ca..24f4733fe 100644 --- a/lib/pleroma/migration_helper/notification_backfill.ex +++ b/lib/pleroma/migration_helper/notification_backfill.ex @@ -19,13 +19,13 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do query |> Repo.chunk_stream(100) |> Enum.each(fn notification -> - type = - notification.activity - |> type_from_activity() + if notification.activity do + type = type_from_activity(notification.activity) - notification - |> Ecto.Changeset.change(%{type: type}) - |> Repo.update() + notification + |> Ecto.Changeset.change(%{type: type}) + |> Repo.update() + end end) end @@ -72,8 +72,7 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do "pleroma:emoji_reaction" "Create" -> - activity - |> type_from_activity_object() + type_from_activity_object(activity) t -> raise "No notification type for activity type #{t}" diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index f317e4d58..a75610879 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -49,7 +49,7 @@ defmodule Pleroma.Repo do end end - def chunk_stream(query, chunk_size) do + def chunk_stream(query, chunk_size, returns_as \\ :one) do # We don't actually need start and end funcitons of resource streaming, # but it seems to be the only way to not fetch records one-by-one and # have individual records be the elements of the stream, instead of @@ -69,7 +69,12 @@ defmodule Pleroma.Repo do records -> last_id = List.last(records).id - {records, last_id} + + if returns_as == :one do + {records, last_id} + else + {[records], last_id} + end end end, fn _ -> :ok end -- cgit v1.2.3 From f66a15c4a51e1c8f614b4c1609b2385a29762931 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 14 Sep 2020 14:44:25 +0300 Subject: RichMedia parser: do not set a cache TTL for unchanging errors --- lib/pleroma/web/rich_media/parser.ex | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 5727fda18..ab8f35922 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -36,6 +36,14 @@ defmodule Pleroma.Web.RichMedia.Parser do {:ok, _data} = res -> res + {:error, :body_too_large} = e -> + e + + {:error, {:content_type, _}} -> + e + + # The TTL is not set for the errors above, since they are unlikely to change + # with time {:error, _} = e -> ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) Cachex.expire(:rich_media_cache, url, ttl) -- cgit v1.2.3 From f70335002df9b2b3f47f0ccaed6aaeebfb14435f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 14 Sep 2020 14:45:58 +0300 Subject: RichMedia: Do a HEAD request to check content type/length This shouldn't be too expensive, since the connections are pooled, but it should save us some bandwidth since we won't fetch non-html files and files that are too large for us to process (especially since you can't cancel a request without closing the connection with HTTP1). --- lib/pleroma/web/rich_media/helpers.ex | 46 ++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index bd7f03cbe..d7a19df4a 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -87,6 +87,50 @@ defmodule Pleroma.Web.RichMedia.Helpers do def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] - Pleroma.HTTP.get(url, headers, @options) + head_check = + case Pleroma.HTTP.head(url, headers, @options) do + # If the HEAD request didn't reach the server for whatever reason, + # we assume the GET that comes right after won't either + {:error, _} = e -> + e + + {:ok, %Tesla.Env{status: 200, headers: headers}} -> + with :ok <- check_content_type(headers), + :ok <- check_content_length(headers), + do: :ok + + _ -> + :ok + end + + with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options) + end + + defp check_content_type(headers) do + case List.keyfind(headers, "content-type", 0) do + {_, content_type} -> + case Plug.Conn.Utils.media_type(content_type) do + {:ok, "text", "html", _} -> :ok + _ -> {:error, {:content_type, content_type}} + end + + _ -> + :ok + end + end + + @max_body @options[:max_body] + defp check_content_length(headers) do + case List.keyfind(headers, "content-length", 0) do + {_, maybe_content_length} -> + case Integer.parse(maybe_content_length) do + {content_length, ""} when content_length <= @max_body -> :ok + {_, ""} -> {:error, :body_too_large} + _ -> :ok + end + + _ -> + :ok + end end end -- cgit v1.2.3 From bb407edce4b512aae74c12ea0c1abcc92bc18ddb Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 14 Sep 2020 15:46:00 +0300 Subject: RichMedia: fix a compilation error due to nonexistent variable No idea why this passed Gitlab CI --- lib/pleroma/web/rich_media/parser.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index ab8f35922..33f6f1fa1 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -39,7 +39,7 @@ defmodule Pleroma.Web.RichMedia.Parser do {:error, :body_too_large} = e -> e - {:error, {:content_type, _}} -> + {:error, {:content_type, _}} = e -> e # The TTL is not set for the errors above, since they are unlikely to change -- cgit v1.2.3 From 38b2db297b3207607072347b408dc7eacbac600e Mon Sep 17 00:00:00 2001 From: stwf Date: Mon, 14 Sep 2020 13:18:11 -0400 Subject: search indexing metadata respects discoverable flag --- lib/pleroma/web/metadata/restrict_indexing.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/restrict_indexing.ex index f15607896..a1dcb6e15 100644 --- a/lib/pleroma/web/metadata/restrict_indexing.ex +++ b/lib/pleroma/web/metadata/restrict_indexing.ex @@ -10,7 +10,9 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do """ @impl true - def build_tags(%{user: %{local: false}}) do + def build_tags(%{user: %{local: true, discoverable: true}}), do: [] + + def build_tags(_) do [ {:meta, [ @@ -19,7 +21,4 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do ], []} ] end - - @impl true - def build_tags(%{user: %{local: true}}), do: [] end -- cgit v1.2.3 From 709723182d69e1bb41a23c8abeb5d7c2c67b8c49 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 14 Sep 2020 17:06:42 -0500 Subject: Ensure SimplePolicy's tags in string representation don't trip up the object validator --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index af4384213..8fe430644 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -309,7 +309,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do emoji = tags - |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end) + |> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end) |> Enum.reduce(%{}, fn data, mapping -> name = String.trim(data["name"], ":") -- cgit v1.2.3 From 3ab59a6f3c7b7bae2e69d1a8d1bf484d039a5420 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Tue, 15 Sep 2020 13:00:07 +0300 Subject: Mastodon API: fix the public timeline returning an error when the `reply_visibility` parameter is set to `self` for an unauthenticated user --- lib/pleroma/web/activity_pub/activity_pub.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 66a9f78a3..5aac3f53b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -767,7 +767,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp restrict_replies(query, %{ - reply_filtering_user: user, + reply_filtering_user: %User{} = user, reply_visibility: "self" }) do from( @@ -783,7 +783,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp restrict_replies(query, %{ - reply_filtering_user: user, + reply_filtering_user: %User{} = user, reply_visibility: "following" }) do from( -- cgit v1.2.3 From 599f8bb152ca0669d17baa5f313f00f0791209b6 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 16 Sep 2020 09:47:18 +0300 Subject: RepoStreamer.chunk_stream -> Repo.chunk_stream --- lib/mix/tasks/pleroma/database.ex | 4 ++-- lib/mix/tasks/pleroma/user.ex | 4 ++-- lib/pleroma/repo.ex | 14 ++++++++++++++ lib/pleroma/repo_streamer.ex | 34 ---------------------------------- lib/pleroma/user.ex | 3 +-- 5 files changed, 19 insertions(+), 40 deletions(-) delete mode 100644 lib/pleroma/repo_streamer.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 7f1108dcf..a01c36ece 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -99,7 +99,7 @@ defmodule Mix.Tasks.Pleroma.Database do where: fragment("(?)->>'likes' is not null", object.data), select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)} ) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn objects -> ids = objects @@ -145,7 +145,7 @@ defmodule Mix.Tasks.Pleroma.Database do |> where(local: true) |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) |> where([_a, o], fragment("?->>'type' = 'Note'", o.data)) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> expires_at = diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 01824aa18..b20c49d89 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -179,7 +179,7 @@ defmodule Mix.Tasks.Pleroma.User do start_pleroma() Pleroma.User.Query.build(%{nickname: "@#{instance}"}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> @@ -370,7 +370,7 @@ defmodule Mix.Tasks.Pleroma.User do start_pleroma() Pleroma.User.Query.build(%{local: true}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index a75610879..4524bd5e2 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -49,6 +49,20 @@ defmodule Pleroma.Repo do end end + @doc """ + Returns a lazy enumerable that emits all entries from the data store matching the given query. + + `returns_as` use to group records. use the `batches` option to fetch records in bulk. + + ## Examples + + # fetch records one-by-one + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500) + + # fetch records in bulk + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches) + """ + @spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t() def chunk_stream(query, chunk_size, returns_as \\ :one) do # We don't actually need start and end funcitons of resource streaming, # but it seems to be the only way to not fetch records one-by-one and diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex deleted file mode 100644 index cb4d7bb7a..000000000 --- a/lib/pleroma/repo_streamer.ex +++ /dev/null @@ -1,34 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.RepoStreamer do - alias Pleroma.Repo - import Ecto.Query - - def chunk_stream(query, chunk_size) do - Stream.unfold(0, fn - :halt -> - {[], :halt} - - last_id -> - query - |> order_by(asc: :id) - |> where([r], r.id > ^last_id) - |> limit(^chunk_size) - |> Repo.all() - |> case do - [] -> - {[], :halt} - - records -> - last_id = List.last(records).id - {records, last_id} - end - end) - |> Stream.take_while(fn - [] -> false - _ -> true - end) - end -end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e73d19964..57497eb83 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -25,7 +25,6 @@ defmodule Pleroma.User do alias Pleroma.Object alias Pleroma.Registration alias Pleroma.Repo - alias Pleroma.RepoStreamer alias Pleroma.User alias Pleroma.UserRelationship alias Pleroma.Web @@ -1775,7 +1774,7 @@ defmodule Pleroma.User do def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id |> Activity.Queries.by_actor() - |> RepoStreamer.chunk_stream(50) + |> Repo.chunk_stream(50, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> delete_activity(activity, user) end) end) -- cgit v1.2.3 From adb1b0282dfbced2b2986c90cff765be37dd5151 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 16 Sep 2020 17:23:05 +0300 Subject: ConnectionPool Worker: use monitor flush instead of checking ref `:flush` removes the DOWN message if one had arrived, so this check should no longer be necessary. --- lib/pleroma/gun/connection_pool/worker.ex | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index 49d41e4c7..bf57e9e5f 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -93,25 +93,18 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do end) {ref, state} = pop_in(state.client_monitors[client_pid]) - # DOWN message can receive right after `remove_client` call and cause worker to terminate - state = - if is_nil(ref) do - state - else - Process.demonitor(ref) - timer = - if used_by == [] do - max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) - Process.send_after(self(), :idle_close, max_idle) - else - nil - end + Process.demonitor(ref, [:flush]) - %{state | timer: timer} + timer = + if used_by == [] do + max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) + Process.send_after(self(), :idle_close, max_idle) + else + nil end - {:reply, :ok, state, :hibernate} + {:reply, :ok, %{state | timer: timer}, :hibernate} end @impl true -- cgit v1.2.3 From 7a88b726bf81e1610ade2b07ffd6af672b701600 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 16 Sep 2020 17:29:16 +0200 Subject: User: Remote users don't need to be confirmed or approved --- lib/pleroma/user.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 57497eb83..1ffe60dfc 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -275,9 +275,9 @@ defmodule Pleroma.User do @spec account_status(User.t()) :: account_status() def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def account_status(%User{approval_pending: true}), do: :approval_pending + def account_status(%User{local: true, approval_pending: true}), do: :approval_pending - def account_status(%User{confirmation_pending: true}) do + def account_status(%User{local: true, confirmation_pending: true}) do if Config.get([:instance, :account_activation_required]) do :confirmation_pending else -- cgit v1.2.3 From a781f41f969bd1a929005b2b5006a40d42855ae8 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 16 Sep 2020 22:30:42 +0300 Subject: [#2497] Media preview proxy: misc. improvements (`static` param support, dynamic fifo pipe path), refactoring. --- lib/pleroma/helpers/media_helper.ex | 4 +--- lib/pleroma/helpers/uri_helper.ex | 13 ++++++++----- lib/pleroma/web/mastodon_api/views/account_view.ex | 4 ++-- lib/pleroma/web/media_proxy/media_proxy.ex | 2 +- lib/pleroma/web/media_proxy/media_proxy_controller.ex | 19 ++++++++++++++++--- lib/pleroma/web/oauth/oauth_controller.ex | 4 ++-- 6 files changed, 30 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index d834b4a07..9b7348ee2 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -9,8 +9,6 @@ defmodule Pleroma.Helpers.MediaHelper do alias Pleroma.HTTP - @tmp_base "/tmp/pleroma-media_preview-pipe" - def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), @@ -103,7 +101,7 @@ defmodule Pleroma.Helpers.MediaHelper do end defp mkfifo do - path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}" + path = Path.join(System.tmp_dir!(), "pleroma-media-preview-pipe-#{Ecto.UUID.generate()}") case System.cmd("mkfifo", [path]) do {_, 0} -> diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex index 9c9e53447..f1301f055 100644 --- a/lib/pleroma/helpers/uri_helper.ex +++ b/lib/pleroma/helpers/uri_helper.ex @@ -3,14 +3,17 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Helpers.UriHelper do - def append_uri_params(uri, appended_params) do + def modify_uri_params(uri, overridden_params, deleted_params \\ []) do uri = URI.parse(uri) - appended_params = for {k, v} <- appended_params, into: %{}, do: {to_string(k), v} - existing_params = URI.query_decoder(uri.query || "") |> Enum.into(%{}) - updated_params_keys = Enum.uniq(Map.keys(existing_params) ++ Map.keys(appended_params)) + + existing_params = URI.query_decoder(uri.query || "") |> Map.new() + overridden_params = Map.new(overridden_params, fn {k, v} -> {to_string(k), v} end) + deleted_params = Enum.map(deleted_params, &to_string/1) updated_params = - for k <- updated_params_keys, do: {k, appended_params[k] || existing_params[k]} + existing_params + |> Map.merge(overridden_params) + |> Map.drop(deleted_params) uri |> Map.put(:query, URI.encode_query(updated_params)) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index a811f81c2..121ba1693 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -182,9 +182,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do display_name = user.name || user.nickname avatar = User.avatar_url(user) |> MediaProxy.url() - avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(output_format: "jpeg") + avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true) header = User.banner_url(user) |> MediaProxy.url() - header_static = User.banner_url(user) |> MediaProxy.preview_url(output_format: "jpeg") + header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index ba553998b..8656b8cad 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -157,7 +157,7 @@ defmodule Pleroma.Web.MediaProxy do def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do uri = proxy_url("proxy/preview", sig_base64, url_base64, filename) - UriHelper.append_uri_params(uri, preview_params) + UriHelper.modify_uri_params(uri, preview_params) end def verify_request_path_and_url( diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 78df7763e..fe279e964 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Config alias Pleroma.Helpers.MediaHelper + alias Pleroma.Helpers.UriHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy alias Plug.Conn @@ -74,14 +75,26 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp handle_preview( - "image/" <> _ = _content_type, + "image/gif" = _content_type, _content_length, - %{params: %{"output_format" => "jpeg"}} = conn, + %{params: %{"static" => static}} = conn, media_proxy_url - ) do + ) + when static in ["true", true] do handle_jpeg_preview(conn, media_proxy_url) end + defp handle_preview( + _content_type, + _content_length, + %{params: %{"static" => static}} = conn, + _media_proxy_url + ) + when static in ["true", true] do + uri_without_static_param = UriHelper.modify_uri_params(current_url(conn), %{}, ["static"]) + redirect(conn, external: uri_without_static_param) + end + defp handle_preview("image/gif" = _content_type, _content_length, conn, media_proxy_url) do redirect(conn, external: media_proxy_url) end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 26e68be42..a4152e840 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -119,7 +119,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{access_token: token.token} url_params = Maps.put_if_present(url_params, :state, params["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn @@ -161,7 +161,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{code: auth.token} url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn -- cgit v1.2.3 From 5a8ea0a5b07c22d567a60af36345483fe880b638 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 17 Sep 2020 09:13:43 +0300 Subject: small refactoring --- lib/pleroma/user/import.ex | 10 ++-------- lib/pleroma/workers/background_worker.ex | 29 +++-------------------------- 2 files changed, 5 insertions(+), 34 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index de27bdc4c..e458021c8 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -65,20 +65,14 @@ defmodule Pleroma.User.Import do def blocks_import(%User{} = blocker, [_ | _] = identifiers) do BackgroundWorker.enqueue( "blocks_import", - %{ - "blocker_id" => blocker.id, - "blocked_identifiers" => identifiers - } + %{"user_id" => blocker.id, "identifiers" => identifiers} ) end def follow_import(%User{} = follower, [_ | _] = identifiers) do BackgroundWorker.enqueue( "follow_import", - %{ - "follower_id" => follower.id, - "followed_identifiers" => identifiers - } + %{"user_id" => follower.id, "identifiers" => identifiers} ) end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index f9c767ee0..55b5a13d9 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -26,33 +26,10 @@ defmodule Pleroma.Workers.BackgroundWorker do User.perform(:force_password_reset, user) end - def perform(%Job{ - args: %{ - "op" => "blocks_import", - "blocker_id" => blocker_id, - "blocked_identifiers" => blocked_identifiers - } - }) do - blocker = User.get_cached_by_id(blocker_id) - {:ok, User.Import.perform(:blocks_import, blocker, blocked_identifiers)} - end - - def perform(%Job{ - args: %{ - "op" => "follow_import", - "follower_id" => follower_id, - "followed_identifiers" => followed_identifiers - } - }) do - follower = User.get_cached_by_id(follower_id) - {:ok, User.Import.perform(:follow_import, follower, followed_identifiers)} - end - - def perform(%Job{ - args: %{"op" => "mutes_import", "user_id" => user_id, "identifiers" => identifiers} - }) do + def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) + when op in ["blocks_import", "follow_import", "mutes_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(:mutes_import, user, identifiers)} + {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} end def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do -- cgit v1.2.3 From 582ad5d4e1587b3dba9d879bd68dd9a315c8446e Mon Sep 17 00:00:00 2001 From: eugenijm Date: Sun, 30 Aug 2020 15:15:14 +0300 Subject: AdminAPI: Allow to modify Terms of Service and Instance Panel via Admin API --- .../controllers/instance_document_controller.ex | 37 +++++++ .../admin/instance_document_operation.ex | 108 +++++++++++++++++++++ lib/pleroma/web/instance_document.ex | 62 ++++++++++++ lib/pleroma/web/router.ex | 4 + 4 files changed, 211 insertions(+) create mode 100644 lib/pleroma/web/admin_api/controllers/instance_document_controller.ex create mode 100644 lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex create mode 100644 lib/pleroma/web/instance_document.ex (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex new file mode 100644 index 000000000..2144e44ac --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do + use Pleroma.Web, :controller + + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.InstanceDocument + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation + + plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show) + plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete]) + + def show(conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.get(document_name) do + json(conn, %{"url" => url}) + end + end + + def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.put(document_name, file.path) do + json(conn, %{"url" => url}) + end + end + + def delete(conn, %{name: document_name}) do + with :ok <- InstanceDocument.delete(document_name) do + json(conn, %{}) + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex new file mode 100644 index 000000000..e0eb993fb --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex @@ -0,0 +1,108 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def show_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.show", + security: [%{"oAuth" => ["read"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Update the instance document", + operationId: "AdminAPI.InstanceDocumentController.update", + security: [%{"oAuth" => ["write"]}], + requestBody: Helpers.request_body("Parameters", update_request()), + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp update_request do + %Schema{ + title: "UpdateRequest", + description: "POST body for uploading the file", + type: :object, + required: [:file], + properties: %{ + file: %Schema{ + type: :string, + format: :binary, + description: "The file to be uploaded, using multipart form data." + } + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.delete", + security: [%{"oAuth" => ["write"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp instance_document do + %Schema{ + title: "InstanceDocument", + type: :object, + properties: %{ + url: %Schema{type: :string} + }, + example: %{ + "url" => "https://example.com/static/terms-of-service.html" + } + } + end +end diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex new file mode 100644 index 000000000..969a44e41 --- /dev/null +++ b/lib/pleroma/web/instance_document.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.InstanceDocument do + alias Pleroma.Config + alias Pleroma.Web.Endpoint + + @instance_documents %{ + "terms-of-service" => "/static/terms-of-service.html", + "instance-panel" => "/instance/panel.html" + } + + @spec get(String.t()) :: {:ok, String.t()} | {:error, atom()} + def get(document_name) do + case Map.fetch(@instance_documents, document_name) do + {:ok, path} -> {:ok, Path.join(Endpoint.url(), path)} + _ -> {:error, :not_found} + end + end + + @spec put(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()} + def put(document_name, origin_path) do + with {_, {:ok, destination_path}} <- + {:instance_document, Map.fetch(@instance_documents, document_name)}, + :ok <- put_file(origin_path, destination_path) do + {:ok, Path.join(Endpoint.url(), destination_path)} + else + {:instance_document, :error} -> {:error, :not_found} + error -> error + end + end + + @spec delete(String.t()) :: :ok | {:error, atom()} + def delete(document_name) do + with {_, {:ok, path}} <- {:instance_document, Map.fetch(@instance_documents, document_name)}, + instance_static_dir_path <- instance_static_dir(path), + :ok <- File.rm(instance_static_dir_path) do + :ok + else + {:instance_document, :error} -> {:error, :not_found} + {:error, :enoent} -> {:error, :not_found} + error -> error + end + end + + defp put_file(origin_path, destination_path) do + with destination <- instance_static_dir(destination_path), + {_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))}, + {_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do + :ok + else + {error, _} -> {:error, error} + end + end + + defp instance_static_dir(filename) do + [:instance, :static_dir] + |> Config.get!() + |> Path.join(filename) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e4440d442..a4a58c2c4 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -182,6 +182,10 @@ defmodule Pleroma.Web.Router do get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses) + get("/instance_document/:name", InstanceDocumentController, :show) + patch("/instance_document/:name", InstanceDocumentController, :update) + delete("/instance_document/:name", InstanceDocumentController, :delete) + patch("/users/confirm_email", AdminAPIController, :confirm_email) patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) -- cgit v1.2.3 From c711a2b15761db9d2d30035e9fee0783f0bf77b0 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Thu, 17 Sep 2020 16:54:38 +0300 Subject: Return the file content for `GET /api/pleroma/admin/instance_document/:document_name` --- .../web/admin_api/controllers/instance_document_controller.ex | 8 ++++++-- .../web/api_spec/operations/admin/instance_document_operation.ex | 9 ++++++++- lib/pleroma/web/instance_document.ex | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex index 2144e44ac..504d9b517 100644 --- a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do use Pleroma.Web, :controller + alias Pleroma.Plugs.InstanceStatic alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.InstanceDocument @@ -18,8 +19,11 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete]) def show(conn, %{name: document_name}) do - with {:ok, url} <- InstanceDocument.get(document_name) do - json(conn, %{"url" => url}) + with {:ok, url} <- InstanceDocument.get(document_name), + {:ok, content} <- File.read(InstanceStatic.file_path(url)) do + conn + |> put_resp_content_type("text/html") + |> send_resp(200, content) end end diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex index e0eb993fb..a120ff4e8 100644 --- a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do | Helpers.admin_api_params() ], responses: %{ - 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 200 => document_content(), 400 => Operation.response("Bad Request", "application/json", ApiError), 403 => Operation.response("Forbidden", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError) @@ -105,4 +105,11 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do } } end + + defp document_content do + Operation.response("InstanceDocumentContent", "text/html", %Schema{ + type: :string, + example: "

Instance panel

" + }) + end end diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex index 969a44e41..df5caebf0 100644 --- a/lib/pleroma/web/instance_document.ex +++ b/lib/pleroma/web/instance_document.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.InstanceDocument do @spec get(String.t()) :: {:ok, String.t()} | {:error, atom()} def get(document_name) do case Map.fetch(@instance_documents, document_name) do - {:ok, path} -> {:ok, Path.join(Endpoint.url(), path)} + {:ok, path} -> {:ok, path} _ -> {:error, :not_found} end end -- cgit v1.2.3 From db80b9d630f9fc72ebc269cb24142501116c269a Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 17 Sep 2020 16:13:21 +0300 Subject: RichMedia: Fix log spam on failures and resetting TTL on cached errors --- lib/pleroma/web/rich_media/parser.ex | 73 ++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 33f6f1fa1..c70d2fdba 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -20,36 +20,61 @@ defmodule Pleroma.Web.RichMedia.Parser do with {:ok, data} <- get_cached_or_parse(url), {:ok, _} <- set_ttl_based_on_image(data, url) do {:ok, data} - else - {:error, {:invalid_metadata, data}} = e -> - Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) - e - - error -> - Logger.error(fn -> "Rich media error for #{url}: #{inspect(error)}" end) - error end end defp get_cached_or_parse(url) do - case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do - {:ok, _data} = res -> - res - - {:error, :body_too_large} = e -> - e - - {:error, {:content_type, _}} = e -> - e - - # The TTL is not set for the errors above, since they are unlikely to change - # with time - {:error, _} = e -> - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - Cachex.expire(:rich_media_cache, url, ttl) - e + case Cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} + + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res + + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end + + {:error, e} -> + {:error, {:cachex_error, e}} end end + + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok + + # The TTL is not set for the errors above, since they are unlikely to change + # with time + + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + Cachex.expire(:rich_media_cache, url, ttl) + :ok + end + + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end + + defp log_error(url, reason) do + Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) + end end @doc """ -- cgit v1.2.3 From 7cdbd91d83c02a79c22783ca489ef82e82b31a51 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 17 Sep 2020 17:13:40 +0300 Subject: [#2497] Configurability of :min_content_length (preview proxy). Refactoring, documentation, tests. --- lib/pleroma/helpers/media_helper.ex | 1 + .../web/media_proxy/media_proxy_controller.ex | 90 +++++++++++----------- 2 files changed, 47 insertions(+), 44 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 9b7348ee2..b6f35a24b 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -58,6 +58,7 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_image_resize_args(_), do: {:error, :missing_options} + # Note: video thumbnail is intentionally not resized (always has original dimensions) def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), {:ok, env} <- HTTP.get(url, [], pool: :media), diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index fe279e964..90651ed9b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn - @min_content_length_for_preview 100 * 1024 - def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), @@ -37,7 +35,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, - {:ok, url} <- MediaProxy.decode_url(sig64, url64) do + {:ok, url} <- MediaProxy.decode_url(sig64, url64), + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do handle_preview(conn, url) else {:enabled, false} -> @@ -59,8 +58,25 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do content_type = Tesla.get_header(head_response, "content-type") content_length = Tesla.get_header(head_response, "content-length") content_length = content_length && String.to_integer(content_length) + static = conn.params["static"] in ["true", true] + + cond do + static and content_type == "image/gif" -> + handle_jpeg_preview(conn, media_proxy_url) - handle_preview(content_type, content_length, conn, media_proxy_url) + static -> + drop_static_param_and_redirect(conn) + + content_type == "image/gif" -> + redirect(conn, external: media_proxy_url) + + min_content_length_for_preview() > 0 and content_length > 0 and + content_length < min_content_length_for_preview() -> + redirect(conn, external: media_proxy_url) + + true -> + handle_preview(content_type, conn, media_proxy_url) + end else # If HEAD failed, redirecting to media proxy URI doesn't make much sense; returning an error {_, %{status: status}} -> @@ -74,58 +90,27 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end - defp handle_preview( - "image/gif" = _content_type, - _content_length, - %{params: %{"static" => static}} = conn, - media_proxy_url - ) - when static in ["true", true] do - handle_jpeg_preview(conn, media_proxy_url) - end - - defp handle_preview( - _content_type, - _content_length, - %{params: %{"static" => static}} = conn, - _media_proxy_url - ) - when static in ["true", true] do - uri_without_static_param = UriHelper.modify_uri_params(current_url(conn), %{}, ["static"]) - redirect(conn, external: uri_without_static_param) - end - - defp handle_preview("image/gif" = _content_type, _content_length, conn, media_proxy_url) do - redirect(conn, external: media_proxy_url) - end - - defp handle_preview("image/" <> _ = _content_type, content_length, conn, media_proxy_url) - when is_integer(content_length) and content_length > 0 and - content_length < @min_content_length_for_preview do - redirect(conn, external: media_proxy_url) - end - - defp handle_preview("image/png" <> _ = _content_type, _content_length, conn, media_proxy_url) do + defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do handle_png_preview(conn, media_proxy_url) end - defp handle_preview("image/" <> _ = _content_type, _content_length, conn, media_proxy_url) do + defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do handle_jpeg_preview(conn, media_proxy_url) end - defp handle_preview("video/" <> _ = _content_type, _content_length, conn, media_proxy_url) do + defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do handle_video_preview(conn, media_proxy_url) end - defp handle_preview(_unsupported_content_type, _content_length, conn, media_proxy_url) do + defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do fallback_on_preview_error(conn, media_proxy_url) end defp handle_png_preview(conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() - with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(), - {:ok, thumbnail_binary} <- + with {:ok, thumbnail_binary} <- MediaHelper.image_resize( media_proxy_url, %{ @@ -146,9 +131,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp handle_jpeg_preview(conn, media_proxy_url) do quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() - with {thumbnail_max_width, thumbnail_max_height} <- thumbnail_max_dimensions(), - {:ok, thumbnail_binary} <- + with {:ok, thumbnail_binary} <- MediaHelper.image_resize( media_proxy_url, %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} @@ -174,6 +159,15 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end end + defp drop_static_param_and_redirect(conn) do + uri_without_static_param = + conn + |> current_url() + |> UriHelper.modify_uri_params(%{}, ["static"]) + + redirect(conn, external: uri_without_static_param) + end + defp fallback_on_preview_error(conn, media_proxy_url) do redirect(conn, external: media_proxy_url) end @@ -189,7 +183,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp thumbnail_max_dimensions do - config = Config.get([:media_preview_proxy], []) + config = media_preview_proxy_config() thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width) thumbnail_max_height = Keyword.fetch!(config, :thumbnail_max_height) @@ -197,6 +191,14 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do {thumbnail_max_width, thumbnail_max_height} end + defp min_content_length_for_preview do + Keyword.get(media_preview_proxy_config(), :min_content_length, 0) + end + + defp media_preview_proxy_config do + Config.get!([:media_preview_proxy]) + end + defp media_proxy_opts do Config.get([:media_proxy, :proxy_opts], []) end -- cgit v1.2.3 From f7e40f7ef134a3030aa61114daa39810efb5889d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 17 Sep 2020 09:32:50 -0500 Subject: Deny ConfigDB migration when deprecated settings found --- lib/mix/tasks/pleroma/config.ex | 10 +++++-- lib/pleroma/config/deprecation_warnings.ex | 43 ++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 904c5a74b..18f99318d 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -32,7 +32,8 @@ defmodule Mix.Tasks.Pleroma.Config do @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do - if Pleroma.Config.get([:configurable_from_database]) do + with true <- Pleroma.Config.get([:configurable_from_database]), + :ok <- Pleroma.Config.DeprecationWarnings.warn() do config_file = if file_path do file_path @@ -46,7 +47,8 @@ defmodule Mix.Tasks.Pleroma.Config do do_migrate_to_db(config_file) else - migration_error() + :error -> deprecation_error() + _ -> migration_error() end end @@ -120,6 +122,10 @@ defmodule Mix.Tasks.Pleroma.Config do ) end + defp deprecation_error do + shell_error("Migration is not allowed until all deprecation warnings have been resolved.") + end + if Code.ensure_loaded?(Config.Reader) do defp config_header, do: "import Config\r\n\r\n" defp read_file(config_file), do: Config.Reader.read_imports!(config_file) diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 412d55a77..98c4dc9c8 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -26,6 +26,10 @@ defmodule Pleroma.Config.DeprecationWarnings do !!!DEPRECATION WARNING!!! You are using the old configuration mechanism for the hellthread filter. Please check config.md. """) + + :error + else + :ok end end @@ -47,17 +51,26 @@ defmodule Pleroma.Config.DeprecationWarnings do config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} """) + + :error + else + :ok end end def warn do - check_hellthread_threshold() - mrf_user_allowlist() - check_old_mrf_config() - check_media_proxy_whitelist_config() - check_welcome_message_config() - check_gun_pool_options() - check_activity_expiration_config() + with :ok <- check_hellthread_threshold(), + :ok <- mrf_user_allowlist(), + :ok <- check_old_mrf_config(), + :ok <- check_media_proxy_whitelist_config(), + :ok <- check_welcome_message_config(), + :ok <- check_gun_pool_options(), + :ok <- check_activity_expiration_config() do + :ok + else + _ -> + :error + end end def check_welcome_message_config do @@ -74,6 +87,10 @@ defmodule Pleroma.Config.DeprecationWarnings do \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` """) + + :error + else + :ok end end @@ -101,8 +118,11 @@ defmodule Pleroma.Config.DeprecationWarnings do end end) - if warning != "" do + if warning == "" do + :ok + else Logger.warn(warning_preface <> warning) + :error end end @@ -115,6 +135,10 @@ defmodule Pleroma.Config.DeprecationWarnings do !!!DEPRECATION WARNING!!! Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. """) + + :error + else + :ok end end @@ -157,6 +181,9 @@ defmodule Pleroma.Config.DeprecationWarnings do Logger.warn(Enum.join([warning_preface | pool_warnings])) Config.put(:pools, updated_config) + :error + else + :ok end end -- cgit v1.2.3 From 41939e3175cf31884cb84acd136c303a84c77f8c Mon Sep 17 00:00:00 2001 From: stwf Date: Mon, 14 Sep 2020 11:40:52 -0400 Subject: User search respect discoverable flag --- lib/pleroma/user/search.ex | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 7babd47ea..b8c648672 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -52,6 +52,7 @@ defmodule Pleroma.User.Search do |> base_query(following) |> filter_blocked_user(for_user) |> filter_invisible_users() + |> filter_discoverable_users() |> filter_internal_users() |> filter_blocked_domains(for_user) |> fts_search(query_string) @@ -122,6 +123,10 @@ defmodule Pleroma.User.Search do from(q in query, where: q.invisible == false) end + defp filter_discoverable_users(query) do + from(q in query, where: q.discoverable == true) + end + defp filter_internal_users(query) do from(q in query, where: q.actor_type != "Application") end -- cgit v1.2.3 From 3a0f99ed35a84145e713d4c640c50dc82c1b0dbb Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 14 Sep 2020 13:52:13 +0200 Subject: KeywordPolicy: Still match when fields are absent --- lib/pleroma/web/activity_pub/mrf/keyword_policy.ex | 67 +++++++++++----------- 1 file changed, 32 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 15e09dcf0..db66cfa3e 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -20,9 +20,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do String.match?(string, pattern) end - defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do + defp object_payload(%{} = object) do + [object["content"], object["summary"], object["name"]] + |> Enum.filter(& &1) + |> Enum.join("\n") + end + + defp check_reject(%{"object" => %{} = object} = message) do + payload = object_payload(object) + if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> - string_matches?(content, pattern) or string_matches?(summary, pattern) + string_matches?(payload, pattern) end) do {:reject, "[KeywordPolicy] Matches with rejected keyword"} else @@ -30,12 +38,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end end - defp check_ftl_removal( - %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message - ) do + defp check_ftl_removal(%{"to" => to, "object" => %{} = object} = message) do + payload = object_payload(object) + if Pleroma.Constants.as_public() in to and Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern -> - string_matches?(content, pattern) or string_matches?(summary, pattern) + string_matches?(payload, pattern) end) do to = List.delete(to, Pleroma.Constants.as_public()) cc = [Pleroma.Constants.as_public() | message["cc"] || []] @@ -51,35 +59,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end end - defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do - content = - if is_binary(content) do - content - else - "" - end - - summary = - if is_binary(summary) do - summary - else - "" - end - - {content, summary} = - Enum.reduce( - Pleroma.Config.get([:mrf_keyword, :replace]), - {content, summary}, - fn {pattern, replacement}, {content_acc, summary_acc} -> - {String.replace(content_acc, pattern, replacement), - String.replace(summary_acc, pattern, replacement)} - end - ) - - {:ok, - message - |> put_in(["object", "content"], content) - |> put_in(["object", "summary"], summary)} + defp check_replace(%{"object" => %{} = object} = message) do + object = + ["content", "name", "summary"] + |> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end) + |> Enum.reduce(object, fn field, object -> + data = + Enum.reduce( + Pleroma.Config.get([:mrf_keyword, :replace]), + object[field], + fn {pat, repl}, acc -> String.replace(acc, pat, repl) end + ) + + Map.put(object, field, data) + end) + + message = Map.put(message, "object", object) + + {:ok, message} end @impl true -- cgit v1.2.3 From abf25e5d5254edc88a65610bf5a0fd7e52f545c3 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 12 Sep 2020 12:05:36 +0200 Subject: Create MRF.filter_pipeline to inject :object_data when present --- lib/pleroma/web/activity_pub/mrf.ex | 24 +++++++++++++++++++--- .../web/activity_pub/mrf/subchain_policy.ex | 3 +-- lib/pleroma/web/activity_pub/pipeline.ex | 8 ++++++-- 3 files changed, 28 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index 206d6af52..5e5361082 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -5,16 +5,34 @@ defmodule Pleroma.Web.ActivityPub.MRF do @callback filter(Map.t()) :: {:ok | :reject, Map.t()} - def filter(policies, %{} = object) do + def filter(policies, %{} = message) do policies - |> Enum.reduce({:ok, object}, fn - policy, {:ok, object} -> policy.filter(object) + |> Enum.reduce({:ok, message}, fn + policy, {:ok, message} -> policy.filter(message) _, error -> error end) end def filter(%{} = object), do: get_policies() |> filter(object) + def pipeline_filter(%{} = message, meta) do + object = meta[:object_data] + ap_id = message["object"] + + if object && ap_id do + with {:ok, message} <- filter(Map.put(message, "object", object)) do + meta = Keyword.put(meta, :object_data, message["object"]) + {:ok, Map.put(message, "object", ap_id), meta} + else + {err, message} -> {err, message, meta} + end + else + {err, message} = filter(message) + + {err, message, meta} + end + end + def get_policies do Pleroma.Config.get([:mrf, :policies], []) |> get_policies() end diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex index c9f20571f..048052da6 100644 --- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -28,8 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do }" ) - subchain - |> MRF.filter(message) + MRF.filter(subchain, message) else _e -> {:ok, message} end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 36e325c37..2db86f116 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -26,13 +26,17 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do {:error, e} -> {:error, e} + + {:reject, e} -> + {:reject, e} end end def do_common_pipeline(object, meta) do with {_, {:ok, validated_object, meta}} <- {:validate_object, ObjectValidator.validate(object, meta)}, - {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, + {_, {:ok, mrfd_object, meta}} <- + {:mrf_object, MRF.pipeline_filter(validated_object, meta)}, {_, {:ok, activity, meta}} <- {:persist_object, ActivityPub.persist(mrfd_object, meta)}, {_, {:ok, activity, meta}} <- @@ -40,7 +44,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do {:ok, activity, meta} else - {:mrf_object, {:reject, _}} -> {:ok, nil, meta} + {:mrf_object, {:reject, message, _}} -> {:reject, message} e -> {:error, e} end end -- cgit v1.2.3 From 7bf269fe836ded974d2187c6b36eba4ab185ff25 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 14 Sep 2020 14:07:22 +0200 Subject: Fix MRF reject for ChatMessage --- lib/pleroma/web/api_spec/operations/chat_operation.ex | 3 ++- lib/pleroma/web/api_spec/operations/status_operation.ex | 2 +- lib/pleroma/web/common_api/common_api.ex | 3 +++ lib/pleroma/web/pleroma_api/controllers/chat_controller.ex | 10 ++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index b1a0d26ab..56554d5b4 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -184,7 +184,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do "application/json", ChatMessage ), - 400 => Operation.response("Bad Request", "application/json", ApiError) + 400 => Operation.response("Bad Request", "application/json", ApiError), + 422 => Operation.response("MRF Rejection", "application/json", ApiError) }, security: [ %{ diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 5bd4619d5..d7ebde6f6 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -55,7 +55,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do "application/json", %Schema{oneOf: [Status, ScheduledStatus]} ), - 422 => Operation.response("Bad Request", "application/json", ApiError) + 422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index a8c83bc8f..60a50b027 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -48,6 +48,9 @@ defmodule Pleroma.Web.CommonAPI do local: true )} do {:ok, activity} + else + {:common_pipeline, {:reject, _} = e} -> e + e -> e end end diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index 27c9a2e0f..867cff829 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -90,6 +90,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do conn |> put_view(MessageReferenceView) |> render("show.json", chat_message_reference: cm_ref) + else + {:reject, message} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{error: message}) end end -- cgit v1.2.3 From f2ef9735c52c648a03de4af41f19bb4ec857de03 Mon Sep 17 00:00:00 2001 From: Steven Fuchs Date: Fri, 18 Sep 2020 11:58:22 +0000 Subject: Federate data through persistent websocket connections --- lib/pleroma/application.ex | 9 +- lib/pleroma/object/fetcher.ex | 60 ++++++-- lib/pleroma/signature.ex | 6 +- lib/pleroma/user.ex | 10 +- lib/pleroma/web/activity_pub/activity_pub.ex | 14 +- lib/pleroma/web/activity_pub/publisher.ex | 34 +++-- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- lib/pleroma/web/fed_sockets/fed_registry.ex | 185 ++++++++++++++++++++++++ lib/pleroma/web/fed_sockets/fed_socket.ex | 137 ++++++++++++++++++ lib/pleroma/web/fed_sockets/fed_sockets.ex | 182 +++++++++++++++++++++++ lib/pleroma/web/fed_sockets/fetch_registry.ex | 151 +++++++++++++++++++ lib/pleroma/web/fed_sockets/incoming_handler.ex | 88 +++++++++++ lib/pleroma/web/fed_sockets/ingester_worker.ex | 33 +++++ lib/pleroma/web/fed_sockets/outgoing_handler.ex | 146 +++++++++++++++++++ lib/pleroma/web/fed_sockets/socket_info.ex | 52 +++++++ lib/pleroma/web/fed_sockets/supervisor.ex | 59 ++++++++ 16 files changed, 1123 insertions(+), 45 deletions(-) create mode 100644 lib/pleroma/web/fed_sockets/fed_registry.ex create mode 100644 lib/pleroma/web/fed_sockets/fed_socket.ex create mode 100644 lib/pleroma/web/fed_sockets/fed_sockets.ex create mode 100644 lib/pleroma/web/fed_sockets/fetch_registry.ex create mode 100644 lib/pleroma/web/fed_sockets/incoming_handler.ex create mode 100644 lib/pleroma/web/fed_sockets/ingester_worker.ex create mode 100644 lib/pleroma/web/fed_sockets/outgoing_handler.ex create mode 100644 lib/pleroma/web/fed_sockets/socket_info.ex create mode 100644 lib/pleroma/web/fed_sockets/supervisor.ex (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index c39e24919..00ec79a2a 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -99,7 +99,7 @@ defmodule Pleroma.Application do {Oban, Config.get(Oban)} ] ++ task_children(@env) ++ - streamer_child(@env) ++ + dont_run_in_test(@env) ++ chat_child(@env, chat_enabled?()) ++ [ Pleroma.Web.Endpoint, @@ -188,16 +188,17 @@ defmodule Pleroma.Application do defp chat_enabled?, do: Config.get([:chat, :enabled]) - defp streamer_child(env) when env in [:test, :benchmark], do: [] + defp dont_run_in_test(env) when env in [:test, :benchmark], do: [] - defp streamer_child(_) do + defp dont_run_in_test(_) do [ {Registry, [ name: Pleroma.Web.Streamer.registry(), keys: :duplicate, partitions: System.schedulers_online() - ]} + ]}, + Pleroma.Web.FedSockets.Supervisor ] end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 24dc7cb95..169298b34 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator + alias Pleroma.Web.FedSockets require Logger require Pleroma.Constants @@ -182,9 +183,47 @@ defmodule Pleroma.Object.Fetcher do end end - def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do + def fetch_and_contain_remote_object_from_id(prm, opts \\ []) + + def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts), + do: fetch_and_contain_remote_object_from_id(id, opts) + + def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do Logger.debug("Fetching object #{id} via AP") + with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, + {:ok, body} <- get_object(id, opts), + {:ok, data} <- safe_json_decode(body), + :ok <- Containment.contain_origin_from_id(id, data) do + {:ok, data} + else + {:scheme, _} -> + {:error, "Unsupported URI scheme"} + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + def fetch_and_contain_remote_object_from_id(_id, _opts), + do: {:error, "id must be a string"} + + defp get_object(id, opts) do + with false <- Keyword.get(opts, :force_http, false), + {:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do + Logger.debug("fetching via fedsocket - #{inspect(id)}") + FedSockets.fetch(fedsocket, id) + else + _other -> + Logger.debug("fetching via http - #{inspect(id)}") + get_object_http(id) + end + end + + defp get_object_http(id) do date = Pleroma.Signature.signed_date() headers = @@ -192,20 +231,13 @@ defmodule Pleroma.Object.Fetcher do |> maybe_date_fetch(date) |> sign_fetch(id, date) - Logger.debug("Fetch headers: #{inspect(headers)}") + case HTTP.get(id, headers) do + {:ok, %{body: body, status: code}} when code in 200..299 -> + {:ok, body} - with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, - {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers), - {:ok, data} <- Jason.decode(body), - :ok <- Containment.contain_origin_from_id(id, data) do - {:ok, data} - else {:ok, %{status: code}} when code in [404, 410] -> {:error, "Object has been deleted"} - {:scheme, _} -> - {:error, "Unsupported URI scheme"} - {:error, e} -> {:error, e} @@ -214,8 +246,6 @@ defmodule Pleroma.Object.Fetcher do end end - def fetch_and_contain_remote_object_from_id(%{"id" => id}), - do: fetch_and_contain_remote_object_from_id(id) - - def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + defp safe_json_decode(nil), do: {:ok, nil} + defp safe_json_decode(json), do: Jason.decode(json) end diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 3aa6909d2..e388993b7 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -39,7 +39,7 @@ defmodule Pleroma.Signature do def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> @@ -50,8 +50,8 @@ defmodule Pleroma.Signature do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true), + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1ffe60dfc..d92484a40 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1820,12 +1820,12 @@ defmodule Pleroma.User do def html_filter_policy(_), do: Config.get([:markup, :scrub_policy]) - def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) + def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts) - def get_or_fetch_by_ap_id(ap_id) do + def get_or_fetch_by_ap_id(ap_id, opts \\ []) do cached_user = get_cached_by_ap_id(ap_id) - maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id) + maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts) case {cached_user, maybe_fetched_user} do {_, {:ok, %User{} = user}} -> @@ -1898,8 +1898,8 @@ defmodule Pleroma.User do def public_key(_), do: {:error, "key not found"} - def get_public_key_for_ap_id(ap_id) do - with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), + def get_public_key_for_ap_id(ap_id, opts \\ []) do + with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts), {:ok, public_key} <- public_key(user) do {:ok, public_key} else diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 92fc1e422..06e8e1a7c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1270,10 +1270,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def fetch_follow_information_for_user(user) do with {:ok, following_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.following_address, + force_http: true + ), {:ok, hide_follows} <- collection_private(following_data), {:ok, followers_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true), {:ok, hide_followers} <- collection_private(followers_data) do {:ok, %{ @@ -1347,8 +1349,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do 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), + def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do + with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts), {:ok, data} <- user_data_from_user_object(data) do {:ok, maybe_update_follow_information(data)} else @@ -1390,13 +1392,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def make_user_from_ap_id(ap_id) do + def make_user_from_ap_id(ap_id, opts \\ []) do user = User.get_cached_by_ap_id(ap_id) if user && !User.ap_enabled?(user) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else - with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do + with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do if user do user |> User.remote_user_changeset(data) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index d88f7f3ee..9c3956683 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.FedSockets require Pleroma.Constants @@ -50,15 +51,35 @@ defmodule Pleroma.Web.ActivityPub.Publisher do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do Logger.debug("Federating #{id} to #{inbox}") - uri = URI.parse(inbox) + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + Logger.debug("publishing via fedsockets - #{inspect(inbox)}") + FedSockets.publish(fedsocket, json) + _ -> + Logger.debug("publishing via http - #{inspect(inbox)}") + http_publish(inbox, actor, json, params) + end + end + + def publish_one(%{actor_id: actor_id} = params) do + actor = User.get_cached_by_id(actor_id) + + params + |> Map.delete(:actor_id) + |> Map.put(:actor, actor) + |> publish_one() + end + + defp http_publish(inbox, actor, json, params) do + uri = %{path: path} = URI.parse(inbox) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) date = Pleroma.Signature.signed_date() signature = Pleroma.Signature.sign(actor, %{ - "(request-target)": "post #{uri.path}", + "(request-target)": "post #{path}", host: signature_host(uri), "content-length": byte_size(json), digest: digest, @@ -89,15 +110,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - def publish_one(%{actor_id: actor_id} = params) do - actor = User.get_cached_by_id(actor_id) - - params - |> Map.delete(:actor_id) - |> Map.put(:actor, actor) - |> publish_one() - end - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do if port == URI.default_port(scheme) do host diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index fcca014f0..aa6a69463 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1000,7 +1000,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def upgrade_user_from_ap_id(ap_id) do with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), - {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), + {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true), {:ok, user} <- update_user(user, data) do TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) {:ok, user} diff --git a/lib/pleroma/web/fed_sockets/fed_registry.ex b/lib/pleroma/web/fed_sockets/fed_registry.ex new file mode 100644 index 000000000..e00ea69c0 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_registry.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedRegistry do + @moduledoc """ + The FedRegistry stores the active FedSockets for quick retrieval. + + The storage and retrieval portion of the FedRegistry is done in process through + elixir's `Registry` module for speed and its ability to monitor for terminated processes. + + Dropped connections will be caught by `Registry` and deleted. Since the next + message will initiate a new connection there is no reason to try and reconnect at that point. + + Normally outside modules should have no need to call or use the FedRegistry themselves. + """ + + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @default_rejection_duration 15 * 60 * 1000 + @rejections :fed_socket_rejections + + @doc """ + Retrieves a FedSocket from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_socket} for working FedSockets + * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval + * {:error, some_reason} usually :missing for unknown origins + """ + def get_fed_socket(origin) do + case get_registry_data(origin) do + {:error, reason} -> + {:error, reason} + + {:ok, %{state: :connected} = socket_info} -> + {:ok, socket_info} + end + end + + @doc """ + Adds a connected FedSocket to the Registry. + + Always returns {:ok, fed_socket} + """ + def add_fed_socket(origin, pid \\ nil) do + origin + |> SocketInfo.build(pid) + |> SocketInfo.connect() + |> add_socket_info + end + + defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do + case Registry.register(FedSockets.Registry, origin, socket_info) do + {:ok, _owner} -> + clear_prior_rejection(origin) + Logger.debug("fedsocket added: #{inspect(origin)}") + + {:ok, socket_info} + + {:error, {:already_registered, _pid}} -> + FedSocket.close(socket_info) + existing_socket_info = Registry.lookup(FedSockets.Registry, origin) + + {:ok, existing_socket_info} + + _ -> + {:error, :error_adding_socket} + end + end + + @doc """ + Mark this origin as having rejected a connection attempt. + This will keep it from getting additional connection attempts + for a period of time specified in the config. + + Always returns {:ok, new_reg_data} + """ + def set_host_rejected(uri) do + new_reg_data = + uri + |> SocketInfo.origin() + |> get_or_create_registry_data() + |> set_to_rejected() + |> save_registry_data() + + {:ok, new_reg_data} + end + + @doc """ + Retrieves the FedRegistryData from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_registry_data} for known origins + * {:error, :missing} for uniknown origins + * {:error, :cache_error} indicating some low level runtime issues + """ + def get_registry_data(origin) do + case Registry.lookup(FedSockets.Registry, origin) do + [] -> + if is_rejected?(origin) do + Logger.debug("previously rejected fedsocket requested") + {:error, :rejected} + else + {:error, :missing} + end + + [{_pid, %{state: :connected} = socket_info}] -> + {:ok, socket_info} + + _ -> + {:error, :cache_error} + end + end + + @doc """ + Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo + """ + def list_all do + (list_all_connected() ++ list_all_rejected()) + |> Enum.into(%{}) + end + + defp list_all_connected do + FedSockets.Registry + |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}]) + end + + defp list_all_rejected do + {:ok, keys} = Cachex.keys(@rejections) + + {:ok, registry_data} = + Cachex.execute(@rejections, fn worker -> + Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end) + end) + + registry_data + end + + defp clear_prior_rejection(origin), + do: Cachex.del(@rejections, origin) + + defp is_rejected?(origin) do + case Cachex.get(@rejections, origin) do + {:ok, nil} -> + false + + {:ok, _} -> + true + end + end + + defp get_or_create_registry_data(origin) do + case get_registry_data(origin) do + {:error, :missing} -> + %SocketInfo{origin: origin} + + {:ok, socket_info} -> + socket_info + end + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do + {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end) + socket_info + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do + rejection_expiration = + Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration) + + {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration) + socket_info + end + + defp set_to_rejected(%SocketInfo{} = socket_info), + do: %SocketInfo{socket_info | state: :rejected} +end diff --git a/lib/pleroma/web/fed_sockets/fed_socket.ex b/lib/pleroma/web/fed_sockets/fed_socket.ex new file mode 100644 index 000000000..98d64e65a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_socket.ex @@ -0,0 +1,137 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedSocket do + @moduledoc """ + The FedSocket module abstracts the actions to be taken taken on connections regardless of + whether the connection started as inbound or outbound. + + + Normally outside modules will have no need to call the FedSocket module directly. + """ + + alias Pleroma.Object + alias Pleroma.Object.Containment + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectView + alias Pleroma.Web.ActivityPub.UserView + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.FedSockets.FetchRegistry + alias Pleroma.Web.FedSockets.IngesterWorker + alias Pleroma.Web.FedSockets.OutgoingHandler + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103" + + def connect_to_host(uri) do + case OutgoingHandler.start_link(uri) do + {:ok, pid} -> + {:ok, pid} + + error -> + {:error, error} + end + end + + def close(%SocketInfo{pid: socket_pid}), + do: Process.send(socket_pid, :close, []) + + def publish(%SocketInfo{pid: socket_pid}, json) do + %{action: :publish, data: json} + |> Jason.encode!() + |> send_packet(socket_pid) + end + + def fetch(%SocketInfo{pid: socket_pid}, id) do + fetch_uuid = FetchRegistry.register_fetch(id) + + %{action: :fetch, data: id, uuid: fetch_uuid} + |> Jason.encode!() + |> send_packet(socket_pid) + + wait_for_fetch_to_return(fetch_uuid, 0) + end + + def receive_package(%SocketInfo{} = fed_socket, json) do + json + |> Jason.decode!() + |> process_package(fed_socket) + end + + defp wait_for_fetch_to_return(uuid, cntr) do + case FetchRegistry.check_fetch(uuid) do + {:error, :waiting} -> + Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc()) + wait_for_fetch_to_return(uuid, cntr + 1) + + {:error, :missing} -> + Logger.error("FedSocket fetch timed out - #{inspect(uuid)}") + {:error, :timeout} + + {:ok, _fr} -> + FetchRegistry.pop_fetch(uuid) + end + end + + defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do + if Containment.contain_origin(origin, data) do + IngesterWorker.enqueue("ingest", %{"object" => data}) + end + + {:reply, %{"action" => "publish_reply", "status" => "processed"}} + end + + defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do + FetchRegistry.register_fetch_received(uuid, data) + {:noreply, nil} + end + + defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do + {:ok, data} = render_fetched_data(ap_id, uuid) + {:reply, data} + end + + defp process_package(%{"action" => "publish_reply"}, _fed_socket) do + {:noreply, nil} + end + + defp process_package(other, _fed_socket) do + Logger.warn("unknown json packages received #{inspect(other)}") + {:noreply, nil} + end + + defp render_fetched_data(ap_id, uuid) do + {:ok, + %{ + "action" => "fetch_reply", + "status" => "processed", + "uuid" => uuid, + "data" => represent_item(ap_id) + }} + end + + defp represent_item(ap_id) do + case User.get_by_ap_id(ap_id) do + nil -> + object = Object.get_cached_by_ap_id(ap_id) + + if Visibility.is_public?(object) do + Phoenix.View.render_to_string(ObjectView, "object.json", object: object) + else + nil + end + + user -> + Phoenix.View.render_to_string(UserView, "user.json", user: user) + end + end + + defp send_packet(data, socket_pid) do + Process.send(socket_pid, {:send, data}, []) + end + + def shake, do: @shake +end diff --git a/lib/pleroma/web/fed_sockets/fed_sockets.ex b/lib/pleroma/web/fed_sockets/fed_sockets.ex new file mode 100644 index 000000000..035d54796 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_sockets.ex @@ -0,0 +1,182 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets do + @moduledoc """ + This documents the FedSockets framework. A framework for federating + ActivityPub objects between servers via persistant WebSocket connections. + + FedSockets allow servers to authenticate on first contact and maintain that + connection, eliminating the need to authenticate every time data needs to be shared. + + ## Protocol + FedSockets currently support 2 types of data transfer: + * `publish` method which doesn't require a response + * `fetch` method requires a response be sent + + ### Publish + The publish operation sends a json encoded map of the shape: + %{action: :publish, data: json} + and accepts (but does not require) a reply of form: + %{"action" => "publish_reply"} + + The outgoing params represent + * data: ActivityPub object encoded into json + + + ### Fetch + The fetch operation sends a json encoded map of the shape: + %{action: :fetch, data: id, uuid: fetch_uuid} + and requires a reply of form: + %{"action" => "fetch_reply", "uuid" => uuid, "data" => data} + + The outgoing params represent + * id: an ActivityPub object URI + * uuid: a unique uuid generated by the sender + + The reply params represent + * data: an ActivityPub object encoded into json + * uuid: the uuid sent along with the fetch request + + ## Examples + Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module. + + A typical publish operation can be performed through the following code, and a fetch operation in a similar manner. + + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + FedSockets.publish(fedsocket, json) + + _ -> + alternative_publish(inbox, actor, json, params) + end + + ## Configuration + FedSockets have the following config settings + + config :pleroma, :fed_sockets, + enabled: true, + ping_interval: :timer.seconds(15), + connection_duration: :timer.hours(1), + rejection_duration: :timer.hours(1), + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime. + * connection_duration - How long a FedSocket can sit idle before it's culled. + * rejection_duration - After failing to make a FedSocket connection a host will be excluded + from further connections for this amount of time + * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry + * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry + + Cachex options are + * default: the minimum amount of time a fetch can wait before it times out. + * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed + * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement + + """ + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + @doc """ + returns a FedSocket for the given origin. Will reuse an existing one or create a new one. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + + It can and usually does include additional path parameters, + but these are ignored as the FedSockets are organized by host and port info alone. + """ + def get_or_create_fed_socket(address) do + with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)}, + {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)}, + {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do + Logger.debug("fedsocket created for - #{inspect(address)}") + {:ok, fed_socket} + else + {:cache, {:ok, socket}} -> + Logger.debug("fedsocket found in cache - #{inspect(address)}") + {:ok, socket} + + {:connect, {:error, _host}} -> + Logger.debug("set host rejected for - #{inspect(address)}") + FedRegistry.set_host_rejected(address) + {:error, :rejected} + + {_, {:error, :disabled}} -> + {:error, :disabled} + + {_, {:error, reason}} -> + Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}") + {:error, reason} + end + end + + @doc """ + returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + """ + def get_fed_socket(address) do + origin = SocketInfo.origin(address) + + with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)}, + {:ok, socket} <- FedRegistry.get_fed_socket(origin) do + {:ok, socket} + else + {:config, _} -> + {:error, :disabled} + + {:error, :rejected} -> + Logger.debug("FedSocket previously rejected - #{inspect(origin)}") + {:error, :rejected} + + {:error, reason} -> + {:error, reason} + end + end + + @doc """ + Sends the supplied data via the publish protocol. + It will not block waiting for a reply. + Returns :ok but this is not an indication of a successful transfer. + + the data is expected to be JSON encoded binary data. + """ + def publish(%SocketInfo{} = fed_socket, json) do + FedSocket.publish(fed_socket, json) + end + + @doc """ + Sends the supplied data via the fetch protocol. + It will block waiting for a reply or timeout. + + Returns {:ok, object} where object is the requested object (or nil) + {:error, :timeout} in the event the message was not responded to + + the id is expected to be the URI of an ActivityPub object. + """ + def fetch(%SocketInfo{} = fed_socket, id) do + FedSocket.fetch(fed_socket, id) + end + + @doc """ + Disconnect all and restart FedSockets. + This is mainly used in development and testing but could be useful in production. + """ + def reset do + FedRegistry + |> Process.whereis() + |> Process.exit(:testing) + end + + def uri_for_origin(origin), + do: "ws://#{origin}/api/fedsocket/v1" +end diff --git a/lib/pleroma/web/fed_sockets/fetch_registry.ex b/lib/pleroma/web/fed_sockets/fetch_registry.ex new file mode 100644 index 000000000..7897f0fc6 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fetch_registry.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FetchRegistry do + @moduledoc """ + The FetchRegistry acts as a broker for fetch requests and return values. + This allows calling processes to block while waiting for a reply. + It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing + multi threaded processes to avoid bottlenecking. + + Normally outside modules will have no need to call or use the FetchRegistry themselves. + + The `Cachex` parameters can be controlled from the config. Since exact timeout intervals + aren't necessary the following settings are used by default: + + config :pleroma, :fed_sockets, + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + + """ + + defmodule FetchRegistryData do + defstruct uuid: nil, + sent_json: nil, + received_json: nil, + sent_at: nil, + received_at: nil + end + + alias Ecto.UUID + + require Logger + + @fetches :fed_socket_fetches + + @doc """ + Registers a json request wth the FetchRegistry and returns the identifying UUID. + """ + def register_fetch(json) do + %FetchRegistryData{uuid: uuid} = + json + |> new_registry_data + |> save_registry_data + + uuid + end + + @doc """ + Reports on the status of a Fetch given the identifying UUID. + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def check_fetch(uuid) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil}} -> + {:error, :waiting} + + {:ok, %FetchRegistryData{} = reg_data} -> + {:ok, reg_data} + + e -> + e + end + end + + @doc """ + Retrieves the response to a fetch given the identifying UUID. + The completed fetch will be deleted from the FetchRegistry + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def pop_fetch(uuid) do + case check_fetch(uuid) do + {:ok, %FetchRegistryData{received_json: received_json}} -> + delete_registry_data(uuid) + {:ok, received_json} + + e -> + e + end + end + + @doc """ + This is called to register a fetch has returned. + It expects the result data along with the UUID that was sent in the request + + Will return the fetched object or :error + """ + def register_fetch_received(uuid, data) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil} = reg_data} -> + reg_data + |> set_fetch_received(data) + |> save_registry_data() + + {:ok, %FetchRegistryData{} = reg_data} -> + Logger.warn("tried to add fetched data twice - #{uuid}") + reg_data + + {:error, _} -> + Logger.warn("Error adding fetch to registry - #{uuid}") + :error + end + end + + defp new_registry_data(json) do + %FetchRegistryData{ + uuid: UUID.generate(), + sent_json: json, + sent_at: :erlang.monotonic_time(:millisecond) + } + end + + defp get_registry_data(origin) do + case Cachex.get(@fetches, origin) do + {:ok, nil} -> + {:error, :missing} + + {:ok, reg_data} -> + {:ok, reg_data} + + _ -> + {:error, :cache_error} + end + end + + defp set_fetch_received(%FetchRegistryData{} = reg_data, data), + do: %FetchRegistryData{ + reg_data + | received_at: :erlang.monotonic_time(:millisecond), + received_json: data + } + + defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do + {:ok, true} = Cachex.put(@fetches, uuid, reg_data) + reg_data + end + + defp delete_registry_data(origin), + do: {:ok, true} = Cachex.del(@fetches, origin) +end diff --git a/lib/pleroma/web/fed_sockets/incoming_handler.ex b/lib/pleroma/web/fed_sockets/incoming_handler.ex new file mode 100644 index 000000000..49d0d9d84 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/incoming_handler.ex @@ -0,0 +1,88 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IncomingHandler do + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + import HTTPSignatures, only: [validate_conn: 1, split_signature: 1] + + @behaviour :cowboy_websocket + + def init(req, state) do + shake = FedSocket.shake() + + with true <- Pleroma.Config.get([:fed_sockets, :enabled]), + sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil), + headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req), + true <- validate_conn(%{req_headers: headers}), + %{"keyId" => origin} <- split_signature(headers["signature"]) do + req = + if is_nil(sec_protocol) do + req + else + :cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req) + end + + {:cowboy_websocket, req, %{origin: origin}, %{}} + else + _ -> + {:ok, req, state} + end + end + + def websocket_init(%{origin: origin}) do + case FedRegistry.add_fed_socket(origin) do + {:ok, socket_info} -> + {:ok, socket_info} + + e -> + Logger.error("FedSocket websocket_init failed - #{inspect(e)}") + {:error, inspect(e)} + end + end + + # Use the ping to check if the connection should be expired + def websocket_handle(:ping, socket_info) do + if SocketInfo.expired?(socket_info) do + {:stop, socket_info} + else + {:ok, socket_info, :hibernate} + end + end + + def websocket_handle({:text, data}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:ok, socket_info} + + {:reply, reply} -> + {:reply, {:text, Jason.encode!(reply)}, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:ok, socket_info} + end + end + + def websocket_info({:send, message}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + {:reply, {:text, message}, socket_info} + end + + def websocket_info(:close, state) do + {:stop, state} + end + + def websocket_info(message, state) do + Logger.debug("#{__MODULE__} unknown message #{inspect(message)}") + {:ok, state} + end +end diff --git a/lib/pleroma/web/fed_sockets/ingester_worker.ex b/lib/pleroma/web/fed_sockets/ingester_worker.ex new file mode 100644 index 000000000..325f2a4ab --- /dev/null +++ b/lib/pleroma/web/fed_sockets/ingester_worker.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IngesterWorker do + use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue" + require Logger + + alias Pleroma.Web.Federator + + @impl Oban.Worker + def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do + try do + ingestee + |> Jason.decode!() + |> do_ingestion() + rescue + e -> + Logger.error("IngesterWorker error - #{inspect(e)}") + e + end + end + + defp do_ingestion(params) do + case Federator.incoming_ap_doc(params) do + {:error, reason} -> + {:error, reason} + + {:ok, object} -> + {:ok, object} + end + end +end diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex new file mode 100644 index 000000000..6ddef17fe --- /dev/null +++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex @@ -0,0 +1,146 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.OutgoingHandler do + use GenServer + + require Logger + + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + def start_link(uri) do + GenServer.start_link(__MODULE__, %{uri: uri}) + end + + def init(%{uri: uri}) do + case initiate_connection(uri) do + {:ok, ws_origin, conn_pid} -> + FedRegistry.add_fed_socket(ws_origin, conn_pid) + + {:error, reason} -> + Logger.debug("Outgoing connection failed - #{inspect(reason)}") + :ignore + end + end + + def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:noreply, socket_info} + + {:reply, reply} -> + :gun.ws_send(conn_pid, {:text, Jason.encode!(reply)}) + {:noreply, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:noreply, socket_info} + end + end + + def handle_info(:close, state) do + Logger.debug("Sending close frame !!!!!!!") + {:close, state} + end + + def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do + {:stop, :normal, state} + end + + def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do + socket_info = SocketInfo.touch(socket_info) + :gun.ws_send(conn_pid, {:text, data}) + {:noreply, socket_info} + end + + def handle_info({:gun_ws, _, _, :pong}, state) do + {:noreply, state, :hibernate} + end + + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}") + {:noreply, state} + end + + def terminate(reason, state) do + Logger.debug( + "#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}" + ) + + {:ok, state} + end + + def initiate_connection(uri) do + ws_uri = + uri + |> SocketInfo.origin() + |> FedSockets.uri_for_origin() + + %{host: host, port: port, path: path} = URI.parse(ws_uri) + + with {:ok, conn_pid} <- :gun.open(to_charlist(host), port), + {:ok, _} <- :gun.await_up(conn_pid), + reference <- :gun.get(conn_pid, to_charlist(path)), + {:response, :fin, 204, _} <- :gun.await(conn_pid, reference), + headers <- build_headers(uri), + ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do + receive do + {:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} -> + {:ok, ws_uri, conn_pid} + after + 15_000 -> + Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}") + {:error, :timeout} + end + else + {:response, :nofin, 404, _} -> + {:error, :fedsockets_not_supported} + + e -> + Logger.debug("Fedsocket error connecting to #{inspect(uri)}") + {:error, e} + end + end + + defp build_headers(uri) do + host_for_sig = uri |> URI.parse() |> host_signature() + + shake = FedSocket.shake() + digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64()) + date = Pleroma.Signature.signed_date() + shake_size = byte_size(shake) + + signature_opts = %{ + "(request-target)": shake, + "content-length": to_charlist("#{shake_size}"), + date: date, + digest: digest, + host: host_for_sig + } + + signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts) + + [ + {'signature', to_charlist(signature)}, + {'date', date}, + {'digest', to_charlist(digest)}, + {'content-length', to_charlist("#{shake_size}")}, + {to_charlist("(request-target)"), to_charlist(shake)} + ] + end + + defp host_signature(%{host: host, scheme: scheme, port: port}) do + if port == URI.default_port(scheme) do + host + else + "#{host}:#{port}" + end + end +end diff --git a/lib/pleroma/web/fed_sockets/socket_info.ex b/lib/pleroma/web/fed_sockets/socket_info.ex new file mode 100644 index 000000000..d6fdffe1a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/socket_info.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.SocketInfo do + defstruct origin: nil, + pid: nil, + conn_pid: nil, + state: :default, + connected_until: nil + + alias Pleroma.Web.FedSockets.SocketInfo + @default_connection_duration 15 * 60 * 1000 + + def build(uri, conn_pid \\ nil) do + uri + |> build_origin() + |> build_pids(conn_pid) + |> touch() + end + + def touch(%SocketInfo{} = socket_info), + do: %{socket_info | connected_until: new_ttl()} + + def connect(%SocketInfo{} = socket_info), + do: %{socket_info | state: :connected} + + def expired?(%{connected_until: connected_until}), + do: connected_until < :erlang.monotonic_time(:millisecond) + + def origin(uri), + do: build_origin(uri).origin + + defp build_pids(socket_info, conn_pid), + do: struct(socket_info, pid: self(), conn_pid: conn_pid) + + defp build_origin(uri) when is_binary(uri), + do: uri |> URI.parse() |> build_origin + + defp build_origin(%{host: host, port: nil, scheme: scheme}), + do: build_origin(%{host: host, port: URI.default_port(scheme)}) + + defp build_origin(%{host: host, port: port}), + do: %SocketInfo{origin: "#{host}:#{port}"} + + defp new_ttl do + connection_duration = + Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration) + + :erlang.monotonic_time(:millisecond) + connection_duration + end +end diff --git a/lib/pleroma/web/fed_sockets/supervisor.ex b/lib/pleroma/web/fed_sockets/supervisor.ex new file mode 100644 index 000000000..a5f4bebfb --- /dev/null +++ b/lib/pleroma/web/fed_sockets/supervisor.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.Supervisor do + use Supervisor + import Cachex.Spec + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(args) do + children = [ + build_cache(:fed_socket_fetches, args), + build_cache(:fed_socket_rejections, args), + {Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]} + ] + + opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor] + Supervisor.init(children, opts) + end + + defp build_cache(name, args) do + opts = get_opts(name, args) + + %{ + id: String.to_atom("#{name}_cache"), + start: {Cachex, :start_link, [name, opts]}, + type: :worker + } + end + + defp get_opts(cache_name, args) + when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do + default = get_opts_or_config(args, cache_name, :default, 15_000) + interval = get_opts_or_config(args, cache_name, :interval, 3_000) + lazy = get_opts_or_config(args, cache_name, :lazy, false) + + [expiration: expiration(default: default, interval: interval, lazy: lazy)] + end + + defp get_opts(name, args) do + Keyword.get(args, name, []) + end + + defp get_opts_or_config(args, name, key, default) do + args + |> Keyword.get(name, []) + |> Keyword.get(key) + |> case do + nil -> + Pleroma.Config.get([:fed_sockets, name, key], default) + + value -> + value + end + end +end -- cgit v1.2.3 From 26c571df339ebfdadf760f75871b9343131a2d81 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 18 Sep 2020 16:09:37 +0300 Subject: FedSockets: fix log spam on cached rejects --- lib/pleroma/web/fed_sockets/fed_sockets.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/fed_sockets/fed_sockets.ex b/lib/pleroma/web/fed_sockets/fed_sockets.ex index 035d54796..1fd5899c8 100644 --- a/lib/pleroma/web/fed_sockets/fed_sockets.ex +++ b/lib/pleroma/web/fed_sockets/fed_sockets.ex @@ -104,6 +104,9 @@ defmodule Pleroma.Web.FedSockets do Logger.debug("fedsocket found in cache - #{inspect(address)}") {:ok, socket} + {:cache, {:error, :rejected} = e} -> + e + {:connect, {:error, _host}} -> Logger.debug("set host rejected for - #{inspect(address)}") FedRegistry.set_host_rejected(address) -- cgit v1.2.3 From 26859c549c58901bbcc9dde009fd1cb81f6eb292 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 18 Sep 2020 17:49:15 -0500 Subject: Add user agent to fedsocket requests --- lib/pleroma/web/fed_sockets/outgoing_handler.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex index 6ddef17fe..3ff4a85e3 100644 --- a/lib/pleroma/web/fed_sockets/outgoing_handler.ex +++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.FedSockets.OutgoingHandler do require Logger + alias Pleroma.Application alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.FedSockets alias Pleroma.Web.FedSockets.FedRegistry @@ -87,7 +88,10 @@ defmodule Pleroma.Web.FedSockets.OutgoingHandler do with {:ok, conn_pid} <- :gun.open(to_charlist(host), port), {:ok, _} <- :gun.await_up(conn_pid), - reference <- :gun.get(conn_pid, to_charlist(path)), + reference <- + :gun.get(conn_pid, to_charlist(path), [ + {'user-agent', to_charlist(Application.user_agent())} + ]), {:response, :fin, 204, _} <- :gun.await(conn_pid, reference), headers <- build_headers(uri), ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do @@ -132,7 +136,8 @@ defmodule Pleroma.Web.FedSockets.OutgoingHandler do {'date', date}, {'digest', to_charlist(digest)}, {'content-length', to_charlist("#{shake_size}")}, - {to_charlist("(request-target)"), to_charlist(shake)} + {to_charlist("(request-target)"), to_charlist(shake)}, + {'user-agent', to_charlist(Application.user_agent())} ] end -- cgit v1.2.3 From 51116b539201eb99fa89cabc88a08f92c67bfeca Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 19 Sep 2020 00:50:00 +0300 Subject: OpenAPI: fix various errors pointed out by editor.swagger.io --- lib/pleroma/web/api_spec/helpers.ex | 6 +++++- lib/pleroma/web/api_spec/operations/account_operation.ex | 7 +++++-- lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex | 2 +- lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex | 2 +- lib/pleroma/web/api_spec/operations/list_operation.ex | 3 +-- lib/pleroma/web/api_spec/schemas/chat_message.ex | 3 ++- lib/pleroma/web/api_spec/schemas/scheduled_status.ex | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 2a7f1a706..34de2ed57 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -72,7 +72,11 @@ defmodule Pleroma.Web.ApiSpec.Helpers do end def empty_array_response do - Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + Operation.response("Empty array", "application/json", %Schema{ + type: :array, + items: %Schema{type: :object, example: %{}}, + example: [] + }) end def no_content_response do diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index aaebc9b5c..d90ddb787 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -372,6 +372,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do tags: ["accounts"], summary: "Identity proofs", operationId: "AccountController.identity_proofs", + # Validators complains about unused path params otherwise + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + ], description: "Not implemented", responses: %{ 200 => empty_array_response() @@ -469,7 +473,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do identifier: %Schema{type: :string}, message: %Schema{type: :string} }, - required: [], # Note: example of successful registration with failed login response: # example: %{ # "identifier" => "missing_confirmed_email", @@ -530,7 +533,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do nullable: true, oneOf: [ %Schema{type: :array, items: attribute_field()}, - %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}} + %Schema{type: :object, additionalProperties: attribute_field()} ] }, # NOTE: `source` field is not supported diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index 2f812ac77..5ff263ceb 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -69,7 +69,7 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do type: :object, properties: %{ category: %Schema{type: :string}, - tags: %Schema{type: :array} + tags: %Schema{type: :array, items: %Schema{type: :string}} } } ], diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index 1a49fece0..745d41f88 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do parameters: [ Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji", - required: false + required: nil ) ], security: [%{"oAuth" => ["read:statuses"]}], diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index 15039052e..f6e73968a 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -187,8 +187,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do type: :object, properties: %{ account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID} - }, - required: required && [:account_ids] + } }, required: required ) diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex index bbf2a4427..9d2799618 100644 --- a/lib/pleroma/web/api_spec/schemas/chat_message.ex +++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Emoji require OpenApiSpex @@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do chat_id: %Schema{type: :string}, content: %Schema{type: :string, nullable: true}, created_at: %Schema{type: :string, format: :"date-time"}, - emojis: %Schema{type: :array}, + emojis: %Schema{type: :array, items: Emoji}, attachment: %Schema{type: :object, nullable: true}, card: %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index 0520d0848..addefa9d3 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -27,9 +27,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, sensitive: %Schema{type: :boolean, nullable: true}, spoiler_text: %Schema{type: :string, nullable: true}, - visibility: %Schema{type: VisibilityScope, nullable: true}, + visibility: %Schema{allOf: [VisibilityScope], nullable: true}, scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, - poll: %Schema{type: Poll, nullable: true}, + poll: %Schema{allOf: [Poll], nullable: true}, in_reply_to_id: %Schema{type: :string, nullable: true} } } -- cgit v1.2.3 From 4b12e071ac7ea87c91f6192cc0da90c32e846ca4 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 19 Sep 2020 00:50:38 +0300 Subject: OpenAPI: make it possible to generate a spec without starting the app Needed for api docs generation on pleroma.social that will come later --- lib/pleroma/web/api_spec.ex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 79fd5f871..93a5273e3 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -13,10 +13,15 @@ defmodule Pleroma.Web.ApiSpec do @impl OpenApi def spec do %OpenApi{ - servers: [ - # Populate the Server info from a phoenix endpoint - OpenApiSpex.Server.from_endpoint(Endpoint) - ], + servers: + if Phoenix.Endpoint.server?(:pleroma, Endpoint) do + [ + # Populate the Server info from a phoenix endpoint + OpenApiSpex.Server.from_endpoint(Endpoint) + ] + else + [] + end, info: %OpenApiSpex.Info{ title: "Pleroma", description: Application.spec(:pleroma, :description) |> to_string(), -- cgit v1.2.3 From 60b025b782eb27b86a791451149b6690431371dc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 19 Sep 2020 19:16:55 +0300 Subject: [#2074] OAuth scope checking in Streaming API. --- lib/pleroma/plugs/oauth_scopes_plug.ex | 2 +- lib/pleroma/web/mastodon_api/websocket_handler.ex | 10 ++-- lib/pleroma/web/streamer/streamer.ex | 70 +++++++++++++++++------ 3 files changed, 59 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index efc25b79f..b1a736d78 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -53,7 +53,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do |> assign(:token, nil) end - @doc "Filters descendants of supported scopes" + @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" def filter_descendants(scopes, supported_scopes) do Enum.filter( scopes, diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index cf923ded8..439cdd716 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -23,8 +23,8 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), access_token <- Map.get(params, "access_token"), - {:ok, user} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do + {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), + {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do req = if sec_websocket do :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) @@ -117,7 +117,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do # Public streams without authentication. defp authenticate_request(nil, nil) do - {:ok, nil} + {:ok, nil, nil} end # Authenticated streams. @@ -125,9 +125,9 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do token = access_token || sec_websocket with true <- is_bitstring(token), - %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), user = %User{} <- User.get_cached_by_id(user_id) do - {:ok, user} + {:ok, user, oauth_token} else _ -> {:error, :unauthorized} end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex index d1d70e556..5475f18a6 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer/streamer.ex @@ -11,10 +11,12 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth.Token alias Pleroma.Web.StreamerView @mix_env Mix.env() @@ -26,53 +28,87 @@ defmodule Pleroma.Web.Streamer do @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"] @doc "Expands and authorizes a stream, and registers the process for streaming." - @spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) :: + @spec get_topic_and_add_socket( + stream :: String.t(), + User.t() | nil, + Token.t() | nil, + Map.t() | nil + ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} - def get_topic_and_add_socket(stream, user, params \\ %{}) do - case get_topic(stream, user, params) do + def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do + case get_topic(stream, user, oauth_token, params) do {:ok, topic} -> add_socket(topic, user) error -> error end end @doc "Expand and authorizes a stream" - @spec get_topic(stream :: String.t(), User.t() | nil, Map.t()) :: + @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) :: {:ok, topic :: String.t()} | {:error, :bad_topic} - def get_topic(stream, user, params \\ %{}) + def get_topic(stream, user, oauth_token, params \\ %{}) # Allow all public steams. - def get_topic(stream, _, _) when stream in @public_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do {:ok, stream} end # Allow all hashtags streams. - def get_topic("hashtag", _, %{"tag" => tag}) do + def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do {:ok, "hashtag:" <> tag} end # Expand user streams. - def get_topic(stream, %User{} = user, _) when stream in @user_streams do - {:ok, stream <> ":" <> to_string(user.id)} + def get_topic( + stream, + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + _params + ) + when stream in @user_streams and user_id == token_user_id do + # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) + required_scopes = + if stream == "user:notification" do + ["read:notifications"] + else + ["read:statuses"] + end + + if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do + {:error, :unauthorized} + else + {:ok, stream <> ":" <> to_string(user.id)} + end end - def get_topic(stream, _, _) when stream in @user_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do {:error, :unauthorized} end # List streams. - def get_topic("list", %User{} = user, %{"list" => id}) do - if Pleroma.List.get(id, user) do - {:ok, "list:" <> to_string(id)} - else - {:error, :bad_topic} + def get_topic( + "list", + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + %{"list" => id} + ) + when user_id == token_user_id do + cond do + OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> + {:error, :unauthorized} + + Pleroma.List.get(id, user) -> + {:ok, "list:" <> to_string(id)} + + true -> + {:error, :bad_topic} end end - def get_topic("list", _, _) do + def get_topic("list", _user, _oauth_token, _params) do {:error, :unauthorized} end - def get_topic(_, _, _) do + def get_topic(_stream, _user, _oauth_token, _params) do {:error, :bad_topic} end -- cgit v1.2.3 From f2f0a0260f00e316f62d42e766787b20cc92601a Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 21 Sep 2020 16:08:38 +0200 Subject: ActivityPub: Don't block-filter your own posts We are filtering out replies to people you block, but that should not include your own posts. --- lib/pleroma/web/activity_pub/activity_pub.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 06e8e1a7c..aacd58d03 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -841,7 +841,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do from( [activity, object: o] in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), - where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), + where: + fragment( + "((not (? && ?)) or ? = ?)", + activity.recipients, + ^blocked_ap_ids, + activity.actor, + ^user.ap_id + ), where: fragment( "recipients_contain_blocked_domains(?, ?) = false", -- cgit v1.2.3 From 8afdbcdb1c012c37cde3a3d2bea147f95c5111b0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 21 Sep 2020 10:13:41 -0500 Subject: Force HTTP for fedsockets --- lib/pleroma/web/fed_sockets/outgoing_handler.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex index 3ff4a85e3..e235a7c43 100644 --- a/lib/pleroma/web/fed_sockets/outgoing_handler.ex +++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Web.FedSockets.OutgoingHandler do %{host: host, port: port, path: path} = URI.parse(ws_uri) - with {:ok, conn_pid} <- :gun.open(to_charlist(host), port), + with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}), {:ok, _} <- :gun.await_up(conn_pid), reference <- :gun.get(conn_pid, to_charlist(path), [ -- cgit v1.2.3 From 8b6ac45f3cb5a3c1ac16e766a6f503ee491bc310 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 21 Sep 2020 13:28:02 -0500 Subject: Allow emails to be sent again, #2101 #2172 https://github.com/swoosh/swoosh/issues/478#issuecomment-642393707 --- lib/pleroma/emails/mailer.ex | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 8b1bdef75..5108c71c8 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -35,6 +35,11 @@ defmodule Pleroma.Emails.Mailer do def deliver(email, config \\ []) def deliver(email, config) do + # temporary hackney fix until hackney max_connections bug is fixed + # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 + email = + Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + case enabled?() do true -> Swoosh.Mailer.deliver(email, parse_config(config)) false -> {:error, :deliveries_disabled} -- cgit v1.2.3 From 23d5d7cf593550669812527ab12501b2a2adc6eb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 21 Sep 2020 13:56:40 -0500 Subject: Fix #2172 HTTP error on password reset --- lib/pleroma/web/mastodon_api/controllers/auth_controller.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 9f09550e1..57c0be5fe 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization @@ -61,9 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do TwitterAPI.password_reset(nickname_or_email) - conn - |> put_status(:no_content) - |> json("") + json_response(conn, :no_content, "") end defp local_mastodon_root_path(conn) do -- cgit v1.2.3 From e5927e92a641a799f9b4e89466374be3c76c4ef2 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 21 Sep 2020 17:08:49 -0500 Subject: Fix deprecation warning for welcome message --- lib/pleroma/config/deprecation_warnings.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 98c4dc9c8..51ddb2889 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -83,9 +83,9 @@ defmodule Pleroma.Config.DeprecationWarnings do if use_old_config do Logger.error(""" !!!DEPRECATION WARNING!!! - Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace: - \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` - \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` + Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g., + \n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to: + \n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`" """) :error -- cgit v1.2.3 From ee3052a2d8fda37e27f31c8d824ce7ac174b993c Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 22 Sep 2020 14:20:19 +0200 Subject: ActivityPub: Return Announces when filtering by `following`. --- lib/pleroma/web/activity_pub/activity_pub.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index aacd58d03..eb44cffec 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -790,7 +790,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do [activity, object] in query, where: fragment( - "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?", + """ + ?->>'type' != 'Create' -- This isn't a Create + OR ?->>'inReplyTo' is null -- this isn't a reply + OR ? && array_remove(?, ?) -- The recipient is us or one of our friends, + -- unless they are the author (because authors + -- are also part of the recipients). This leads + -- to a bug that self-replies by friends won't + -- show up. + OR ? = ? -- The actor is us + """, + activity.data, object.data, ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)], activity.recipients, -- cgit v1.2.3 From 0e0ece251af9f85d80968df5b43fe49b85f35434 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 22 Sep 2020 16:55:40 +0400 Subject: Filter out internal users by default --- lib/pleroma/user/query.ex | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index d618432ff..193b90d9d 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -47,6 +47,7 @@ defmodule Pleroma.User.Query do is_moderator: boolean(), super_users: boolean(), invisible: boolean(), + internal: boolean(), followers: User.t(), friends: User.t(), recipients_from_activity: [String.t()], @@ -80,7 +81,9 @@ defmodule Pleroma.User.Query do end defp prepare_query(query, criteria) do - Enum.reduce(criteria, query, &compose_query/2) + criteria + |> Map.put_new(:internal, false) + |> Enum.reduce(query, &compose_query/2) end defp compose_query({key, value}, query) @@ -129,14 +132,12 @@ defmodule Pleroma.User.Query do defp compose_query({:active, _}, query) do User.restrict_deactivated(query) - |> where([u], not is_nil(u.nickname)) |> where([u], u.approval_pending == false) end defp compose_query({:legacy_active, _}, query) do query |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info)) - |> where([u], not is_nil(u.nickname)) end defp compose_query({:deactivated, false}, query) do @@ -145,7 +146,6 @@ defmodule Pleroma.User.Query do defp compose_query({:deactivated, true}, query) do where(query, [u], u.deactivated == ^true) - |> where([u], not is_nil(u.nickname)) end defp compose_query({:need_approval, _}, query) do @@ -199,10 +199,15 @@ defmodule Pleroma.User.Query do limit(query, ^limit) end + defp compose_query({:internal, false}, query) do + query + |> where([u], not is_nil(u.nickname)) + |> where([u], not like(u.nickname, "internal.%")) + end + defp compose_query(_unsupported_param, query), do: query defp location_query(query, local) do where(query, [u], u.local == ^local) - |> where([u], not is_nil(u.nickname)) end end -- cgit v1.2.3 From 2fc3b46ee7e8ee8c22d4cf0039bbe9755a0e3beb Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Sep 2020 11:13:36 -0500 Subject: Remove deprecation warning. We were supposed to remove backwards compat for this in 2.1. --- lib/pleroma/config/deprecation_warnings.ex | 26 -------------------------- 1 file changed, 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 51ddb2889..091d02366 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -33,34 +33,8 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - def mrf_user_allowlist do - config = Config.get(:mrf_user_allowlist) - - if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do - rewritten = - Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc -> - Map.put(acc, to_string(k), v) - end) - - Config.put(:mrf_user_allowlist, rewritten) - - Logger.error(""" - !!!DEPRECATION WARNING!!! - As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format. - Pleroma 2.1 will remove support for the old format. Please change your configuration to match this: - - config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} - """) - - :error - else - :ok - end - end - def warn do with :ok <- check_hellthread_threshold(), - :ok <- mrf_user_allowlist(), :ok <- check_old_mrf_config(), :ok <- check_media_proxy_whitelist_config(), :ok <- check_welcome_message_config(), -- cgit v1.2.3 From e2dcf039d24b1606c90cea75ef11c79b7677c209 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 22 Sep 2020 11:15:40 -0500 Subject: Fix gun_pool_options deprecation warning message --- lib/pleroma/config/deprecation_warnings.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 091d02366..4ba6eaa77 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -122,7 +122,7 @@ defmodule Pleroma.Config.DeprecationWarnings do if timeout = pool_config[:await_up_timeout] do Logger.warn(""" !!!DEPRECATION WARNING!!! - Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases. """) Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout)) -- cgit v1.2.3 From 5e86a2809e37100b54e0fc88db79245e13f684aa Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 23 Sep 2020 11:45:32 +0200 Subject: transmogrifier: Drop incoming create early if it already exists --- lib/pleroma/web/activity_pub/transmogrifier.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index aa6a69463..d7dd9fe6b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -515,15 +515,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Create", "object" => %{"type" => objtype}} = data, + %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, _options ) when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do data = Map.put(data, "object", strip_internal_fields(data["object"])) with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), + nil <- Activity.get_create_by_object_ap_id(obj_id), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} + else + %Activity{} = activity -> {:ok, activity} + e -> e end end -- cgit v1.2.3 From 8af8eb5ce79a35e01415763164533c537e017776 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 23 Sep 2020 12:32:47 -0500 Subject: Chase Pleroma.RepoStreamer.chunk_stream -> Pleroma.Repo.chunk_stream --- lib/mix/tasks/pleroma/email.ex | 7 ++----- lib/mix/tasks/pleroma/user.ex | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index c0bef0386..1f543241a 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -31,11 +31,8 @@ defmodule Mix.Tasks.Pleroma.Email do confirmation_pending: true, invisible: false }) - |> Pleroma.RepoStreamer.chunk_stream(500) - |> Stream.each(fn users -> - users - |> Enum.each(fn user -> Pleroma.User.try_send_confirmation_email(user) end) - end) + |> Pleroma.Repo.chunk_stream(500, :batches) + |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1)) |> Stream.run() end end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 8196e34b1..d50205600 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -370,7 +370,7 @@ defmodule Mix.Tasks.Pleroma.User do is_admin: false, invisible: false }) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> User.need_confirmation(user, false) end) @@ -388,7 +388,7 @@ defmodule Mix.Tasks.Pleroma.User do is_admin: false, invisible: false }) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> User.need_confirmation(user, true) end) -- cgit v1.2.3 From 9b6d89ff8c798079f4db18eb2b5c66a7426ecbc5 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 27 Jun 2020 13:43:25 +0300 Subject: support for special chars in pack name --- lib/pleroma/emoji/pack.ex | 5 +++-- .../api_spec/operations/pleroma_emoji_pack_operation.ex | 2 +- lib/pleroma/web/router.ex | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 0b3f8f00b..9901aa832 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -244,7 +244,8 @@ defmodule Pleroma.Emoji.Pack do uri = url |> String.trim() |> URI.parse() with :ok <- validate_shareable_packs_available(uri), - {:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(), + {:ok, remote_pack} <- + uri |> URI.merge("/api/pleroma/emoji/packs/show?name=#{name}") |> http_get(), {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name), {:ok, archive} <- download_archive(url, sha), pack <- copy_as(remote_pack, as || name), @@ -572,7 +573,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, %{ sha: sha, - url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string() + url: URI.merge(uri, "/api/pleroma/emoji/packs/archive?name=#{name}") |> to_string() }} %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index 59548af13..87ee5feb4 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -192,7 +192,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do end defp name_param do - Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) + Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true) end defp url_param do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 42a9db21d..6f591b12f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -234,21 +234,21 @@ defmodule Pleroma.Web.Router do get("/remote", EmojiPackController, :remote) post("/download", EmojiPackController, :download) - post("/:name", EmojiPackController, :create) - patch("/:name", EmojiPackController, :update) - delete("/:name", EmojiPackController, :delete) + post("/create", EmojiPackController, :create) + patch("/update", EmojiPackController, :update) + delete("/delete", EmojiPackController, :delete) - post("/:name/files", EmojiFileController, :create) - patch("/:name/files", EmojiFileController, :update) - delete("/:name/files", EmojiFileController, :delete) + post("/files", EmojiFileController, :add_file) + patch("/files", EmojiFileController, :update_file) + delete("/files", EmojiFileController, :delete_file) end # Pack info / downloading scope "/packs" do pipe_through(:api) get("/", EmojiPackController, :index) - get("/:name", EmojiPackController, :show) - get("/:name/archive", EmojiPackController, :archive) + get("/show", EmojiPackController, :show) + get("/archive", EmojiPackController, :archive) end end -- cgit v1.2.3 From dbbc8016670166c24a29dcc3e2f0d66bb2f4e35f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 27 Jun 2020 14:33:49 +0300 Subject: pagination for remote emoji packs --- lib/pleroma/emoji/pack.ex | 8 ++++---- .../api_spec/operations/pleroma_emoji_pack_operation.ex | 16 +++++++++++++++- .../web/pleroma_api/controllers/emoji_pack_controller.ex | 5 +++-- 3 files changed, 22 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 9901aa832..4420eff5a 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -198,13 +198,13 @@ defmodule Pleroma.Emoji.Pack do end end - @spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()} - def list_remote(url) do - uri = url |> String.trim() |> URI.parse() + @spec list_remote(keyword()) :: {:ok, map()} | {:error, atom()} + def list_remote(opts) do + uri = opts[:url] |> String.trim() |> URI.parse() with :ok <- validate_shareable_packs_available(uri) do uri - |> URI.merge("/api/pleroma/emoji/packs") + |> URI.merge("/api/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}") |> http_get() end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index 87ee5feb4..79f52dcb3 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -19,7 +19,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do tags: ["Emoji Packs"], summary: "Make request to another instance for emoji packs list", security: [%{"oAuth" => ["write"]}], - parameters: [url_param()], + parameters: [ + url_param(), + Operation.parameter( + :page, + :query, + %Schema{type: :integer, default: 1}, + "Page" + ), + Operation.parameter( + :page_size, + :query, + %Schema{type: :integer, default: 30}, + "Number of emoji to return" + ) + ], operationId: "PleromaAPI.EmojiPackController.remote", responses: %{ 200 => emoji_packs_response(), diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index e3969fee1..6696f8b92 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -23,8 +23,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation - def remote(conn, %{url: url}) do - with {:ok, packs} <- Pack.list_remote(url) do + def remote(conn, params) do + with {:ok, packs} <- + Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do json(conn, packs) else {:error, :not_shareable} -> -- cgit v1.2.3 From 8c6ec4c111081b34f68363ce20423e2f338fa2dd Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sun, 20 Sep 2020 09:51:36 +0300 Subject: pack routes change --- lib/pleroma/emoji/pack.ex | 4 ++-- lib/pleroma/web/router.ex | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 4420eff5a..8f1989ada 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -245,7 +245,7 @@ defmodule Pleroma.Emoji.Pack do with :ok <- validate_shareable_packs_available(uri), {:ok, remote_pack} <- - uri |> URI.merge("/api/pleroma/emoji/packs/show?name=#{name}") |> http_get(), + uri |> URI.merge("/api/pleroma/emoji/pack?name=#{name}") |> http_get(), {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name), {:ok, archive} <- download_archive(url, sha), pack <- copy_as(remote_pack, as || name), @@ -524,7 +524,7 @@ defmodule Pleroma.Emoji.Pack do defp http_get(%URI{} = url), do: url |> to_string() |> http_get() defp http_get(url) do - with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], pool: :default) do Jason.decode(body) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6f591b12f..707d5e1c4 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -226,6 +226,20 @@ defmodule Pleroma.Web.Router do end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do + scope "/pack" do + pipe_through(:admin_api) + + post("/", EmojiPackController, :create) + patch("/", EmojiPackController, :update) + delete("/", EmojiPackController, :delete) + end + + scope "/pack" do + pipe_through(:api) + + get("/", EmojiPackController, :show) + end + # Modifying packs scope "/packs" do pipe_through(:admin_api) @@ -246,8 +260,8 @@ defmodule Pleroma.Web.Router do # Pack info / downloading scope "/packs" do pipe_through(:api) + get("/", EmojiPackController, :index) - get("/show", EmojiPackController, :show) get("/archive", EmojiPackController, :archive) end end -- cgit v1.2.3 From 5d7ec00bedc61e8899941c374604ae5854c62f4c Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 24 Sep 2020 09:42:30 +0300 Subject: fixes after rebase --- .../web/api_spec/operations/pleroma_emoji_file_operation.ex | 2 +- lib/pleroma/web/router.ex | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index efbfce75f..a56641426 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -126,7 +126,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do end defp name_param do - Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) + Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true) end defp files_object do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 707d5e1c4..e22b31b4c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -248,13 +248,9 @@ defmodule Pleroma.Web.Router do get("/remote", EmojiPackController, :remote) post("/download", EmojiPackController, :download) - post("/create", EmojiPackController, :create) - patch("/update", EmojiPackController, :update) - delete("/delete", EmojiPackController, :delete) - - post("/files", EmojiFileController, :add_file) - patch("/files", EmojiFileController, :update_file) - delete("/files", EmojiFileController, :delete_file) + post("/files", EmojiFileController, :create) + patch("/files", EmojiFileController, :update) + delete("/files", EmojiFileController, :delete) end # Pack info / downloading -- cgit v1.2.3 From d0078bc40406939cb584847c90b00aad006812e4 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 24 Sep 2020 15:54:55 +0200 Subject: User Search: Boost resolved results and exact ap_id matches. --- lib/pleroma/user/search.ex | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index b8c648672..408295e0c 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -19,11 +19,21 @@ defmodule Pleroma.User.Search do query_string = format_query(query_string) - maybe_resolve(resolve, for_user, query_string) + # If this returns anything, it should bounce to the top + maybe_resolved = maybe_resolve(resolve, for_user, query_string) + maybe_ap_id_match = User.get_cached_by_ap_id(query_string) + + top_user_ids = + case {maybe_resolved, maybe_ap_id_match} do + {{:ok, %User{} = user}, %User{} = other_user} -> [user.id, other_user.id] + {{:ok, %User{} = user}, _} -> [user.id] + {_, %User{} = user} -> [user.id] + _ -> [] + end results = query_string - |> search_query(for_user, following) + |> search_query(for_user, following, top_user_ids) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) results @@ -47,7 +57,7 @@ defmodule Pleroma.User.Search do end end - defp search_query(query_string, for_user, following) do + defp search_query(query_string, for_user, following, top_user_ids) do for_user |> base_query(following) |> filter_blocked_user(for_user) @@ -56,13 +66,20 @@ defmodule Pleroma.User.Search do |> filter_internal_users() |> filter_blocked_domains(for_user) |> fts_search(query_string) + |> select_top_users(top_user_ids) |> trigram_rank(query_string) - |> boost_search_rank(for_user) + |> boost_search_rank(for_user, top_user_ids) |> subquery() |> order_by(desc: :search_rank) |> maybe_restrict_local(for_user) end + defp select_top_users(query, top_user_ids) do + from(u in query, + or_where: u.id in ^top_user_ids + ) + end + defp fts_search(query, query_string) do query_string = to_tsquery(query_string) @@ -180,7 +197,7 @@ defmodule Pleroma.User.Search do defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) - defp boost_search_rank(query, %User{} = for_user) do + defp boost_search_rank(query, %User{} = for_user, top_user_ids) do friends_ids = User.get_friends_ids(for_user) followers_ids = User.get_followers_ids(for_user) @@ -192,6 +209,7 @@ defmodule Pleroma.User.Search do CASE WHEN (?) THEN (?) * 1.5 WHEN (?) THEN (?) * 1.3 WHEN (?) THEN (?) * 1.1 + WHEN (?) THEN 9001 ELSE (?) END """, u.id in ^friends_ids and u.id in ^followers_ids, @@ -200,11 +218,26 @@ defmodule Pleroma.User.Search do u.search_rank, u.id in ^followers_ids, u.search_rank, + u.id in ^top_user_ids, u.search_rank ) } ) end - defp boost_search_rank(query, _for_user), do: query + defp boost_search_rank(query, _for_user, top_user_ids) do + from(u in subquery(query), + select_merge: %{ + search_rank: + fragment( + """ + CASE WHEN (?) THEN 9001 + ELSE (?) END + """, + u.id in ^top_user_ids, + u.search_rank + ) + } + ) + end end -- cgit v1.2.3 From 935ef21b0285975b08037827a33f32bfcbbff951 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 24 Sep 2020 16:47:34 -0500 Subject: Use the import --- lib/mix/tasks/pleroma/email.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index 1f543241a..0e4c87598 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -6,7 +6,7 @@ defmodule Mix.Tasks.Pleroma.Email do @moduledoc File.read!("docs/administration/CLI_tasks/email.md") def run(["test" | args]) do - Mix.Pleroma.start_pleroma() + start_pleroma() {options, [], []} = OptionParser.parse( -- cgit v1.2.3 From e33360fdb958708661a5bda415b9f06b5e1290d5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 24 Sep 2020 18:23:47 -0500 Subject: Cannot use batches with User.try_send_confirmation_email/1 --- lib/mix/tasks/pleroma/email.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index 0e4c87598..9e989ed46 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -31,7 +31,7 @@ defmodule Mix.Tasks.Pleroma.Email do confirmation_pending: true, invisible: false }) - |> Pleroma.Repo.chunk_stream(500, :batches) + |> Pleroma.Repo.chunk_stream(500) |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1)) |> Stream.run() end -- cgit v1.2.3 From 81faf540b30d0ecf75779e7a5c16f42b7218cec8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 24 Sep 2020 18:35:20 -0500 Subject: Add some user feedback --- lib/mix/tasks/pleroma/email.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index 9e989ed46..9972cb988 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -25,6 +25,8 @@ defmodule Mix.Tasks.Pleroma.Email do def run(["resend_confirmation_emails"]) do start_pleroma() + shell_info("Sending emails to all unconfirmed users") + Pleroma.User.Query.build(%{ local: true, deactivated: false, -- cgit v1.2.3 From a8c17ea25a79491328345f5834397eb6821a77f1 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 25 Sep 2020 08:46:14 +0200 Subject: User Search: Also find user by uri --- lib/pleroma/user/search.ex | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 408295e0c..d747bfa52 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -5,6 +5,7 @@ defmodule Pleroma.User.Search do alias Pleroma.Pagination alias Pleroma.User + alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType import Ecto.Query @limit 20 @@ -21,15 +22,12 @@ defmodule Pleroma.User.Search do # If this returns anything, it should bounce to the top maybe_resolved = maybe_resolve(resolve, for_user, query_string) - maybe_ap_id_match = User.get_cached_by_ap_id(query_string) top_user_ids = - case {maybe_resolved, maybe_ap_id_match} do - {{:ok, %User{} = user}, %User{} = other_user} -> [user.id, other_user.id] - {{:ok, %User{} = user}, _} -> [user.id] - {_, %User{} = user} -> [user.id] - _ -> [] - end + [] + |> maybe_add_resolved(maybe_resolved) + |> maybe_add_ap_id_match(query_string) + |> maybe_add_uri_match(query_string) results = query_string @@ -39,6 +37,29 @@ defmodule Pleroma.User.Search do results end + defp maybe_add_resolved(list, {:ok, %User{} = user}) do + [user.id | list] + end + + defp maybe_add_resolved(list, _), do: list + + defp maybe_add_ap_id_match(list, query) do + if user = User.get_cached_by_ap_id(query) do + [user.id | list] + else + list + end + end + + defp maybe_add_uri_match(list, query) do + with {:ok, query} <- UriType.cast(query), + %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do + [user.id | list] + else + _ -> list + end + end + defp format_query(query_string) do # Strip the beginning @ off if there is a query query_string = String.trim_leading(query_string, "@") -- cgit v1.2.3 From 05b5241314182c5aab2907e27d4c5f46d7617f56 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 25 Sep 2020 09:12:48 +0200 Subject: Linter fixes --- lib/pleroma/user/search.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index d747bfa52..03f2c552f 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -3,9 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User.Search do + alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType alias Pleroma.Pagination alias Pleroma.User - alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType + import Ecto.Query @limit 20 -- cgit v1.2.3 From 8b84ca4901c378d734cd87ae3e4bf72c508a84bf Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 25 Sep 2020 10:37:59 -0500 Subject: Simplify the value comparison --- lib/mix/tasks/pleroma/user.ex | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index d50205600..e06262804 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -455,11 +455,7 @@ defmodule Mix.Tasks.Pleroma.User do end defp set_confirmed(user, value) do - {:ok, user} = - case value do - true -> User.need_confirmation(user, false) - false -> User.need_confirmation(user, true) - end + {:ok, user} = User.need_confirmation(user, !value) shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}") user -- cgit v1.2.3 From 4e4f77108207157a49a627edb03951e2f15b62f1 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 26 Sep 2020 19:32:16 +0300 Subject: Adjusted MediaProxyControllerTest to gracefully fail on missing dependencies. Installation docs update. Added ffmpeg/imagemagick checks to launch checks (if media preview proxy is enabled). Added documentation on installing optional media / graphics packages (imagemagick, ffmpeg, exiftool). --- lib/pleroma/application.ex | 18 +++++++++++++++++- lib/pleroma/helpers/media_helper.ex | 12 ++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 00ec79a2a..d7d8e423e 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -268,7 +268,8 @@ defmodule Pleroma.Application do with true <- filter in filters, false <- Pleroma.Utils.command_available?(command_required) do Logger.error( - "#{filter} is specified in list of Pleroma.Upload filters, but the #{command_required} command is not found" + "#{filter} is specified in list of Pleroma.Upload filters, but the " <> + "#{command_required} command is not found" ) end end @@ -276,5 +277,20 @@ defmodule Pleroma.Application do check_filter.(Pleroma.Upload.Filters.Exiftool, "exiftool") check_filter.(Pleroma.Upload.Filters.Mogrify, "mogrify") check_filter.(Pleroma.Upload.Filters.Mogrifun, "mogrify") + + with true <- Config.get([:media_preview_proxy, :enabled]), + missing_graphics_tools = Pleroma.Helpers.MediaHelper.missing_dependencies(), + [] <- missing_graphics_tools do + :noop + else + false -> + :noop + + missing_graphics_tools -> + Logger.error( + "The following dependencies required by Media preview proxy " <> + "(which is currently enabled) are not installed: #{inspect(missing_graphics_tools)}" + ) + end end end diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index b6f35a24b..6b799173e 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -9,6 +9,18 @@ defmodule Pleroma.Helpers.MediaHelper do alias Pleroma.HTTP + require Logger + + def missing_dependencies do + Enum.reduce([imagemagick: "convert", ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> + if Pleroma.Utils.command_available?(executable) do + acc + else + [sym | acc] + end + end) + end + def image_resize(url, options) do with executable when is_binary(executable) <- System.find_executable("convert"), {:ok, args} <- prepare_image_resize_args(options), -- cgit v1.2.3 From de993b856bc2145e7c4aaa47767c7edc826798c7 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 28 Sep 2020 09:16:42 +0300 Subject: added `force` option to the unfollow operation --- lib/mix/tasks/pleroma/relay.ex | 13 +++++++++++-- lib/pleroma/user.ex | 4 +--- lib/pleroma/web/activity_pub/relay.ex | 20 ++++++++++++++++---- .../web/admin_api/controllers/relay_controller.ex | 16 ++++------------ .../web/api_spec/operations/admin/relay_operation.ex | 12 +++++++++++- 5 files changed, 43 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index a6d8d6c1c..bb808ca47 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -21,10 +21,19 @@ defmodule Mix.Tasks.Pleroma.Relay do end end - def run(["unfollow", target]) do + def run(["unfollow", target | rest]) do start_pleroma() - with {:ok, _activity} <- Relay.unfollow(target) do + {options, [], []} = + OptionParser.parse( + rest, + strict: [force: :boolean], + aliases: [f: :force] + ) + + force = Keyword.get(options, :force, false) + + with {:ok, _activity} <- Relay.unfollow(target, %{force: force}) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 03be61ccf..71ace1c34 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -915,9 +915,7 @@ defmodule Pleroma.User do FollowingRelationship.unfollow(follower, followed) {:ok, followed} = update_follower_count(followed) - {:ok, follower} = - follower - |> update_following_count() + {:ok, follower} = update_following_count(follower) {:ok, follower, followed} diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index b65710a94..6606e1780 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -30,12 +30,16 @@ defmodule Pleroma.Web.ActivityPub.Relay do end end - @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()} - def unfollow(target_instance) do + @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + def unfollow(target_instance, opts \\ %{}) do with %User{} = local_user <- get_actor(), - {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), + {:ok, target_user} <- fetch_target_user(target_instance, opts), {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do - User.unfollow(local_user, target_user) + case target_user.id do + nil -> User.update_following_count(local_user) + _ -> User.unfollow(local_user, target_user) + end + Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}") {:ok, activity} else @@ -43,6 +47,14 @@ defmodule Pleroma.Web.ActivityPub.Relay do end end + defp fetch_target_user(ap_id, opts) do + case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do + {_, {:ok, %User{} = user}} -> {:ok, user} + {true, _} -> {:ok, %User{ap_id: ap_id}} + {_, error} -> error + end + end + @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()} def publish(%Activity{data: %{"type" => "Create"}} = activity) do with %User{} = user <- get_actor(), diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex index 95d06dde7..6c19f09f7 100644 --- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex @@ -33,11 +33,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do with {:ok, _message} <- Relay.follow(target) do - ModerationLog.insert_log(%{ - action: "relay_follow", - actor: admin, - target: target - }) + ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target}) json(conn, %{actor: target, followed_back: target in Relay.following()}) else @@ -48,13 +44,9 @@ defmodule Pleroma.Web.AdminAPI.RelayController do end end - def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do - with {:ok, _message} <- Relay.unfollow(target) do - ModerationLog.insert_log(%{ - action: "relay_unfollow", - actor: admin, - target: target - }) + def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do + with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do + ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target}) json(conn, target) else diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex index e06b2d164..f754bb9f5 100644 --- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do operationId: "AdminAPI.RelayController.unfollow", security: [%{"oAuth" => ["write:follows"]}], parameters: admin_api_params(), - requestBody: request_body("Parameters", relay_url()), + requestBody: request_body("Parameters", relay_unfollow()), responses: %{ 200 => Operation.response("Status", "application/json", %Schema{ @@ -91,4 +91,14 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do } } end + + defp relay_unfollow do + %Schema{ + type: :object, + properties: %{ + relay_url: %Schema{type: :string, format: :uri}, + force: %Schema{type: :boolean, default: false} + } + } + end end -- cgit v1.2.3 From ba7f9459b4798388eb4e441d096302c018354033 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 28 Sep 2020 18:22:59 -0500 Subject: Revert Rich Media censorship for sensitive statuses The #NSFW hashtag test was broken anyway. --- lib/pleroma/web/rich_media/helpers.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index d7a19df4a..d67b594b5 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -57,7 +57,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do def fetch_data_for_object(object) do with true <- Config.get([:rich_media, :enabled]), - false <- object.data["sensitive"] || false, {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), :ok <- validate_page_url(page_url), -- cgit v1.2.3 From 7d5c3883acafc2c84f65f38dd639d4999f14215a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 29 Sep 2020 16:28:06 +0300 Subject: [#3031] Refactoring: moved system commands checks to ApplicationRequirements. --- lib/pleroma/application.ex | 34 ---------------- lib/pleroma/application_requirements.ex | 72 ++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 39 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index d7d8e423e..e73d89350 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -56,7 +56,6 @@ defmodule Pleroma.Application do Pleroma.ApplicationRequirements.verify!() setup_instrumenters() load_custom_modules() - check_system_commands() Pleroma.Docs.JSON.compile() adapter = Application.get_env(:tesla, :adapter) @@ -260,37 +259,4 @@ defmodule Pleroma.Application do end defp http_children(_, _), do: [] - - defp check_system_commands do - filters = Config.get([Pleroma.Upload, :filters]) - - check_filter = fn filter, command_required -> - with true <- filter in filters, - false <- Pleroma.Utils.command_available?(command_required) do - Logger.error( - "#{filter} is specified in list of Pleroma.Upload filters, but the " <> - "#{command_required} command is not found" - ) - end - end - - check_filter.(Pleroma.Upload.Filters.Exiftool, "exiftool") - check_filter.(Pleroma.Upload.Filters.Mogrify, "mogrify") - check_filter.(Pleroma.Upload.Filters.Mogrifun, "mogrify") - - with true <- Config.get([:media_preview_proxy, :enabled]), - missing_graphics_tools = Pleroma.Helpers.MediaHelper.missing_dependencies(), - [] <- missing_graphics_tools do - :noop - else - false -> - :noop - - missing_graphics_tools -> - Logger.error( - "The following dependencies required by Media preview proxy " <> - "(which is currently enabled) are not installed: #{inspect(missing_graphics_tools)}" - ) - end - end end diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 16f62b6f5..b977257a3 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -9,6 +9,9 @@ defmodule Pleroma.ApplicationRequirements do defmodule VerifyError, do: defexception([:message]) + alias Pleroma.Config + alias Pleroma.Helpers.MediaHelper + import Ecto.Query require Logger @@ -16,7 +19,8 @@ defmodule Pleroma.ApplicationRequirements do @spec verify!() :: :ok | VerifyError.t() def verify! do :ok - |> check_confirmation_accounts! + |> check_system_commands!() + |> check_confirmation_accounts!() |> check_migrations_applied!() |> check_welcome_message_config!() |> check_rum!() @@ -48,7 +52,9 @@ defmodule Pleroma.ApplicationRequirements do if Pleroma.Config.get([:instance, :account_activation_required]) && not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do Logger.error( - "Account activation enabled, but no Mailer settings enabled.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer." + "Account activation enabled, but no Mailer settings enabled.\n" <> + "Please set config :pleroma, :instance, account_activation_required: false\n" <> + "Otherwise setup and enable Mailer." ) {:error, @@ -81,7 +87,9 @@ defmodule Pleroma.ApplicationRequirements do Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end) Logger.error( - "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true" + "The following migrations were not applied:\n#{down_migrations_text}" <> + "If you want to start Pleroma anyway, set\n" <> + "config :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true" ) {:error, "Unapplied Migrations detected"} @@ -124,14 +132,22 @@ defmodule Pleroma.ApplicationRequirements do case {setting, migrate} do {true, false} -> Logger.error( - "Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`" + "Use `RUM` index is enabled, but were not applied migrations for it.\n" <> + "If you want to start Pleroma anyway, set\n" <> + "config :pleroma, :database, rum_enabled: false\n" <> + "Otherwise apply the following migrations:\n" <> + "`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`" ) {:error, "Unapplied RUM Migrations detected"} {false, true} -> Logger.error( - "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`" + "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\n" <> + "If you want to use `RUM`, set\n" <> + "config :pleroma, :database, rum_enabled: true\n" <> + "Otherwise roll `RUM` migrations back.\n" <> + "`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`" ) {:error, "RUM Migrations detected"} @@ -140,4 +156,50 @@ defmodule Pleroma.ApplicationRequirements do :ok end end + + defp check_system_commands!(:ok) do + filter_commands_statuses = [ + check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"), + check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"), + check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify") + ] + + preview_proxy_commands_status = + if !Config.get([:media_preview_proxy, :enabled]) or + MediaHelper.missing_dependencies() == [] do + true + else + Logger.error( + "The following dependencies required by Media preview proxy " <> + "(which is currently enabled) are not installed: " <> + inspect(MediaHelper.missing_dependencies()) + ) + + false + end + + if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do + :ok + else + {:error, + "System commands missing. Check logs and see `docs/installation` for more details."} + end + end + + defp check_system_commands!(result), do: result + + defp check_filter(filter, command_required) do + filters = Config.get([Pleroma.Upload, :filters]) + + if filter in filters and not Pleroma.Utils.command_available?(command_required) do + Logger.error( + "#{filter} is specified in list of Pleroma.Upload filters, but the " <> + "#{command_required} command is not found" + ) + + false + else + true + end + end end -- cgit v1.2.3 From b3a9ba09ec5867d240c3769ae4c3fbf598f68d92 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 29 Sep 2020 12:16:15 -0500 Subject: More robust expires_at timestamp processing --- lib/pleroma/user.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 71ace1c34..09ea80793 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2291,7 +2291,9 @@ defmodule Pleroma.User do # if pinned activity was scheduled for deletion, we reschedule it for deletion if data["expires_at"] do - {:ok, expires_at, _} = DateTime.from_iso8601(data["expires_at"]) + # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation + {:ok, expires_at} = + data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast() Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ activity_id: id, -- cgit v1.2.3 From 006b62fd12adadbf698419990ab13bf6f1e901b2 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 29 Sep 2020 21:49:04 +0300 Subject: OpenAPI CastAndValidate: filter out empty params Closes #2198 --- lib/pleroma/web/api_spec/cast_and_validate.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index fbfc27d6f..6d1a7ebbc 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -115,6 +115,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do %{reason: :unexpected_field, name: name, path: [name]}, params -> Map.delete(params, name) + # Filter out empty params + %{reason: :invalid_type, path: [name_atom], value: ""}, params -> + Map.delete(params, to_string(name_atom)) + %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() update_in(params, path, &List.delete(&1, value)) -- cgit v1.2.3 From 90fee49c52799a7d6ad890ecc49d146ab6ad8455 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 30 Sep 2020 14:14:41 +0200 Subject: User search: Once again, change uri handling. They can indeed be non-unique. --- lib/pleroma/user/search.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 03f2c552f..35a828008 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -54,8 +54,9 @@ defmodule Pleroma.User.Search do defp maybe_add_uri_match(list, query) do with {:ok, query} <- UriType.cast(query), - %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do - [user.id | list] + q = from(u in User, where: u.uri == ^query, select: u.id), + users = Pleroma.Repo.all(q) do + users ++ list else _ -> list end -- cgit v1.2.3 From cbdaabad345914e7424e614032056ff86e21142f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 1 Oct 2020 13:32:11 +0300 Subject: web push http_client fix --- lib/pleroma/http/web_push.ex | 12 ++++++++++++ lib/pleroma/web/push/impl.ex | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/http/web_push.ex (limited to 'lib') diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex new file mode 100644 index 000000000..78148a12e --- /dev/null +++ b/lib/pleroma/http/web_push.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.WebPush do + @moduledoc false + + def post(url, payload, headers) do + list_headers = Map.to_list(headers) + Pleroma.HTTP.post(url, payload, list_headers) + end +end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 16368485e..da535aa68 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do @types ["Create", "Follow", "Announce", "Like", "Move"] @doc "Performs sending notifications for user subscriptions" - @spec perform(Notification.t()) :: list(any) | :error + @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type} def perform( %{ activity: %{data: %{"type" => activity_type}} = activity, @@ -64,20 +64,20 @@ defmodule Pleroma.Web.Push.Impl do @doc "Push message to web" def push_message(body, sub, api_key, subscription) do case WebPushEncryption.send_web_push(body, sub, api_key) do - {:ok, %{status_code: code}} when 400 <= code and code < 500 -> + {:ok, %{status: code}} when code in 400..499 -> Logger.debug("Removing subscription record") Repo.delete!(subscription) :ok - {:ok, %{status_code: code}} when 200 <= code and code < 300 -> + {:ok, %{status: code}} when code in 200..299 -> :ok - {:ok, %{status_code: code}} -> + {:ok, %{status: code}} -> Logger.error("Web Push Notification failed with code: #{code}") :error - _ -> - Logger.error("Web Push Notification failed with unknown error") + error -> + Logger.error("Web Push Notification failed with #{inspect(error)}") :error end end -- cgit v1.2.3 From 35ee759e74d0737598311d8e4245168f981812d3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 5 Oct 2020 11:48:41 -0500 Subject: Add helper function to convert single IPs into CIDR format if they were not provided that way --- lib/pleroma/plugs/remote_ip.ex | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 0ac9050d0..d1b1f793a 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -47,8 +47,19 @@ defmodule Pleroma.Plugs.RemoteIp do config |> Keyword.get(:proxies, []) |> Enum.concat(reserved) - |> Enum.map(&InetCidr.parse/1) + |> Enum.map(&maybe_add_cidr/1) {headers, proxies} end + + defp maybe_add_cidr(proxy) when is_binary(proxy) do + proxy = + cond do + "/" in String.codepoints(proxy) -> proxy + InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32" + InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128" + end + + InetCidr.parse(proxy) + end end -- cgit v1.2.3 From d43d05005ae4e8b0f069111baee867492d4f0c52 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 6 Oct 2020 17:02:46 -0500 Subject: Move hardcoded default configuration into config.exs --- lib/pleroma/plugs/remote_ip.ex | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index d1b1f793a..9487efa5f 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -7,45 +7,28 @@ defmodule Pleroma.Plugs.RemoteIp do This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. """ + alias Pleroma.Config import Plug.Conn @behaviour Plug - @headers ~w[ - x-forwarded-for - ] - - # https://en.wikipedia.org/wiki/Localhost - # https://en.wikipedia.org/wiki/Private_network - @reserved ~w[ - 127.0.0.0/8 - ::1/128 - fc00::/7 - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16 - ] - def init(_), do: nil def call(%{remote_ip: original_remote_ip} = conn, _) do - config = Pleroma.Config.get(__MODULE__, []) - - if Keyword.get(config, :enabled, false) do - %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) + if Config.get([__MODULE__, :enabled]) do + %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts()) assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) else conn end end - defp remote_ip_opts(config) do - headers = config |> Keyword.get(:headers, @headers) |> MapSet.new() - reserved = Keyword.get(config, :reserved, @reserved) + defp remote_ip_opts() do + headers = Config.get([__MODULE__, :headers], []) |> MapSet.new() + reserved = Config.get([__MODULE__, :reserved], []) proxies = - config - |> Keyword.get(:proxies, []) + Config.get([__MODULE__, :proxies], []) |> Enum.concat(reserved) |> Enum.map(&maybe_add_cidr/1) -- cgit v1.2.3 From 7a2ed2fc90dd16a5ef45c4dd44a6e09bba035299 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 6 Oct 2020 17:26:31 -0500 Subject: Credo --- lib/pleroma/plugs/remote_ip.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 9487efa5f..51cc87ad8 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Plugs.RemoteIp do end end - defp remote_ip_opts() do + defp remote_ip_opts do headers = Config.get([__MODULE__, :headers], []) |> MapSet.new() reserved = Config.get([__MODULE__, :reserved], []) -- cgit v1.2.3 From d0eca5b12518b0b98ef53003d60b08a78decf35f Mon Sep 17 00:00:00 2001 From: feld Date: Wed, 7 Oct 2020 19:16:53 +0000 Subject: Apply 2 suggestion(s) to 2 file(s) --- lib/pleroma/plugs/remote_ip.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 51cc87ad8..987022156 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -43,6 +43,6 @@ defmodule Pleroma.Plugs.RemoteIp do InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128" end - InetCidr.parse(proxy) + InetCidr.parse(proxy, true) end end -- cgit v1.2.3 From 3ca98878d27478037233a92f72adb3fbade62035 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 9 Oct 2020 17:08:05 -0500 Subject: Deep link to the user account in AdminFE in account confirmation emails --- lib/pleroma/emails/admin_email.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index c27ad1065..8979db2f8 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Emails.AdminEmail do html_body = """

New account for review: @#{account.nickname}

#{HTML.strip_tags(account.registration_reason)}
- Visit AdminFE + Visit AdminFE """ new() -- cgit v1.2.3 From 6c61ef14c3f48910c52e17c68fce175682717962 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 12 Oct 2020 11:18:39 -0500 Subject: Support enabling upload filters during instance gen --- lib/mix/tasks/pleroma/instance.ex | 64 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 91440b453..fc21ae062 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -33,7 +33,10 @@ defmodule Mix.Tasks.Pleroma.Instance do uploads_dir: :string, static_dir: :string, listen_ip: :string, - listen_port: :string + listen_port: :string, + strip_uploads: :string, + anonymize_uploads: :string, + dedupe_uploads: :string ], aliases: [ o: :output, @@ -158,6 +161,30 @@ defmodule Mix.Tasks.Pleroma.Instance do ) |> Path.expand() + strip_uploads = + get_option( + options, + :strip_uploads, + "Do you want to strip location (GPS) data from uploaded images? (y/n)", + "y" + ) === "y" + + anonymize_uploads = + get_option( + options, + :anonymize_uploads, + "Do you want to anonymize the filenames of uploads? (y/n)", + "n" + ) === "y" + + dedupe_uploads = + get_option( + options, + :dedupe_uploads, + "Do you want to deduplicate uploaded files? (y/n)", + "n" + ) === "y" + Config.put([:instance, :static_dir], static_dir) secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) @@ -188,7 +215,13 @@ defmodule Mix.Tasks.Pleroma.Instance do uploads_dir: uploads_dir, rum_enabled: rum_enabled, listen_ip: listen_ip, - listen_port: listen_port + listen_port: listen_port, + upload_filters: + upload_filters(%{ + strip: strip_uploads, + anonymize: anonymize_uploads, + dedupe: dedupe_uploads + }) ) result_psql = @@ -247,4 +280,31 @@ defmodule Mix.Tasks.Pleroma.Instance do File.write(robots_txt_path, robots_txt) shell_info("Writing #{robots_txt_path}.") end + + defp upload_filters(filters) when is_map(filters) do + enabled_filters = + if filters.strip do + [Pleroma.Upload.Filter.ExifTool] + else + [] + end + + enabled_filters = + if filters.anonymize do + enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename] + else + enabled_filters + end + + enabled_filters = + if filters.dedupe do + enabled_filters ++ [Pleroma.Upload.Filter.Dedupe] + else + enabled_filters + end + + enabled_filters + end + + defp upload_filters(_), do: [] end -- cgit v1.2.3 From 8539e386c3f00537f120487e717ec7b25fe6c572 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 12 Oct 2020 12:00:50 -0500 Subject: Add missing Copyright headers --- lib/mix/tasks/pleroma/count_statuses.ex | 4 ++++ lib/mix/tasks/pleroma/digest.ex | 4 ++++ lib/mix/tasks/pleroma/docs.ex | 4 ++++ lib/mix/tasks/pleroma/email.ex | 4 ++++ lib/mix/tasks/pleroma/notification_settings.ex | 4 ++++ lib/pleroma/config/oban.ex | 4 ++++ lib/pleroma/docs/generator.ex | 4 ++++ lib/pleroma/docs/json.ex | 4 ++++ lib/pleroma/docs/markdown.ex | 4 ++++ lib/pleroma/emoji/pack.ex | 4 ++++ lib/pleroma/gun/connection_pool.ex | 4 ++++ lib/pleroma/gun/connection_pool/reclaimer.ex | 4 ++++ lib/pleroma/gun/connection_pool/worker.ex | 4 ++++ lib/pleroma/gun/connection_pool/worker_supervisor.ex | 4 ++++ lib/pleroma/http/adapter_helper/default.ex | 4 ++++ lib/pleroma/http/adapter_helper/hackney.ex | 4 ++++ lib/pleroma/jwt.ex | 4 ++++ lib/pleroma/moderation_log.ex | 4 ++++ lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex | 4 ++++ lib/pleroma/plugs/rate_limiter/supervisor.ex | 4 ++++ lib/pleroma/telemetry/logger.ex | 4 ++++ lib/pleroma/web/activity_pub/builder.ex | 4 ++++ lib/pleroma/web/activity_pub/side_effects.ex | 4 ++++ lib/pleroma/web/mailer/subscription_controller.ex | 4 ++++ lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex | 4 ++++ lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex | 4 ++++ lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex | 4 ++++ lib/pleroma/web/rich_media/parsers/ttl/ttl.ex | 4 ++++ lib/pleroma/web/views/email_view.ex | 4 ++++ lib/pleroma/web/views/mailer/subscription_view.ex | 4 ++++ 30 files changed, 120 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/count_statuses.ex b/lib/mix/tasks/pleroma/count_statuses.ex index e1e8195dd..8761d8f17 100644 --- a/lib/mix/tasks/pleroma/count_statuses.ex +++ b/lib/mix/tasks/pleroma/count_statuses.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.CountStatuses do @shortdoc "Re-counts statuses for all users" diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex index 3595f912d..cac148b88 100644 --- a/lib/mix/tasks/pleroma/digest.ex +++ b/lib/mix/tasks/pleroma/digest.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Digest do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 6088fc71d..ad5c37fc9 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Docs do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index 9972cb988..bc5facc09 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Email do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex index 00f5ba7bf..f99275de1 100644 --- a/lib/mix/tasks/pleroma/notification_settings.ex +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.NotificationSettings do @shortdoc "Enable&Disable privacy option for push notifications" @moduledoc """ diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index 9f601b1a3..8e0351d52 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Config.Oban do require Logger diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index a671a6278..a70f83b73 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.Generator do @callback process(keyword()) :: {:ok, String.t()} diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index feeb4320e..13618b509 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.JSON do @behaviour Pleroma.Docs.Generator @external_resource "config/description.exs" diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index da3f20f43..eac0789a6 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.Markdown do @behaviour Pleroma.Docs.Generator diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 8f1989ada..0670f29f1 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Emoji.Pack do @derive {Jason.Encoder, only: [:files, :pack, :files_count]} defstruct files: %{}, diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex index f34602b73..e322f192a 100644 --- a/lib/pleroma/gun/connection_pool.ex +++ b/lib/pleroma/gun/connection_pool.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool do @registry __MODULE__ diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex index cea800882..241e8b04f 100644 --- a/lib/pleroma/gun/connection_pool/reclaimer.ex +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.Reclaimer do use GenServer, restart: :temporary diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index bf57e9e5f..b71816bed 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.Worker do alias Pleroma.Gun use GenServer, restart: :temporary diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index 39615c956..4c23bcbd9 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do @moduledoc "Supervisor for pool workers. Does not do anything except enforce max connection limit" diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex index e13441316..8567a616b 100644 --- a/lib/pleroma/http/adapter_helper/default.ex +++ b/lib/pleroma/http/adapter_helper/default.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.HTTP.AdapterHelper.Default do alias Pleroma.HTTP.AdapterHelper diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index ef84553c1..ff60513fd 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper diff --git a/lib/pleroma/jwt.ex b/lib/pleroma/jwt.ex index 10102ff5d..faeb77781 100644 --- a/lib/pleroma/jwt.ex +++ b/lib/pleroma/jwt.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.JWT do use Joken.Config diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 47036a6f6..38a863443 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.ModerationLog do use Ecto.Schema diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex index 884268d96..0bf5aadfb 100644 --- a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex +++ b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do use DynamicSupervisor diff --git a/lib/pleroma/plugs/rate_limiter/supervisor.ex b/lib/pleroma/plugs/rate_limiter/supervisor.ex index 9672f7876..ce196df52 100644 --- a/lib/pleroma/plugs/rate_limiter/supervisor.ex +++ b/lib/pleroma/plugs/rate_limiter/supervisor.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Plugs.RateLimiter.Supervisor do use Supervisor diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 197b1d091..003079cf3 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Telemetry.Logger do @moduledoc "Transforms Pleroma telemetry events to logs" diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 9a7b7d9de..298aff6b7 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.Builder do @moduledoc """ This module builds the objects. Meant to be used for creating local objects. diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b9a83a544..2eec0ce86 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.SideEffects do @moduledoc """ This module looks at an inserted object and executes the side effects that it diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex index 478a83518..ace44afd1 100644 --- a/lib/pleroma/web/mailer/subscription_controller.ex +++ b/lib/pleroma/web/mailer/subscription_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Mailer.SubscriptionController do use Pleroma.Web, :controller diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index 71c53df1d..7c0345094 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.PleromaAPI.EmojiFileController do use Pleroma.Web, :controller diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 6696f8b92..a0e5c739a 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.PleromaAPI.EmojiPackController do use Pleroma.Web, :controller diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex index c5aaea2d4..15109d28d 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do @behaviour Pleroma.Web.RichMedia.Parser.TTL diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex index 6b3ec6d30..13511888c 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex +++ b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parser.TTL do @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()} end diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index 6b0fbe61e..bcdee6571 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.EmailView do use Pleroma.Web, :view import Phoenix.HTML diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex index fc3d20816..4562a9d6c 100644 --- a/lib/pleroma/web/views/mailer/subscription_view.ex +++ b/lib/pleroma/web/views/mailer/subscription_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Mailer.SubscriptionView do use Pleroma.Web, :view end -- cgit v1.2.3 From 83ae45b000261d3e03a4b554064350a5ead172c3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Oct 2020 18:49:37 -0500 Subject: Preload `/api/pleroma/frontend_configurations`, fixes #1932 --- lib/pleroma/web/preload/instance.ex | 9 +++++++++ lib/pleroma/web/twitter_api/controllers/util_controller.ex | 6 +----- lib/pleroma/web/twitter_api/views/util_view.ex | 6 ++++++ 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/instance.ex index 50d1f3382..cc6f8cf99 100644 --- a/lib/pleroma/web/preload/instance.ex +++ b/lib/pleroma/web/preload/instance.ex @@ -7,11 +7,13 @@ defmodule Pleroma.Web.Preload.Providers.Instance do alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Nodeinfo.Nodeinfo alias Pleroma.Web.Preload.Providers.Provider + alias Pleroma.Web.TwitterAPI.UtilView @behaviour Provider @instance_url "/api/v1/instance" @panel_url "/instance/panel.html" @nodeinfo_url "/nodeinfo/2.0.json" + @fe_config_url "/api/pleroma/frontend_configurations" @impl Provider def generate_terms(_params) do @@ -19,6 +21,7 @@ defmodule Pleroma.Web.Preload.Providers.Instance do |> build_info_tag() |> build_panel_tag() |> build_nodeinfo_tag() + |> build_fe_config_tag() end defp build_info_tag(acc) do @@ -47,4 +50,10 @@ defmodule Pleroma.Web.Preload.Providers.Instance do Map.put(acc, @nodeinfo_url, nodeinfo_data) end end + + defp build_fe_config_tag(acc) do + fe_data = UtilView.render("frontend_configurations.json", %{}) + + Map.put(acc, @fe_config_url, fe_data) + end end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 70b0fbd54..6d827846d 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -74,11 +74,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def frontend_configurations(conn, _params) do - config = - Config.get(:frontend_configurations, %{}) - |> Enum.into(%{}) - - json(conn, config) + render(conn, "frontend_configurations.json") end def emoji(conn, _params) do diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index d3bdb4f62..98eea1d18 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do use Pleroma.Web, :view import Phoenix.HTML.Form + alias Pleroma.Config alias Pleroma.Web def status_net_config(instance) do @@ -19,4 +20,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do """ end + + def render("frontend_configurations.json", _) do + Config.get(:frontend_configurations, %{}) + |> Enum.into(%{}) + end end -- cgit v1.2.3 From b573711e9c8200ecdd4a722ce1e02b48d3f74cce Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 20 Jun 2020 18:37:44 +0300 Subject: file locations consistency --- lib/credo/check/consistency/file_location.ex | 132 +++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 lib/credo/check/consistency/file_location.ex (limited to 'lib') diff --git a/lib/credo/check/consistency/file_location.ex b/lib/credo/check/consistency/file_location.ex new file mode 100644 index 000000000..5ef17b894 --- /dev/null +++ b/lib/credo/check/consistency/file_location.ex @@ -0,0 +1,132 @@ +defmodule Credo.Check.Consistency.FileLocation do + @moduledoc false + + # credo:disable-for-this-file Credo.Check.Readability.Specs + + @checkdoc """ + File location should follow the namespace hierarchy of the module it defines. + + Examples: + + - `lib/my_system.ex` should define the `MySystem` module + - `lib/my_system/accounts.ex` should define the `MySystem.Accounts` module + """ + @explanation [warning: @checkdoc] + + # `use Credo.Check` required that module attributes are already defined, so we need to place these attributes + # before use/alias expressions. + # credo:disable-for-next-line VBT.Credo.Check.Consistency.ModuleLayout + use Credo.Check, category: :warning, base_priority: :high + + alias Credo.Code + + def run(source_file, params \\ []) do + case verify(source_file, params) do + :ok -> + [] + + {:error, module, expected_file} -> + error(IssueMeta.for(source_file, params), module, expected_file) + end + end + + defp verify(source_file, params) do + source_file.filename + |> Path.relative_to_cwd() + |> verify(Code.ast(source_file), params) + end + + @doc false + def verify(relative_path, ast, params) do + if verify_path?(relative_path, params), + do: ast |> main_module() |> verify_module(relative_path, params), + else: :ok + end + + defp verify_path?(relative_path, params) do + case Path.split(relative_path) do + ["lib" | _] -> not exclude?(relative_path, params) + ["test", "support" | _] -> false + ["test", "test_helper.exs"] -> false + ["test" | _] -> not exclude?(relative_path, params) + _ -> false + end + end + + defp exclude?(relative_path, params) do + params + |> Keyword.get(:exclude, []) + |> Enum.any?(&String.starts_with?(relative_path, &1)) + end + + defp main_module(ast) do + {_ast, modules} = Macro.prewalk(ast, [], &traverse/2) + Enum.at(modules, -1) + end + + defp traverse({:defmodule, _meta, args}, modules) do + [{:__aliases__, _, name_parts}, _module_body] = args + {args, [Module.concat(name_parts) | modules]} + end + + defp traverse(ast, state), do: {ast, state} + + # empty file - shouldn't really happen, but we'll let it through + defp verify_module(nil, _relative_path, _params), do: :ok + + defp verify_module(main_module, relative_path, params) do + parsed_path = parsed_path(relative_path, params) + + expected_file = + expected_file_base(parsed_path.root, main_module) <> + Path.extname(parsed_path.allowed) + + if expected_file == parsed_path.allowed, + do: :ok, + else: {:error, main_module, expected_file} + end + + defp parsed_path(relative_path, params) do + parts = Path.split(relative_path) + + allowed = + Keyword.get(params, :ignore_folder_namespace, %{}) + |> Stream.flat_map(fn {root, folders} -> Enum.map(folders, &Path.join([root, &1])) end) + |> Stream.map(&Path.split/1) + |> Enum.find(&List.starts_with?(parts, &1)) + |> case do + nil -> + relative_path + + ignore_parts -> + Stream.drop(ignore_parts, -1) + |> Enum.concat(Stream.drop(parts, length(ignore_parts))) + |> Path.join() + end + + %{root: hd(parts), allowed: allowed} + end + + defp expected_file_base(root_folder, module) do + {parent_namespace, module_name} = module |> Module.split() |> Enum.split(-1) + + relative_path = + if parent_namespace == [], + do: "", + else: parent_namespace |> Module.concat() |> Macro.underscore() + + file_name = module_name |> Module.concat() |> Macro.underscore() + + Path.join([root_folder, relative_path, file_name]) + end + + defp error(issue_meta, module, expected_file) do + format_issue(issue_meta, + message: + "Mismatch between file name and main module #{inspect(module)}. " <> + "Expected file path to be #{expected_file}. " <> + "Either move the file or rename the module.", + line_no: 1 + ) + end +end -- cgit v1.2.3 From 6bf85440b373c9b2fa1e8e7184dcf87518600306 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 20 Jun 2020 19:09:04 +0300 Subject: mix tasks consistency --- lib/mix/tasks/pleroma/ecto.ex | 50 +++++++++++++++++++++++++++++++++++++ lib/mix/tasks/pleroma/ecto/ecto.ex | 50 ------------------------------------- lib/mix/tasks/pleroma/robots_txt.ex | 33 ++++++++++++++++++++++++ lib/mix/tasks/pleroma/robotstxt.ex | 33 ------------------------ 4 files changed, 83 insertions(+), 83 deletions(-) create mode 100644 lib/mix/tasks/pleroma/ecto.ex delete mode 100644 lib/mix/tasks/pleroma/ecto/ecto.ex create mode 100644 lib/mix/tasks/pleroma/robots_txt.ex delete mode 100644 lib/mix/tasks/pleroma/robotstxt.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/ecto.ex b/lib/mix/tasks/pleroma/ecto.ex new file mode 100644 index 000000000..3363cd45f --- /dev/null +++ b/lib/mix/tasks/pleroma/ecto.ex @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-onl + +defmodule Mix.Tasks.Pleroma.Ecto do + @doc """ + Ensures the given repository's migrations path exists on the file system. + """ + @spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t() + def ensure_migrations_path(repo, opts) do + path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations") + + path = + case Path.type(path) do + :relative -> + Path.join(Application.app_dir(:pleroma), path) + + :absolute -> + path + end + + if not File.dir?(path) do + raise_missing_migrations(Path.relative_to_cwd(path), repo) + end + + path + end + + @doc """ + Returns the private repository path relative to the source. + """ + def source_repo_priv(repo) do + config = repo.config() + priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" + Path.join(Application.app_dir(:pleroma), priv) + end + + defp raise_missing_migrations(path, repo) do + raise(""" + Could not find migrations directory #{inspect(path)} + for repo #{inspect(repo)}. + This may be because you are in a new project and the + migration directory has not been created yet. Creating an + empty directory at the path above will fix this error. + If you expected existing migrations to be found, please + make sure your repository has been properly configured + and the configured path exists. + """) + end +end diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto/ecto.ex deleted file mode 100644 index 3363cd45f..000000000 --- a/lib/mix/tasks/pleroma/ecto/ecto.ex +++ /dev/null @@ -1,50 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-onl - -defmodule Mix.Tasks.Pleroma.Ecto do - @doc """ - Ensures the given repository's migrations path exists on the file system. - """ - @spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t() - def ensure_migrations_path(repo, opts) do - path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations") - - path = - case Path.type(path) do - :relative -> - Path.join(Application.app_dir(:pleroma), path) - - :absolute -> - path - end - - if not File.dir?(path) do - raise_missing_migrations(Path.relative_to_cwd(path), repo) - end - - path - end - - @doc """ - Returns the private repository path relative to the source. - """ - def source_repo_priv(repo) do - config = repo.config() - priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}" - Path.join(Application.app_dir(:pleroma), priv) - end - - defp raise_missing_migrations(path, repo) do - raise(""" - Could not find migrations directory #{inspect(path)} - for repo #{inspect(repo)}. - This may be because you are in a new project and the - migration directory has not been created yet. Creating an - empty directory at the path above will fix this error. - If you expected existing migrations to be found, please - make sure your repository has been properly configured - and the configured path exists. - """) - end -end diff --git a/lib/mix/tasks/pleroma/robots_txt.ex b/lib/mix/tasks/pleroma/robots_txt.ex new file mode 100644 index 000000000..24f08180e --- /dev/null +++ b/lib/mix/tasks/pleroma/robots_txt.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.RobotsTxt do + use Mix.Task + + @shortdoc "Generate robots.txt" + @moduledoc """ + Generates robots.txt + + ## Overwrite robots.txt to disallow all + + mix pleroma.robots_txt disallow_all + + This will write a robots.txt that will hide all paths on your instance + from search engines and other robots that obey robots.txt + + """ + def run(["disallow_all"]) do + Mix.Pleroma.start_pleroma() + static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") + + if !File.exists?(static_dir) do + File.mkdir_p!(static_dir) + end + + robots_txt_path = Path.join(static_dir, "robots.txt") + robots_txt_content = "User-Agent: *\nDisallow: /\n" + + File.write!(robots_txt_path, robots_txt_content, [:write]) + end +end diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robotstxt.ex deleted file mode 100644 index 24f08180e..000000000 --- a/lib/mix/tasks/pleroma/robotstxt.ex +++ /dev/null @@ -1,33 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Mix.Tasks.Pleroma.RobotsTxt do - use Mix.Task - - @shortdoc "Generate robots.txt" - @moduledoc """ - Generates robots.txt - - ## Overwrite robots.txt to disallow all - - mix pleroma.robots_txt disallow_all - - This will write a robots.txt that will hide all paths on your instance - from search engines and other robots that obey robots.txt - - """ - def run(["disallow_all"]) do - Mix.Pleroma.start_pleroma() - static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") - - if !File.exists?(static_dir) do - File.mkdir_p!(static_dir) - end - - robots_txt_path = Path.join(static_dir, "robots.txt") - robots_txt_content = "User-Agent: *\nDisallow: /\n" - - File.write!(robots_txt_path, robots_txt_content, [:write]) - end -end -- cgit v1.2.3 From 103f3dcb9ed0a12a11e9cc5c574449439fc2cb0e Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 23 Jun 2020 18:33:03 +0300 Subject: rich media parser ttl files consistency --- lib/pleroma/web/rich_media/parser/ttl.ex | 7 +++ .../web/rich_media/parser/ttl/aws_signed_url.ex | 50 ++++++++++++++++++++++ .../web/rich_media/parsers/ttl/aws_signed_url.ex | 50 ---------------------- lib/pleroma/web/rich_media/parsers/ttl/ttl.ex | 7 --- 4 files changed, 57 insertions(+), 57 deletions(-) create mode 100644 lib/pleroma/web/rich_media/parser/ttl.ex create mode 100644 lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex delete mode 100644 lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex delete mode 100644 lib/pleroma/web/rich_media/parsers/ttl/ttl.ex (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex new file mode 100644 index 000000000..8353f0fff --- /dev/null +++ b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parser.TTL do + @callback ttl(Map.t(), String.t()) :: Integer.t() | nil +end diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex new file mode 100644 index 000000000..fc4ef79c0 --- /dev/null +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do + @behaviour Pleroma.Web.RichMedia.Parser.TTL + + @impl true + def ttl(data, _url) do + image = Map.get(data, :image) + + if is_aws_signed_url(image) do + image + |> parse_query_params() + |> format_query_params() + |> get_expiration_timestamp() + else + {:error, "Not aws signed url #{inspect(image)}"} + end + end + + defp is_aws_signed_url(image) when is_binary(image) and image != "" do + %URI{host: host, query: query} = URI.parse(image) + + String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") + end + + defp is_aws_signed_url(_), do: nil + + defp parse_query_params(image) do + %URI{query: query} = URI.parse(image) + query + end + + defp format_query_params(query) do + query + |> String.split(~r/&|=/) + |> Enum.chunk_every(2) + |> Map.new(fn [k, v] -> {k, v} end) + end + + defp get_expiration_timestamp(params) when is_map(params) do + {:ok, date} = + params + |> Map.get("X-Amz-Date") + |> Timex.parse("{ISO:Basic:Z}") + + {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))} + end +end diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex deleted file mode 100644 index 15109d28d..000000000 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ /dev/null @@ -1,50 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do - @behaviour Pleroma.Web.RichMedia.Parser.TTL - - @impl Pleroma.Web.RichMedia.Parser.TTL - def ttl(data, _url) do - image = Map.get(data, :image) - - if is_aws_signed_url(image) do - image - |> parse_query_params() - |> format_query_params() - |> get_expiration_timestamp() - else - {:error, "Not aws signed url #{inspect(image)}"} - end - end - - defp is_aws_signed_url(image) when is_binary(image) and image != "" do - %URI{host: host, query: query} = URI.parse(image) - - String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") - end - - defp is_aws_signed_url(_), do: nil - - defp parse_query_params(image) do - %URI{query: query} = URI.parse(image) - query - end - - defp format_query_params(query) do - query - |> String.split(~r/&|=/) - |> Enum.chunk_every(2) - |> Map.new(fn [k, v] -> {k, v} end) - end - - defp get_expiration_timestamp(params) when is_map(params) do - {:ok, date} = - params - |> Map.get("X-Amz-Date") - |> Timex.parse("{ISO:Basic:Z}") - - {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))} - end -end diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex deleted file mode 100644 index 13511888c..000000000 --- a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex +++ /dev/null @@ -1,7 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.RichMedia.Parser.TTL do - @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()} -end -- cgit v1.2.3 From b5b4395e4a7c63e31579475888fa892dcdaeecff Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 23 Jun 2020 19:08:19 +0300 Subject: oauth consistency --- lib/pleroma/plugs/o_auth_plug.ex | 120 ++++ lib/pleroma/plugs/o_auth_scopes_plug.ex | 77 +++ lib/pleroma/plugs/oauth_plug.ex | 120 ---- lib/pleroma/plugs/oauth_scopes_plug.ex | 77 --- .../admin_api/controllers/o_auth_app_controller.ex | 77 +++ .../admin_api/controllers/oauth_app_controller.ex | 77 --- .../operations/admin/o_auth_app_operation.ex | 217 ++++++++ .../operations/admin/oauth_app_operation.ex | 217 -------- lib/pleroma/web/o_auth.ex | 6 + lib/pleroma/web/o_auth/app.ex | 149 +++++ lib/pleroma/web/o_auth/authorization.ex | 95 ++++ lib/pleroma/web/o_auth/fallback_controller.ex | 32 ++ lib/pleroma/web/o_auth/mfa_controller.ex | 98 ++++ lib/pleroma/web/o_auth/mfa_view.ex | 17 + lib/pleroma/web/o_auth/o_auth_controller.ex | 610 +++++++++++++++++++++ lib/pleroma/web/o_auth/o_auth_view.ex | 30 + lib/pleroma/web/o_auth/scopes.ex | 76 +++ lib/pleroma/web/o_auth/token.ex | 135 +++++ lib/pleroma/web/o_auth/token/query.ex | 49 ++ .../web/o_auth/token/strategy/refresh_token.ex | 58 ++ lib/pleroma/web/o_auth/token/strategy/revoke.ex | 26 + lib/pleroma/web/o_auth/token/utils.ex | 72 +++ lib/pleroma/web/oauth.ex | 6 - lib/pleroma/web/oauth/app.ex | 149 ----- lib/pleroma/web/oauth/authorization.ex | 95 ---- lib/pleroma/web/oauth/fallback_controller.ex | 32 -- lib/pleroma/web/oauth/mfa_controller.ex | 98 ---- lib/pleroma/web/oauth/mfa_view.ex | 17 - lib/pleroma/web/oauth/oauth_controller.ex | 610 --------------------- lib/pleroma/web/oauth/oauth_view.ex | 30 - lib/pleroma/web/oauth/scopes.ex | 76 --- lib/pleroma/web/oauth/token.ex | 135 ----- lib/pleroma/web/oauth/token/query.ex | 49 -- .../web/oauth/token/strategy/refresh_token.ex | 58 -- lib/pleroma/web/oauth/token/strategy/revoke.ex | 26 - lib/pleroma/web/oauth/token/utils.ex | 72 --- 36 files changed, 1944 insertions(+), 1944 deletions(-) create mode 100644 lib/pleroma/plugs/o_auth_plug.ex create mode 100644 lib/pleroma/plugs/o_auth_scopes_plug.ex delete mode 100644 lib/pleroma/plugs/oauth_plug.ex delete mode 100644 lib/pleroma/plugs/oauth_scopes_plug.ex create mode 100644 lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex delete mode 100644 lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex create mode 100644 lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex delete mode 100644 lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex create mode 100644 lib/pleroma/web/o_auth.ex create mode 100644 lib/pleroma/web/o_auth/app.ex create mode 100644 lib/pleroma/web/o_auth/authorization.ex create mode 100644 lib/pleroma/web/o_auth/fallback_controller.ex create mode 100644 lib/pleroma/web/o_auth/mfa_controller.ex create mode 100644 lib/pleroma/web/o_auth/mfa_view.ex create mode 100644 lib/pleroma/web/o_auth/o_auth_controller.ex create mode 100644 lib/pleroma/web/o_auth/o_auth_view.ex create mode 100644 lib/pleroma/web/o_auth/scopes.ex create mode 100644 lib/pleroma/web/o_auth/token.ex create mode 100644 lib/pleroma/web/o_auth/token/query.ex create mode 100644 lib/pleroma/web/o_auth/token/strategy/refresh_token.ex create mode 100644 lib/pleroma/web/o_auth/token/strategy/revoke.ex create mode 100644 lib/pleroma/web/o_auth/token/utils.ex delete mode 100644 lib/pleroma/web/oauth.ex delete mode 100644 lib/pleroma/web/oauth/app.ex delete mode 100644 lib/pleroma/web/oauth/authorization.ex delete mode 100644 lib/pleroma/web/oauth/fallback_controller.ex delete mode 100644 lib/pleroma/web/oauth/mfa_controller.ex delete mode 100644 lib/pleroma/web/oauth/mfa_view.ex delete mode 100644 lib/pleroma/web/oauth/oauth_controller.ex delete mode 100644 lib/pleroma/web/oauth/oauth_view.ex delete mode 100644 lib/pleroma/web/oauth/scopes.ex delete mode 100644 lib/pleroma/web/oauth/token.ex delete mode 100644 lib/pleroma/web/oauth/token/query.ex delete mode 100644 lib/pleroma/web/oauth/token/strategy/refresh_token.ex delete mode 100644 lib/pleroma/web/oauth/token/strategy/revoke.ex delete mode 100644 lib/pleroma/web/oauth/token/utils.ex (limited to 'lib') diff --git a/lib/pleroma/plugs/o_auth_plug.ex b/lib/pleroma/plugs/o_auth_plug.ex new file mode 100644 index 000000000..6fa71ef47 --- /dev/null +++ b/lib/pleroma/plugs/o_auth_plug.ex @@ -0,0 +1,120 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.OAuthPlug do + import Plug.Conn + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Token + + @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i") + + def init(options), do: options + + def call(%{assigns: %{user: %User{}}} = conn, _), do: conn + + def call(%{params: %{"access_token" => access_token}} = conn, _) do + with {:ok, user, token_record} <- fetch_user_and_token(access_token) do + conn + |> assign(:token, token_record) + |> assign(:user, user) + else + _ -> + # token found, but maybe only with app + with {:ok, app, token_record} <- fetch_app_and_token(access_token) do + conn + |> assign(:token, token_record) + |> assign(:app, app) + else + _ -> conn + end + end + end + + def call(conn, _) do + case fetch_token_str(conn) do + {:ok, token} -> + with {:ok, user, token_record} <- fetch_user_and_token(token) do + conn + |> assign(:token, token_record) + |> assign(:user, user) + else + _ -> + # token found, but maybe only with app + with {:ok, app, token_record} <- fetch_app_and_token(token) do + conn + |> assign(:token, token_record) + |> assign(:app, app) + else + _ -> conn + end + end + + _ -> + conn + end + end + + # Gets user by token + # + @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil + defp fetch_user_and_token(token) do + query = + from(t in Token, + where: t.token == ^token, + join: user in assoc(t, :user), + preload: [user: user] + ) + + # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength + with %Token{user: user} = token_record <- Repo.one(query) do + {:ok, user, token_record} + end + end + + @spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil + defp fetch_app_and_token(token) do + query = + from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app]) + + with %Token{app: app} = token_record <- Repo.one(query) do + {:ok, app, token_record} + end + end + + # Gets token from session by :oauth_token key + # + @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} + defp fetch_token_from_session(conn) do + case get_session(conn, :oauth_token) do + nil -> :no_token_found + token -> {:ok, token} + end + end + + # Gets token from headers + # + @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} + defp fetch_token_str(%Plug.Conn{} = conn) do + headers = get_req_header(conn, "authorization") + + with :no_token_found <- fetch_token_str(headers), + do: fetch_token_from_session(conn) + end + + @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()} + defp fetch_token_str([]), do: :no_token_found + + defp fetch_token_str([token | tail]) do + trimmed_token = String.trim(token) + + case Regex.run(@realm_reg, trimmed_token) do + [_, match] -> {:ok, String.trim(match)} + _ -> fetch_token_str(tail) + end + end +end diff --git a/lib/pleroma/plugs/o_auth_scopes_plug.ex b/lib/pleroma/plugs/o_auth_scopes_plug.ex new file mode 100644 index 000000000..b1a736d78 --- /dev/null +++ b/lib/pleroma/plugs/o_auth_scopes_plug.ex @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.OAuthScopesPlug do + import Plug.Conn + import Pleroma.Web.Gettext + + alias Pleroma.Config + + use Pleroma.Web, :plug + + def init(%{scopes: _} = options), do: options + + @impl true + def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do + op = options[:op] || :| + token = assigns[:token] + + scopes = transform_scopes(scopes, options) + matched_scopes = (token && filter_descendants(scopes, token.scopes)) || [] + + cond do + token && op == :| && Enum.any?(matched_scopes) -> + conn + + token && op == :& && matched_scopes == scopes -> + conn + + options[:fallback] == :proceed_unauthenticated -> + drop_auth_info(conn) + + true -> + missing_scopes = scopes -- matched_scopes + permissions = Enum.join(missing_scopes, " #{op} ") + + error_message = + dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions) + + conn + |> put_resp_content_type("application/json") + |> send_resp(:forbidden, Jason.encode!(%{error: error_message})) + |> halt() + end + end + + @doc "Drops authentication info from connection" + def drop_auth_info(conn) do + # To simplify debugging, setting a private variable on `conn` if auth info is dropped + conn + |> put_private(:authentication_ignored, true) + |> assign(:user, nil) + |> assign(:token, nil) + end + + @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" + def filter_descendants(scopes, supported_scopes) do + Enum.filter( + scopes, + fn scope -> + Enum.find( + supported_scopes, + &(scope == &1 || String.starts_with?(scope, &1 <> ":")) + ) + end + ) + end + + @doc "Transforms scopes by applying supported options (e.g. :admin)" + def transform_scopes(scopes, options) do + if options[:admin] do + Config.oauth_admin_scopes(scopes) + else + scopes + end + end +end diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex deleted file mode 100644 index 6fa71ef47..000000000 --- a/lib/pleroma/plugs/oauth_plug.ex +++ /dev/null @@ -1,120 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Plugs.OAuthPlug do - import Plug.Conn - import Ecto.Query - - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Token - - @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i") - - def init(options), do: options - - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def call(%{params: %{"access_token" => access_token}} = conn, _) do - with {:ok, user, token_record} <- fetch_user_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - end - - def call(conn, _) do - case fetch_token_str(conn) do - {:ok, token} -> - with {:ok, user, token_record} <- fetch_user_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - - _ -> - conn - end - end - - # Gets user by token - # - @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil - defp fetch_user_and_token(token) do - query = - from(t in Token, - where: t.token == ^token, - join: user in assoc(t, :user), - preload: [user: user] - ) - - # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength - with %Token{user: user} = token_record <- Repo.one(query) do - {:ok, user, token_record} - end - end - - @spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil - defp fetch_app_and_token(token) do - query = - from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app]) - - with %Token{app: app} = token_record <- Repo.one(query) do - {:ok, app, token_record} - end - end - - # Gets token from session by :oauth_token key - # - @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_from_session(conn) do - case get_session(conn, :oauth_token) do - nil -> :no_token_found - token -> {:ok, token} - end - end - - # Gets token from headers - # - @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_str(%Plug.Conn{} = conn) do - headers = get_req_header(conn, "authorization") - - with :no_token_found <- fetch_token_str(headers), - do: fetch_token_from_session(conn) - end - - @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_str([]), do: :no_token_found - - defp fetch_token_str([token | tail]) do - trimmed_token = String.trim(token) - - case Regex.run(@realm_reg, trimmed_token) do - [_, match] -> {:ok, String.trim(match)} - _ -> fetch_token_str(tail) - end - end -end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex deleted file mode 100644 index b1a736d78..000000000 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ /dev/null @@ -1,77 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Plugs.OAuthScopesPlug do - import Plug.Conn - import Pleroma.Web.Gettext - - alias Pleroma.Config - - use Pleroma.Web, :plug - - def init(%{scopes: _} = options), do: options - - @impl true - def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do - op = options[:op] || :| - token = assigns[:token] - - scopes = transform_scopes(scopes, options) - matched_scopes = (token && filter_descendants(scopes, token.scopes)) || [] - - cond do - token && op == :| && Enum.any?(matched_scopes) -> - conn - - token && op == :& && matched_scopes == scopes -> - conn - - options[:fallback] == :proceed_unauthenticated -> - drop_auth_info(conn) - - true -> - missing_scopes = scopes -- matched_scopes - permissions = Enum.join(missing_scopes, " #{op} ") - - error_message = - dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions) - - conn - |> put_resp_content_type("application/json") - |> send_resp(:forbidden, Jason.encode!(%{error: error_message})) - |> halt() - end - end - - @doc "Drops authentication info from connection" - def drop_auth_info(conn) do - # To simplify debugging, setting a private variable on `conn` if auth info is dropped - conn - |> put_private(:authentication_ignored, true) - |> assign(:user, nil) - |> assign(:token, nil) - end - - @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" - def filter_descendants(scopes, supported_scopes) do - Enum.filter( - scopes, - fn scope -> - Enum.find( - supported_scopes, - &(scope == &1 || String.starts_with?(scope, &1 <> ":")) - ) - end - ) - end - - @doc "Transforms scopes by applying supported options (e.g. :admin)" - def transform_scopes(scopes, options) do - if options[:admin] do - Config.oauth_admin_scopes(scopes) - else - scopes - end - end -end diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex new file mode 100644 index 000000000..dca23ea73 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.OAuthAppController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.OAuth.App + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(:put_view, Pleroma.Web.MastodonAPI.AppView) + + plug( + OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action in [:create, :index, :update, :delete] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation + + def index(conn, params) do + search_params = + params + |> Map.take([:client_id, :page, :page_size, :trusted]) + |> Map.put(:client_name, params[:name]) + + with {:ok, apps, count} <- App.search(search_params) do + render(conn, "index.json", + apps: apps, + count: count, + page_size: params.page_size, + admin: true + ) + end + end + + def create(%{body_params: params} = conn, _) do + params = Pleroma.Maps.put_if_present(params, :client_name, params[:name]) + + case App.create(params) do + {:ok, app} -> + render(conn, "show.json", app: app, admin: true) + + {:error, changeset} -> + json(conn, App.errors(changeset)) + end + end + + def update(%{body_params: params} = conn, %{id: id}) do + params = Pleroma.Maps.put_if_present(params, :client_name, params[:name]) + + with {:ok, app} <- App.update(id, params) do + render(conn, "show.json", app: app, admin: true) + else + {:error, changeset} -> + json(conn, App.errors(changeset)) + + nil -> + json_response(conn, :bad_request, "") + end + end + + def delete(conn, params) do + with {:ok, _app} <- App.destroy(params.id) do + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end +end diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex deleted file mode 100644 index dca23ea73..000000000 --- a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex +++ /dev/null @@ -1,77 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.AdminAPI.OAuthAppController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Web.OAuth.App - - require Logger - - plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:put_view, Pleroma.Web.MastodonAPI.AppView) - - plug( - OAuthScopesPlug, - %{scopes: ["write"], admin: true} - when action in [:create, :index, :update, :delete] - ) - - action_fallback(Pleroma.Web.AdminAPI.FallbackController) - - defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation - - def index(conn, params) do - search_params = - params - |> Map.take([:client_id, :page, :page_size, :trusted]) - |> Map.put(:client_name, params[:name]) - - with {:ok, apps, count} <- App.search(search_params) do - render(conn, "index.json", - apps: apps, - count: count, - page_size: params.page_size, - admin: true - ) - end - end - - def create(%{body_params: params} = conn, _) do - params = Pleroma.Maps.put_if_present(params, :client_name, params[:name]) - - case App.create(params) do - {:ok, app} -> - render(conn, "show.json", app: app, admin: true) - - {:error, changeset} -> - json(conn, App.errors(changeset)) - end - end - - def update(%{body_params: params} = conn, %{id: id}) do - params = Pleroma.Maps.put_if_present(params, :client_name, params[:name]) - - with {:ok, app} <- App.update(id, params) do - render(conn, "show.json", app: app, admin: true) - else - {:error, changeset} -> - json(conn, App.errors(changeset)) - - nil -> - json_response(conn, :bad_request, "") - end - end - - def delete(conn, params) do - with {:ok, _app} <- App.destroy(params.id) do - json_response(conn, :no_content, "") - else - _ -> json_response(conn, :bad_request, "") - end - end -end diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex new file mode 100644 index 000000000..a75f3e622 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -0,0 +1,217 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + summary: "List OAuth apps", + tags: ["Admin", "oAuth Apps"], + operationId: "AdminAPI.OAuthAppController.index", + security: [%{"oAuth" => ["write"]}], + parameters: [ + Operation.parameter(:name, :query, %Schema{type: :string}, "App name"), + Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"), + Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"), + Operation.parameter( + :trusted, + :query, + %Schema{type: :boolean, default: false}, + "Trusted apps" + ), + Operation.parameter( + :page_size, + :query, + %Schema{type: :integer, default: 50}, + "Number of apps to return" + ) + | admin_api_params() + ], + responses: %{ + 200 => + Operation.response("List of apps", "application/json", %Schema{ + type: :object, + properties: %{ + apps: %Schema{type: :array, items: oauth_app()}, + count: %Schema{type: :integer}, + page_size: %Schema{type: :integer} + }, + example: %{ + "apps" => [ + %{ + "id" => 1, + "name" => "App name", + "client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", + "client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", + "redirect_uri" => "https://example.com/oauth-callback", + "website" => "https://example.com", + "trusted" => true + } + ], + "count" => 1, + "page_size" => 50 + } + }) + } + } + end + + def create_operation do + %Operation{ + tags: ["Admin", "oAuth Apps"], + summary: "Create OAuth App", + operationId: "AdminAPI.OAuthAppController.create", + requestBody: request_body("Parameters", create_request()), + parameters: admin_api_params(), + security: [%{"oAuth" => ["write"]}], + responses: %{ + 200 => Operation.response("App", "application/json", oauth_app()), + 400 => Operation.response("Bad Request", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "oAuth Apps"], + summary: "Update OAuth App", + operationId: "AdminAPI.OAuthAppController.update", + parameters: [id_param() | admin_api_params()], + security: [%{"oAuth" => ["write"]}], + requestBody: request_body("Parameters", update_request()), + responses: %{ + 200 => Operation.response("App", "application/json", oauth_app()), + 400 => + Operation.response("Bad Request", "application/json", %Schema{ + oneOf: [ApiError, %Schema{type: :string}] + }) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "oAuth Apps"], + summary: "Delete OAuth App", + operationId: "AdminAPI.OAuthAppController.delete", + parameters: [id_param() | admin_api_params()], + security: [%{"oAuth" => ["write"]}], + responses: %{ + 204 => no_content_response(), + 400 => no_content_response() + } + } + end + + defp create_request do + %Schema{ + title: "oAuthAppCreateRequest", + type: :object, + required: [:name, :redirect_uris], + properties: %{ + name: %Schema{type: :string, description: "Application Name"}, + scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, + redirect_uris: %Schema{ + type: :string, + description: + "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." + }, + website: %Schema{ + type: :string, + nullable: true, + description: "A URL to the homepage of the app" + }, + trusted: %Schema{ + type: :boolean, + nullable: true, + default: false, + description: "Is the app trusted?" + } + }, + example: %{ + "name" => "My App", + "redirect_uris" => "https://myapp.com/auth/callback", + "website" => "https://myapp.com/", + "scopes" => ["read", "write"], + "trusted" => true + } + } + end + + defp update_request do + %Schema{ + title: "oAuthAppUpdateRequest", + type: :object, + properties: %{ + name: %Schema{type: :string, description: "Application Name"}, + scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, + redirect_uris: %Schema{ + type: :string, + description: + "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." + }, + website: %Schema{ + type: :string, + nullable: true, + description: "A URL to the homepage of the app" + }, + trusted: %Schema{ + type: :boolean, + nullable: true, + default: false, + description: "Is the app trusted?" + } + }, + example: %{ + "name" => "My App", + "redirect_uris" => "https://myapp.com/auth/callback", + "website" => "https://myapp.com/", + "scopes" => ["read", "write"], + "trusted" => true + } + } + end + + defp oauth_app do + %Schema{ + title: "oAuthApp", + type: :object, + properties: %{ + id: %Schema{type: :integer}, + name: %Schema{type: :string}, + client_id: %Schema{type: :string}, + client_secret: %Schema{type: :string}, + redirect_uri: %Schema{type: :string}, + website: %Schema{type: :string, nullable: true}, + trusted: %Schema{type: :boolean} + }, + example: %{ + "id" => 123, + "name" => "My App", + "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM", + "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw", + "redirect_uri" => "https://myapp.com/oauth-callback", + "website" => "https://myapp.com/", + "trusted" => false + } + } + end + + def id_param do + Operation.parameter(:id, :path, :integer, "App ID", + example: 1337, + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex deleted file mode 100644 index a75f3e622..000000000 --- a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex +++ /dev/null @@ -1,217 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do - alias OpenApiSpex.Operation - alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.ApiError - - import Pleroma.Web.ApiSpec.Helpers - - def open_api_operation(action) do - operation = String.to_existing_atom("#{action}_operation") - apply(__MODULE__, operation, []) - end - - def index_operation do - %Operation{ - summary: "List OAuth apps", - tags: ["Admin", "oAuth Apps"], - operationId: "AdminAPI.OAuthAppController.index", - security: [%{"oAuth" => ["write"]}], - parameters: [ - Operation.parameter(:name, :query, %Schema{type: :string}, "App name"), - Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"), - Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"), - Operation.parameter( - :trusted, - :query, - %Schema{type: :boolean, default: false}, - "Trusted apps" - ), - Operation.parameter( - :page_size, - :query, - %Schema{type: :integer, default: 50}, - "Number of apps to return" - ) - | admin_api_params() - ], - responses: %{ - 200 => - Operation.response("List of apps", "application/json", %Schema{ - type: :object, - properties: %{ - apps: %Schema{type: :array, items: oauth_app()}, - count: %Schema{type: :integer}, - page_size: %Schema{type: :integer} - }, - example: %{ - "apps" => [ - %{ - "id" => 1, - "name" => "App name", - "client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", - "client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", - "redirect_uri" => "https://example.com/oauth-callback", - "website" => "https://example.com", - "trusted" => true - } - ], - "count" => 1, - "page_size" => 50 - } - }) - } - } - end - - def create_operation do - %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Create OAuth App", - operationId: "AdminAPI.OAuthAppController.create", - requestBody: request_body("Parameters", create_request()), - parameters: admin_api_params(), - security: [%{"oAuth" => ["write"]}], - responses: %{ - 200 => Operation.response("App", "application/json", oauth_app()), - 400 => Operation.response("Bad Request", "application/json", ApiError) - } - } - end - - def update_operation do - %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Update OAuth App", - operationId: "AdminAPI.OAuthAppController.update", - parameters: [id_param() | admin_api_params()], - security: [%{"oAuth" => ["write"]}], - requestBody: request_body("Parameters", update_request()), - responses: %{ - 200 => Operation.response("App", "application/json", oauth_app()), - 400 => - Operation.response("Bad Request", "application/json", %Schema{ - oneOf: [ApiError, %Schema{type: :string}] - }) - } - } - end - - def delete_operation do - %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Delete OAuth App", - operationId: "AdminAPI.OAuthAppController.delete", - parameters: [id_param() | admin_api_params()], - security: [%{"oAuth" => ["write"]}], - responses: %{ - 204 => no_content_response(), - 400 => no_content_response() - } - } - end - - defp create_request do - %Schema{ - title: "oAuthAppCreateRequest", - type: :object, - required: [:name, :redirect_uris], - properties: %{ - name: %Schema{type: :string, description: "Application Name"}, - scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, - redirect_uris: %Schema{ - type: :string, - description: - "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." - }, - website: %Schema{ - type: :string, - nullable: true, - description: "A URL to the homepage of the app" - }, - trusted: %Schema{ - type: :boolean, - nullable: true, - default: false, - description: "Is the app trusted?" - } - }, - example: %{ - "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", - "website" => "https://myapp.com/", - "scopes" => ["read", "write"], - "trusted" => true - } - } - end - - defp update_request do - %Schema{ - title: "oAuthAppUpdateRequest", - type: :object, - properties: %{ - name: %Schema{type: :string, description: "Application Name"}, - scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"}, - redirect_uris: %Schema{ - type: :string, - description: - "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." - }, - website: %Schema{ - type: :string, - nullable: true, - description: "A URL to the homepage of the app" - }, - trusted: %Schema{ - type: :boolean, - nullable: true, - default: false, - description: "Is the app trusted?" - } - }, - example: %{ - "name" => "My App", - "redirect_uris" => "https://myapp.com/auth/callback", - "website" => "https://myapp.com/", - "scopes" => ["read", "write"], - "trusted" => true - } - } - end - - defp oauth_app do - %Schema{ - title: "oAuthApp", - type: :object, - properties: %{ - id: %Schema{type: :integer}, - name: %Schema{type: :string}, - client_id: %Schema{type: :string}, - client_secret: %Schema{type: :string}, - redirect_uri: %Schema{type: :string}, - website: %Schema{type: :string, nullable: true}, - trusted: %Schema{type: :boolean} - }, - example: %{ - "id" => 123, - "name" => "My App", - "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM", - "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw", - "redirect_uri" => "https://myapp.com/oauth-callback", - "website" => "https://myapp.com/", - "trusted" => false - } - } - end - - def id_param do - Operation.parameter(:id, :path, :integer, "App ID", - example: 1337, - required: true - ) - end -end diff --git a/lib/pleroma/web/o_auth.ex b/lib/pleroma/web/o_auth.ex new file mode 100644 index 000000000..2f1b8708d --- /dev/null +++ b/lib/pleroma/web/o_auth.ex @@ -0,0 +1,6 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth do +end diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex new file mode 100644 index 000000000..df99472e1 --- /dev/null +++ b/lib/pleroma/web/o_auth/app.ex @@ -0,0 +1,149 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.App do + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + alias Pleroma.Repo + + @type t :: %__MODULE__{} + + schema "apps" do + field(:client_name, :string) + field(:redirect_uris, :string) + field(:scopes, {:array, :string}, default: []) + field(:website, :string) + field(:client_id, :string) + field(:client_secret, :string) + field(:trusted, :boolean, default: false) + + has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all) + has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all) + + timestamps() + end + + @spec changeset(t(), map()) :: Ecto.Changeset.t() + def changeset(struct, params) do + cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted]) + end + + @spec register_changeset(t(), map()) :: Ecto.Changeset.t() + def register_changeset(struct, params \\ %{}) do + changeset = + struct + |> changeset(params) + |> validate_required([:client_name, :redirect_uris, :scopes]) + + if changeset.valid? do + changeset + |> put_change( + :client_id, + :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + ) + |> put_change( + :client_secret, + :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + ) + else + changeset + end + end + + @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def create(params) do + %__MODULE__{} + |> register_changeset(params) + |> Repo.insert() + end + + @spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def update(id, params) do + with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do + app + |> changeset(params) + |> Repo.update() + end + end + + @doc """ + Gets app by attrs or create new with attrs. + And updates the scopes if need. + """ + @spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def get_or_make(attrs, scopes) do + with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do + update_scopes(app, scopes) + else + _e -> + %__MODULE__{} + |> register_changeset(Map.put(attrs, :scopes, scopes)) + |> Repo.insert() + end + end + + defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app} + defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app} + + defp update_scopes(%__MODULE__{} = app, scopes) do + app + |> change(%{scopes: scopes}) + |> Repo.update() + end + + @spec search(map()) :: {:ok, [t()], non_neg_integer()} + def search(params) do + query = from(a in __MODULE__) + + query = + if params[:client_name] do + from(a in query, where: a.client_name == ^params[:client_name]) + else + query + end + + query = + if params[:client_id] do + from(a in query, where: a.client_id == ^params[:client_id]) + else + query + end + + query = + if Map.has_key?(params, :trusted) do + from(a in query, where: a.trusted == ^params[:trusted]) + else + query + end + + query = + from(u in query, + limit: ^params[:page_size], + offset: ^((params[:page] - 1) * params[:page_size]) + ) + + count = Repo.aggregate(__MODULE__, :count, :id) + + {:ok, Repo.all(query), count} + end + + @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def destroy(id) do + with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do + Repo.delete(app) + end + end + + @spec errors(Ecto.Changeset.t()) :: map() + def errors(changeset) do + Enum.reduce(changeset.errors, %{}, fn + {:client_name, {error, _}}, acc -> + Map.put(acc, :name, error) + + {key, {error, _}}, acc -> + Map.put(acc, key, error) + end) + end +end diff --git a/lib/pleroma/web/o_auth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex new file mode 100644 index 000000000..268ee5b63 --- /dev/null +++ b/lib/pleroma/web/o_auth/authorization.ex @@ -0,0 +1,95 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Authorization do + use Ecto.Schema + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + + import Ecto.Changeset + import Ecto.Query + + @type t :: %__MODULE__{} + + schema "oauth_authorizations" do + field(:token, :string) + field(:scopes, {:array, :string}, default: []) + field(:valid_until, :naive_datetime_usec) + field(:used, :boolean, default: false) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:app, App) + + timestamps() + end + + @spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) :: + {:ok, Authorization.t()} | {:error, Changeset.t()} + def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do + %{ + scopes: scopes || app.scopes, + user_id: user.id, + app_id: app.id + } + |> create_changeset() + |> Repo.insert() + end + + @spec create_changeset(map()) :: Changeset.t() + def create_changeset(attrs \\ %{}) do + %Authorization{} + |> cast(attrs, [:user_id, :app_id, :scopes, :valid_until]) + |> validate_required([:app_id, :scopes]) + |> add_token() + |> add_lifetime() + end + + defp add_token(changeset) do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + put_change(changeset, :token, token) + end + + defp add_lifetime(changeset) do + put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) + end + + @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() + def use_changeset(%Authorization{} = auth, params) do + auth + |> cast(params, [:used]) + |> validate_required([:used]) + end + + @spec use_token(Authorization.t()) :: + {:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()} + def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do + if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do + Repo.update(use_changeset(auth, %{used: true})) + else + {:error, "token expired"} + end + end + + def use_token(%Authorization{used: true}), do: {:error, "already used"} + + @spec delete_user_authorizations(User.t()) :: {integer(), any()} + def delete_user_authorizations(%User{} = user) do + user + |> delete_by_user_query + |> Repo.delete_all() + end + + def delete_by_user_query(%User{id: user_id}) do + from(a in __MODULE__, where: a.user_id == ^user_id) + end + + @doc "gets auth for app by 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) + |> Repo.find_resource() + end +end diff --git a/lib/pleroma/web/o_auth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex new file mode 100644 index 000000000..a89ced886 --- /dev/null +++ b/lib/pleroma/web/o_auth/fallback_controller.ex @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.FallbackController do + use Pleroma.Web, :controller + alias Pleroma.Web.OAuth.OAuthController + + def call(conn, {:register, :generic_error}) do + conn + |> put_status(:internal_server_error) + |> put_flash( + :error, + dgettext("errors", "Unknown error, please check the details and try again.") + ) + |> OAuthController.registration_details(conn.params) + end + + def call(conn, {:register, _error}) do + conn + |> put_status(:unauthorized) + |> put_flash(:error, dgettext("errors", "Invalid Username/Password")) + |> OAuthController.registration_details(conn.params) + end + + def call(conn, _error) do + conn + |> put_status(:unauthorized) + |> put_flash(:error, dgettext("errors", "Invalid Username/Password")) + |> OAuthController.authorize(conn.params) + end +end diff --git a/lib/pleroma/web/o_auth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex new file mode 100644 index 000000000..f102c93e7 --- /dev/null +++ b/lib/pleroma/web/o_auth/mfa_controller.ex @@ -0,0 +1,98 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAController do + @moduledoc """ + The model represents api to use Multi Factor authentications. + """ + + use Pleroma.Web, :controller + + alias Pleroma.MFA + alias Pleroma.Web.Auth.TOTPAuthenticator + alias Pleroma.Web.OAuth.MFAView, as: View + alias Pleroma.Web.OAuth.OAuthController + alias Pleroma.Web.OAuth.OAuthView + alias Pleroma.Web.OAuth.Token + + plug(:fetch_session when action in [:show, :verify]) + plug(:fetch_flash when action in [:show, :verify]) + + @doc """ + Display form to input mfa code or recovery code. + """ + def show(conn, %{"mfa_token" => mfa_token} = params) do + template = Map.get(params, "challenge_type", "totp") + + conn + |> put_view(View) + |> render("#{template}.html", %{ + mfa_token: mfa_token, + redirect_uri: params["redirect_uri"], + state: params["state"] + }) + end + + @doc """ + Verification code and continue authorization. + """ + def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do + with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), + {:ok, _} <- validates_challenge(user, mfa_params) do + conn + |> OAuthController.after_create_authorization(auth, %{ + "authorization" => %{ + "redirect_uri" => mfa_params["redirect_uri"], + "state" => mfa_params["state"] + } + }) + else + _ -> + conn + |> put_flash(:error, "Two-factor authentication failed.") + |> put_status(:unauthorized) + |> show(mfa_params) + end + end + + @doc """ + Verification second step of MFA (or recovery) and returns access token. + + ## Endpoint + POST /oauth/mfa/challenge + + params: + `client_id` + `client_secret` + `mfa_token` - access token to check second step of mfa + `challenge_type` - 'totp' or 'recovery' + `code` + + """ + def challenge(conn, %{"mfa_token" => mfa_token} = params) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), + {:ok, _} <- validates_challenge(user, params), + {:ok, token} <- Token.exchange_token(app, auth) do + json(conn, OAuthView.render("token.json", %{user: user, token: token})) + else + _error -> + conn + |> put_status(400) + |> json(%{error: "Invalid code"}) + end + end + + # Verify TOTP Code + defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do + TOTPAuthenticator.verify(code, user) + end + + # Verify Recovery Code + defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do + TOTPAuthenticator.verify_recovery_code(user, code) + end + + defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type} +end diff --git a/lib/pleroma/web/o_auth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex new file mode 100644 index 000000000..5d87db268 --- /dev/null +++ b/lib/pleroma/web/o_auth/mfa_view.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAView do + use Pleroma.Web, :view + import Phoenix.HTML.Form + alias Pleroma.MFA + + def render("mfa_response.json", %{token: token, user: user}) do + %{ + error: "mfa_required", + mfa_token: token.token, + supported_challenge_types: MFA.supported_methods(user) + } + end +end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex new file mode 100644 index 000000000..a4152e840 --- /dev/null +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -0,0 +1,610 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.OAuthController do + use Pleroma.Web, :controller + + alias Pleroma.Helpers.UriHelper + alias Pleroma.Maps + alias Pleroma.MFA + alias Pleroma.Plugs.RateLimiter + alias Pleroma.Registration + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.Auth.Authenticator + alias Pleroma.Web.ControllerHelper + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.MFAController + alias Pleroma.Web.OAuth.MFAView + alias Pleroma.Web.OAuth.OAuthView + alias Pleroma.Web.OAuth.Scopes + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken + alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken + + require Logger + + if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth) + + plug(:fetch_session) + plug(:fetch_flash) + + plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]) + + plug(RateLimiter, [name: :authentication] when action == :create_authorization) + + action_fallback(Pleroma.Web.OAuth.FallbackController) + + @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob" + + # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg + def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do + {auth_attrs, params} = Map.pop(params, "authorization") + authorize(conn, Map.merge(params, auth_attrs)) + end + + def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do + if ControllerHelper.truthy_param?(params["force_login"]) do + do_authorize(conn, params) + else + handle_existing_authorization(conn, params) + end + end + + # Note: the token is set in oauth_plug, but the token and client do not always go together. + # For example, MastodonFE's token is set if user requests with another client, + # after user already authorized to MastodonFE. + # So we have to check client and token. + def authorize( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{"client_id" => client_id} = params + ) do + with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app), + ^client_id <- t.app.client_id do + handle_existing_authorization(conn, params) + else + _ -> do_authorize(conn, params) + end + end + + def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params) + + defp do_authorize(%Plug.Conn{} = conn, params) do + app = Repo.get_by(App, client_id: params["client_id"]) + available_scopes = (app && app.scopes) || [] + scopes = Scopes.fetch_scopes(params, available_scopes) + + scopes = + if scopes == [] do + available_scopes + else + scopes + end + + # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template + render(conn, Authenticator.auth_template(), %{ + response_type: params["response_type"], + client_id: params["client_id"], + available_scopes: available_scopes, + scopes: scopes, + redirect_uri: params["redirect_uri"], + state: params["state"], + params: params + }) + end + + defp handle_existing_authorization( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{"redirect_uri" => @oob_token_redirect_uri} + ) do + render(conn, "oob_token_exists.html", %{token: token}) + end + + defp handle_existing_authorization( + %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, + %{} = params + ) do + app = Repo.preload(token, :app).app + + redirect_uri = + if is_binary(params["redirect_uri"]) do + params["redirect_uri"] + else + default_redirect_uri(app) + end + + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) + url_params = %{access_token: token.token} + url_params = Maps.put_if_present(url_params, :state, params["state"]) + url = UriHelper.modify_uri_params(redirect_uri, url_params) + redirect(conn, external: url) + else + conn + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) + |> redirect(external: redirect_uri(conn, redirect_uri)) + end + end + + def create_authorization( + %Plug.Conn{} = conn, + %{"authorization" => _} = params, + opts \\ [] + ) do + with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do + after_create_authorization(conn, auth, params) + else + error -> + handle_create_authorization_error(conn, error, params) + end + end + + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ + "authorization" => %{"redirect_uri" => @oob_token_redirect_uri} + }) do + # Enforcing the view to reuse the template when calling from other controllers + conn + |> put_view(OAuthView) + |> render("oob_authorization_created.html", %{auth: auth}) + end + + def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ + "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs + }) do + app = Repo.preload(auth, :app).app + + # An extra safety measure before we redirect (also done in `do_create_authorization/2`) + if redirect_uri in String.split(app.redirect_uris) do + redirect_uri = redirect_uri(conn, redirect_uri) + url_params = %{code: auth.token} + url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"]) + url = UriHelper.modify_uri_params(redirect_uri, url_params) + redirect(conn, external: url) + else + conn + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) + |> redirect(external: redirect_uri(conn, redirect_uri)) + end + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:error, scopes_issue}, + %{"authorization" => _} = params + ) + when scopes_issue in [:unsupported_scopes, :missing_scopes] do + # Per https://github.com/tootsuite/mastodon/blob/ + # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39 + conn + |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes")) + |> put_status(:unauthorized) + |> authorize(params) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :confirmation_pending}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) + |> put_status(:forbidden) + |> authorize(params) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:mfa_required, user, auth, _}, + params + ) do + {:ok, token} = MFA.Token.create(user, auth) + + data = %{ + "mfa_token" => token.token, + "redirect_uri" => params["authorization"]["redirect_uri"], + "state" => params["authorization"]["state"] + } + + MFAController.show(conn, data) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Password reset is required")) + |> put_status(:forbidden) + |> authorize(params) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :deactivated}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Your account is currently disabled")) + |> put_status(:forbidden) + |> authorize(params) + end + + defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do + Authenticator.handle_error(conn, error) + end + + @doc "Renew access_token with refresh_token" + def token_exchange( + %Plug.Conn{} = conn, + %{"grant_type" => "refresh_token", "refresh_token" => token} = _params + ) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token), + {:ok, token} <- RefreshToken.grant(token) do + json(conn, OAuthView.render("token.json", %{user: user, token: token})) + else + _error -> render_invalid_credentials_error(conn) + end + end + + def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + fixed_token = Token.Utils.fix_padding(params["code"]), + {:ok, auth} <- Authorization.get_by_token(app, fixed_token), + %User{} = user <- User.get_cached_by_id(auth.user_id), + {:ok, token} <- Token.exchange_token(app, auth) do + json(conn, OAuthView.render("token.json", %{user: user, token: token})) + else + error -> + handle_token_exchange_error(conn, error) + end + end + + def token_exchange( + %Plug.Conn{} = conn, + %{"grant_type" => "password"} = params + ) do + with {:ok, %User{} = user} <- Authenticator.get_user(conn), + {:ok, app} <- Token.Utils.fetch_app(conn), + requested_scopes <- Scopes.fetch_scopes(params, app.scopes), + {:ok, token} <- login(user, app, requested_scopes) do + json(conn, OAuthView.render("token.json", %{user: user, token: token})) + else + error -> + handle_token_exchange_error(conn, error) + end + end + + def token_exchange( + %Plug.Conn{} = conn, + %{"grant_type" => "password", "name" => name, "password" => _password} = params + ) do + params = + params + |> Map.delete("name") + |> Map.put("username", name) + + token_exchange(conn, params) + end + + def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + {:ok, auth} <- Authorization.create_authorization(app, %User{}), + {:ok, token} <- Token.exchange_token(app, auth) do + json(conn, OAuthView.render("token.json", %{token: token})) + else + _error -> + handle_token_exchange_error(conn, :invalid_credentails) + end + end + + # Bad request + def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do + conn + |> put_status(:forbidden) + |> json(build_and_response_mfa_token(user, auth)) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do + render_error( + conn, + :forbidden, + "Your account is currently disabled", + %{}, + "account_is_disabled" + ) + end + + defp handle_token_exchange_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending} + ) do + render_error( + conn, + :forbidden, + "Password reset is required", + %{}, + "password_reset_required" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do + render_error( + conn, + :forbidden, + "Your login is missing a confirmed e-mail address", + %{}, + "missing_confirmed_email" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do + render_error( + conn, + :forbidden, + "Your account is awaiting approval.", + %{}, + "awaiting_approval" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do + render_invalid_credentials_error(conn) + end + + def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do + with {:ok, app} <- Token.Utils.fetch_app(conn), + {:ok, _token} <- RevokeToken.revoke(app, params) do + json(conn, %{}) + else + _error -> + # RFC 7009: invalid tokens [in the request] do not cause an error response + json(conn, %{}) + end + end + + def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + + # Response for bad request + defp bad_request(%Plug.Conn{} = conn, _) do + render_error(conn, :internal_server_error, "Bad request") + end + + @doc "Prepares OAuth request to provider for Ueberauth" + def prepare_request(%Plug.Conn{} = conn, %{ + "provider" => provider, + "authorization" => auth_attrs + }) do + scope = + auth_attrs + |> Scopes.fetch_scopes([]) + |> Scopes.to_string() + + state = + auth_attrs + |> Map.delete("scopes") + |> Map.put("scope", scope) + |> Jason.encode!() + + params = + auth_attrs + |> Map.drop(~w(scope scopes client_id redirect_uri)) + |> Map.put("state", state) + + # Handing the request to Ueberauth + redirect(conn, to: o_auth_path(conn, :request, provider, params)) + end + + def request(%Plug.Conn{} = conn, params) do + message = + if params["provider"] do + dgettext("errors", "Unsupported OAuth provider: %{provider}.", + provider: params["provider"] + ) + else + dgettext("errors", "Bad OAuth request.") + end + + conn + |> put_flash(:error, message) + |> redirect(to: "/") + end + + def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do + params = callback_params(params) + messages = for e <- Map.get(failure, :errors, []), do: e.message + message = Enum.join(messages, "; ") + + conn + |> put_flash( + :error, + dgettext("errors", "Failed to authenticate: %{message}.", message: message) + ) + |> redirect(external: redirect_uri(conn, params["redirect_uri"])) + end + + def callback(%Plug.Conn{} = conn, params) do + params = callback_params(params) + + with {:ok, registration} <- Authenticator.get_registration(conn) do + auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state)) + + case Repo.get_assoc(registration, :user) do + {:ok, user} -> + create_authorization(conn, %{"authorization" => auth_attrs}, user: user) + + _ -> + registration_params = + Map.merge(auth_attrs, %{ + "nickname" => Registration.nickname(registration), + "email" => Registration.email(registration) + }) + + conn + |> put_session_registration_id(registration.id) + |> registration_details(%{"authorization" => registration_params}) + end + else + error -> + Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns])) + + conn + |> put_flash(:error, dgettext("errors", "Failed to set up user account.")) + |> redirect(external: redirect_uri(conn, params["redirect_uri"])) + end + end + + defp callback_params(%{"state" => state} = params) do + Map.merge(params, Jason.decode!(state)) + end + + def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do + render(conn, "register.html", %{ + client_id: auth_attrs["client_id"], + redirect_uri: auth_attrs["redirect_uri"], + state: auth_attrs["state"], + scopes: Scopes.fetch_scopes(auth_attrs, []), + nickname: auth_attrs["nickname"], + email: auth_attrs["email"] + }) + end + + def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do + with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), + %Registration{} = registration <- Repo.get(Registration, registration_id), + {_, {:ok, auth, _user}} <- + {:create_authorization, do_create_authorization(conn, params)}, + %User{} = user <- Repo.preload(auth, :user).user, + {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do + conn + |> put_session_registration_id(nil) + |> after_create_authorization(auth, params) + else + {:create_authorization, error} -> + {:register, handle_create_authorization_error(conn, error, params)} + + _ -> + {:register, :generic_error} + end + end + + def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do + with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), + %Registration{} = registration <- Repo.get(Registration, registration_id), + {:ok, user} <- Authenticator.create_from_registration(conn, registration) do + conn + |> put_session_registration_id(nil) + |> create_authorization( + params, + user: user + ) + else + {:error, changeset} -> + message = + Enum.map(changeset.errors, fn {field, {error, _}} -> + "#{field} #{error}" + end) + |> Enum.join("; ") + + message = + String.replace( + message, + "ap_id has already been taken", + "nickname has already been taken" + ) + + conn + |> put_status(:forbidden) + |> put_flash(:error, "Error: #{message}.") + |> registration_details(params) + + _ -> + {:register, :generic_error} + end + end + + defp do_create_authorization(conn, auth_attrs, user \\ nil) + + defp do_create_authorization( + %Plug.Conn{} = conn, + %{ + "authorization" => + %{ + "client_id" => client_id, + "redirect_uri" => redirect_uri + } = auth_attrs + }, + user + ) do + with {_, {:ok, %User{} = user}} <- + {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, + %App{} = app <- Repo.get_by(App, client_id: client_id), + true <- redirect_uri in String.split(app.redirect_uris), + requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes), + {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do + {:ok, auth, user} + end + end + + defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes) + when is_list(requested_scopes) do + with {:account_status, :active} <- {:account_status, User.account_status(user)}, + {:ok, scopes} <- validate_scopes(app, requested_scopes), + {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + {:ok, auth} + end + end + + # Note: intended to be a private function but opened for AccountController that logs in on signup + @doc "If checks pass, creates authorization and token for given user, app and requested scopes." + def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do + with {:ok, auth} <- do_create_authorization(user, app, requested_scopes), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, + {:ok, token} <- Token.exchange_token(app, auth) do + {:ok, token} + end + end + + # Special case: Local MastodonFE + defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) + + defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri + + defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id) + + defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), + do: put_session(conn, :registration_id, registration_id) + + defp build_and_response_mfa_token(user, auth) do + with {:ok, token} <- MFA.Token.create(user, auth) do + MFAView.render("mfa_response.json", %{token: token, user: user}) + end + end + + @spec validate_scopes(App.t(), map() | list()) :: + {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} + defp validate_scopes(%App{} = app, params) when is_map(params) do + requested_scopes = Scopes.fetch_scopes(params, app.scopes) + validate_scopes(app, requested_scopes) + end + + defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do + Scopes.validate(requested_scopes, app.scopes) + end + + def default_redirect_uri(%App{} = app) do + app.redirect_uris + |> String.split() + |> Enum.at(0) + end + + defp render_invalid_credentials_error(conn) do + render_error(conn, :bad_request, "Invalid credentials") + end +end diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex new file mode 100644 index 000000000..f55247ebd --- /dev/null +++ b/lib/pleroma/web/o_auth/o_auth_view.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.OAuthView do + use Pleroma.Web, :view + import Phoenix.HTML.Form + + alias Pleroma.Web.OAuth.Token.Utils + + def render("token.json", %{token: token} = opts) do + response = %{ + token_type: "Bearer", + access_token: token.token, + refresh_token: token.refresh_token, + expires_in: expires_in(), + scope: Enum.join(token.scopes, " "), + created_at: Utils.format_created_at(token) + } + + if user = opts[:user] do + response + |> Map.put(:me, user.ap_id) + else + response + end + end + + defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) +end diff --git a/lib/pleroma/web/o_auth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex new file mode 100644 index 000000000..6f06f1431 --- /dev/null +++ b/lib/pleroma/web/o_auth/scopes.ex @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Scopes do + @moduledoc """ + Functions for dealing with scopes. + """ + + alias Pleroma.Plugs.OAuthScopesPlug + + @doc """ + Fetch scopes from request params. + + Note: `scopes` is used by Mastodon — supporting it but sticking to + OAuth's standard `scope` wherever we control it + """ + @spec fetch_scopes(map() | struct(), list()) :: list() + + def fetch_scopes(params, default) do + parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default) + end + + def parse_scopes(scopes, _default) when is_list(scopes) do + Enum.filter(scopes, &(&1 not in [nil, ""])) + end + + def parse_scopes(scopes, default) when is_binary(scopes) do + scopes + |> to_list + |> parse_scopes(default) + end + + def parse_scopes(_, default) do + default + end + + @doc """ + Convert scopes string to list + """ + @spec to_list(binary()) :: [binary()] + def to_list(nil), do: [] + + def to_list(str) do + str + |> String.trim() + |> String.split(~r/[\s,]+/) + end + + @doc """ + Convert scopes list to string + """ + @spec to_string(list()) :: binary() + def to_string(scopes), do: Enum.join(scopes, " ") + + @doc """ + Validates scopes. + """ + @spec validate(list() | nil, list()) :: + {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} + def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []], + do: {:error, :missing_scopes} + + def validate(scopes, app_scopes) do + case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do + ^scopes -> {:ok, scopes} + _ -> {:error, :unsupported_scopes} + end + end + + def contains_admin_scopes?(scopes) do + scopes + |> OAuthScopesPlug.filter_descendants(["admin"]) + |> Enum.any?() + end +end diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex new file mode 100644 index 000000000..de37998f2 --- /dev/null +++ b/lib/pleroma/web/o_auth/token.ex @@ -0,0 +1,135 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token do + use Ecto.Schema + + import Ecto.Changeset + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Query + + @type t :: %__MODULE__{} + + schema "oauth_tokens" do + field(:token, :string) + field(:refresh_token, :string) + field(:scopes, {:array, :string}, default: []) + field(:valid_until, :naive_datetime_usec) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:app, App) + + timestamps() + end + + @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 + 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 + Query.get_by_app(app_id) + |> Query.get_by_refresh_token(token) + |> Query.preload([:user]) + |> Repo.find_resource() + end + + @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()} + def exchange_token(app, auth) do + with {:ok, auth} <- Authorization.use_token(auth), + true <- auth.app_id == app.id do + user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{} + + create( + app, + user, + %{scopes: auth.scopes} + ) + end + end + + defp put_token(changeset) do + changeset + |> change(%{token: Token.Utils.generate_token()}) + |> validate_required([:token]) + |> unique_constraint(:token) + end + + defp put_refresh_token(changeset, attrs) do + refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token()) + + changeset + |> change(%{refresh_token: refresh_token}) + |> validate_required([:refresh_token]) + |> unique_constraint(:refresh_token) + end + + defp put_valid_until(changeset, attrs) do + expires_in = + Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in())) + + changeset + |> change(%{valid_until: expires_in}) + |> validate_required([:valid_until]) + end + + @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} + def create(%App{} = app, %User{} = user, attrs \\ %{}) do + with {:ok, token} <- do_create(app, user, attrs) do + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + end + + {:ok, token} + end + end + + defp do_create(app, user, attrs) do + %__MODULE__{user_id: user.id, app_id: app.id} + |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes]) + |> validate_required([:scopes, :app_id]) + |> put_valid_until(attrs) + |> put_token() + |> put_refresh_token(attrs) + |> Repo.insert() + end + + def delete_user_tokens(%User{id: user_id}) do + Query.get_by_user(user_id) + |> Repo.delete_all() + end + + def delete_user_token(%User{id: user_id}, token_id) do + Query.get_by_user(user_id) + |> Query.get_by_id(token_id) + |> Repo.delete_all() + end + + def get_user_tokens(%User{id: user_id}) do + Query.get_by_user(user_id) + |> Query.preload([:app]) + |> Repo.all() + end + + def is_expired?(%__MODULE__{valid_until: valid_until}) do + NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 + end + + def is_expired?(_), do: false + + defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) +end diff --git a/lib/pleroma/web/o_auth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex new file mode 100644 index 000000000..fd6d9b112 --- /dev/null +++ b/lib/pleroma/web/o_auth/token/query.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 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_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/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex new file mode 100644 index 000000000..625b0fde2 --- /dev/null +++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex @@ -0,0 +1,58 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do + @moduledoc """ + Functions for dealing with refresh token strategy. + """ + + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Strategy.Revoke + + @doc """ + Will grant access token by refresh token. + """ + @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()} + def grant(token) do + access_token = Repo.preload(token, [:user, :app]) + + result = + Repo.transaction(fn -> + token_params = %{ + app: access_token.app, + user: access_token.user, + scopes: access_token.scopes + } + + access_token + |> revoke_access_token() + |> create_access_token(token_params) + end) + + case result do + {:ok, {:error, reason}} -> {:error, reason} + {:ok, {:ok, token}} -> {:ok, token} + {:error, reason} -> {:error, reason} + end + end + + defp revoke_access_token(token) do + Revoke.revoke(token) + end + + defp create_access_token({:error, error}, _), do: {:error, error} + + defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do + Token.create(app, user, add_refresh_token(token_params, token.refresh_token)) + end + + defp add_refresh_token(params, token) do + case Config.get([:oauth2, :issue_new_refresh_token], false) do + true -> Map.put(params, :refresh_token, token) + false -> params + end + end +end diff --git a/lib/pleroma/web/o_auth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex new file mode 100644 index 000000000..069c1ee21 --- /dev/null +++ b/lib/pleroma/web/o_auth/token/strategy/revoke.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do + @moduledoc """ + Functions for dealing with revocation. + """ + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Token + + @doc "Finds and revokes access token for app and by token" + @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()} + def revoke(%App{} = app, %{"token" => token} = _attrs) do + with {:ok, token} <- Token.get_by_token(app, token), + do: revoke(token) + end + + @doc "Revokes access token" + @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} + def revoke(%Token{} = token) do + Repo.delete(token) + end +end diff --git a/lib/pleroma/web/o_auth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex new file mode 100644 index 000000000..43aeab6b0 --- /dev/null +++ b/lib/pleroma/web/o_auth/token/utils.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.Utils do + @moduledoc """ + Auxiliary functions for dealing with tokens. + """ + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + + @doc "Fetch app by client credentials from request" + @spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found} + def fetch_app(conn) do + res = + conn + |> fetch_client_credentials() + |> fetch_client + + case res do + %App{} = app -> {:ok, app} + _ -> {:error, :not_found} + end + end + + defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do + Repo.get_by(App, client_id: id, client_secret: secret) + end + + defp fetch_client({_id, _secret}), do: nil + + defp fetch_client_credentials(conn) do + # Per RFC 6749, HTTP Basic is preferred to body params + with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"), + {:ok, decoded} <- Base.decode64(encoded), + [id, secret] <- + Enum.map( + String.split(decoded, ":"), + fn s -> URI.decode_www_form(s) end + ) do + {id, secret} + else + _ -> {conn.params["client_id"], conn.params["client_secret"]} + end + end + + @doc "convert token inserted_at to unix timestamp" + def format_created_at(%{inserted_at: inserted_at} = _token) do + inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> DateTime.to_unix() + end + + @doc false + @spec generate_token(keyword()) :: binary() + def generate_token(opts \\ []) do + opts + |> Keyword.get(:size, 32) + |> :crypto.strong_rand_bytes() + |> Base.url_encode64(padding: false) + end + + # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be + # decoding it. Investigate sometime. + def fix_padding(token) do + token + |> URI.decode() + |> Base.url_decode64!(padding: false) + |> Base.url_encode64(padding: false) + end +end diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/oauth.ex deleted file mode 100644 index 2f1b8708d..000000000 --- a/lib/pleroma/web/oauth.ex +++ /dev/null @@ -1,6 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth do -end diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex deleted file mode 100644 index df99472e1..000000000 --- a/lib/pleroma/web/oauth/app.ex +++ /dev/null @@ -1,149 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.App do - use Ecto.Schema - import Ecto.Changeset - import Ecto.Query - alias Pleroma.Repo - - @type t :: %__MODULE__{} - - schema "apps" do - field(:client_name, :string) - field(:redirect_uris, :string) - field(:scopes, {:array, :string}, default: []) - field(:website, :string) - field(:client_id, :string) - field(:client_secret, :string) - field(:trusted, :boolean, default: false) - - has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all) - has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all) - - timestamps() - end - - @spec changeset(t(), map()) :: Ecto.Changeset.t() - def changeset(struct, params) do - cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted]) - end - - @spec register_changeset(t(), map()) :: Ecto.Changeset.t() - def register_changeset(struct, params \\ %{}) do - changeset = - struct - |> changeset(params) - |> validate_required([:client_name, :redirect_uris, :scopes]) - - if changeset.valid? do - changeset - |> put_change( - :client_id, - :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) - ) - |> put_change( - :client_secret, - :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) - ) - else - changeset - end - end - - @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - def create(params) do - %__MODULE__{} - |> register_changeset(params) - |> Repo.insert() - end - - @spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - def update(id, params) do - with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do - app - |> changeset(params) - |> Repo.update() - end - end - - @doc """ - Gets app by attrs or create new with attrs. - And updates the scopes if need. - """ - @spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - def get_or_make(attrs, scopes) do - with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do - update_scopes(app, scopes) - else - _e -> - %__MODULE__{} - |> register_changeset(Map.put(attrs, :scopes, scopes)) - |> Repo.insert() - end - end - - defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app} - defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app} - - defp update_scopes(%__MODULE__{} = app, scopes) do - app - |> change(%{scopes: scopes}) - |> Repo.update() - end - - @spec search(map()) :: {:ok, [t()], non_neg_integer()} - def search(params) do - query = from(a in __MODULE__) - - query = - if params[:client_name] do - from(a in query, where: a.client_name == ^params[:client_name]) - else - query - end - - query = - if params[:client_id] do - from(a in query, where: a.client_id == ^params[:client_id]) - else - query - end - - query = - if Map.has_key?(params, :trusted) do - from(a in query, where: a.trusted == ^params[:trusted]) - else - query - end - - query = - from(u in query, - limit: ^params[:page_size], - offset: ^((params[:page] - 1) * params[:page_size]) - ) - - count = Repo.aggregate(__MODULE__, :count, :id) - - {:ok, Repo.all(query), count} - end - - @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - def destroy(id) do - with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do - Repo.delete(app) - end - end - - @spec errors(Ecto.Changeset.t()) :: map() - def errors(changeset) do - Enum.reduce(changeset.errors, %{}, fn - {:client_name, {error, _}}, acc -> - Map.put(acc, :name, error) - - {key, {error, _}}, acc -> - Map.put(acc, key, error) - end) - end -end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex deleted file mode 100644 index 268ee5b63..000000000 --- a/lib/pleroma/web/oauth/authorization.ex +++ /dev/null @@ -1,95 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Authorization do - use Ecto.Schema - - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Authorization - - import Ecto.Changeset - import Ecto.Query - - @type t :: %__MODULE__{} - - schema "oauth_authorizations" do - field(:token, :string) - field(:scopes, {:array, :string}, default: []) - field(:valid_until, :naive_datetime_usec) - field(:used, :boolean, default: false) - belongs_to(:user, User, type: FlakeId.Ecto.CompatType) - belongs_to(:app, App) - - timestamps() - end - - @spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) :: - {:ok, Authorization.t()} | {:error, Changeset.t()} - def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do - %{ - scopes: scopes || app.scopes, - user_id: user.id, - app_id: app.id - } - |> create_changeset() - |> Repo.insert() - end - - @spec create_changeset(map()) :: Changeset.t() - def create_changeset(attrs \\ %{}) do - %Authorization{} - |> cast(attrs, [:user_id, :app_id, :scopes, :valid_until]) - |> validate_required([:app_id, :scopes]) - |> add_token() - |> add_lifetime() - end - - defp add_token(changeset) do - token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) - put_change(changeset, :token, token) - end - - defp add_lifetime(changeset) do - put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) - end - - @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() - def use_changeset(%Authorization{} = auth, params) do - auth - |> cast(params, [:used]) - |> validate_required([:used]) - end - - @spec use_token(Authorization.t()) :: - {:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()} - def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do - if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do - Repo.update(use_changeset(auth, %{used: true})) - else - {:error, "token expired"} - end - end - - def use_token(%Authorization{used: true}), do: {:error, "already used"} - - @spec delete_user_authorizations(User.t()) :: {integer(), any()} - def delete_user_authorizations(%User{} = user) do - user - |> delete_by_user_query - |> Repo.delete_all() - end - - def delete_by_user_query(%User{id: user_id}) do - from(a in __MODULE__, where: a.user_id == ^user_id) - end - - @doc "gets auth for app by 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) - |> Repo.find_resource() - end -end diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex deleted file mode 100644 index a89ced886..000000000 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ /dev/null @@ -1,32 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.FallbackController do - use Pleroma.Web, :controller - alias Pleroma.Web.OAuth.OAuthController - - def call(conn, {:register, :generic_error}) do - conn - |> put_status(:internal_server_error) - |> put_flash( - :error, - dgettext("errors", "Unknown error, please check the details and try again.") - ) - |> OAuthController.registration_details(conn.params) - end - - def call(conn, {:register, _error}) do - conn - |> put_status(:unauthorized) - |> put_flash(:error, dgettext("errors", "Invalid Username/Password")) - |> OAuthController.registration_details(conn.params) - end - - def call(conn, _error) do - conn - |> put_status(:unauthorized) - |> put_flash(:error, dgettext("errors", "Invalid Username/Password")) - |> OAuthController.authorize(conn.params) - end -end diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/oauth/mfa_controller.ex deleted file mode 100644 index f102c93e7..000000000 --- a/lib/pleroma/web/oauth/mfa_controller.ex +++ /dev/null @@ -1,98 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.MFAController do - @moduledoc """ - The model represents api to use Multi Factor authentications. - """ - - use Pleroma.Web, :controller - - alias Pleroma.MFA - alias Pleroma.Web.Auth.TOTPAuthenticator - alias Pleroma.Web.OAuth.MFAView, as: View - alias Pleroma.Web.OAuth.OAuthController - alias Pleroma.Web.OAuth.OAuthView - alias Pleroma.Web.OAuth.Token - - plug(:fetch_session when action in [:show, :verify]) - plug(:fetch_flash when action in [:show, :verify]) - - @doc """ - Display form to input mfa code or recovery code. - """ - def show(conn, %{"mfa_token" => mfa_token} = params) do - template = Map.get(params, "challenge_type", "totp") - - conn - |> put_view(View) - |> render("#{template}.html", %{ - mfa_token: mfa_token, - redirect_uri: params["redirect_uri"], - state: params["state"] - }) - end - - @doc """ - Verification code and continue authorization. - """ - def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do - with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), - {:ok, _} <- validates_challenge(user, mfa_params) do - conn - |> OAuthController.after_create_authorization(auth, %{ - "authorization" => %{ - "redirect_uri" => mfa_params["redirect_uri"], - "state" => mfa_params["state"] - } - }) - else - _ -> - conn - |> put_flash(:error, "Two-factor authentication failed.") - |> put_status(:unauthorized) - |> show(mfa_params) - end - end - - @doc """ - Verification second step of MFA (or recovery) and returns access token. - - ## Endpoint - POST /oauth/mfa/challenge - - params: - `client_id` - `client_secret` - `mfa_token` - access token to check second step of mfa - `challenge_type` - 'totp' or 'recovery' - `code` - - """ - def challenge(conn, %{"mfa_token" => mfa_token} = params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), - {:ok, _} <- validates_challenge(user, params), - {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) - else - _error -> - conn - |> put_status(400) - |> json(%{error: "Invalid code"}) - end - end - - # Verify TOTP Code - defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do - TOTPAuthenticator.verify(code, user) - end - - # Verify Recovery Code - defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do - TOTPAuthenticator.verify_recovery_code(user, code) - end - - defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type} -end diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/oauth/mfa_view.ex deleted file mode 100644 index 5d87db268..000000000 --- a/lib/pleroma/web/oauth/mfa_view.ex +++ /dev/null @@ -1,17 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.MFAView do - use Pleroma.Web, :view - import Phoenix.HTML.Form - alias Pleroma.MFA - - def render("mfa_response.json", %{token: token, user: user}) do - %{ - error: "mfa_required", - mfa_token: token.token, - supported_challenge_types: MFA.supported_methods(user) - } - end -end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex deleted file mode 100644 index a4152e840..000000000 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ /dev/null @@ -1,610 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.OAuthController do - use Pleroma.Web, :controller - - alias Pleroma.Helpers.UriHelper - alias Pleroma.Maps - alias Pleroma.MFA - alias Pleroma.Plugs.RateLimiter - alias Pleroma.Registration - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.Auth.Authenticator - alias Pleroma.Web.ControllerHelper - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.MFAController - alias Pleroma.Web.OAuth.MFAView - alias Pleroma.Web.OAuth.OAuthView - alias Pleroma.Web.OAuth.Scopes - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken - alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken - - require Logger - - if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth) - - plug(:fetch_session) - plug(:fetch_flash) - - plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]) - - plug(RateLimiter, [name: :authentication] when action == :create_authorization) - - action_fallback(Pleroma.Web.OAuth.FallbackController) - - @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob" - - # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg - def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do - {auth_attrs, params} = Map.pop(params, "authorization") - authorize(conn, Map.merge(params, auth_attrs)) - end - - def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do - if ControllerHelper.truthy_param?(params["force_login"]) do - do_authorize(conn, params) - else - handle_existing_authorization(conn, params) - end - end - - # Note: the token is set in oauth_plug, but the token and client do not always go together. - # For example, MastodonFE's token is set if user requests with another client, - # after user already authorized to MastodonFE. - # So we have to check client and token. - def authorize( - %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, - %{"client_id" => client_id} = params - ) do - with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app), - ^client_id <- t.app.client_id do - handle_existing_authorization(conn, params) - else - _ -> do_authorize(conn, params) - end - end - - def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params) - - defp do_authorize(%Plug.Conn{} = conn, params) do - app = Repo.get_by(App, client_id: params["client_id"]) - available_scopes = (app && app.scopes) || [] - scopes = Scopes.fetch_scopes(params, available_scopes) - - scopes = - if scopes == [] do - available_scopes - else - scopes - end - - # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template - render(conn, Authenticator.auth_template(), %{ - response_type: params["response_type"], - client_id: params["client_id"], - available_scopes: available_scopes, - scopes: scopes, - redirect_uri: params["redirect_uri"], - state: params["state"], - params: params - }) - end - - defp handle_existing_authorization( - %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, - %{"redirect_uri" => @oob_token_redirect_uri} - ) do - render(conn, "oob_token_exists.html", %{token: token}) - end - - defp handle_existing_authorization( - %Plug.Conn{assigns: %{token: %Token{} = token}} = conn, - %{} = params - ) do - app = Repo.preload(token, :app).app - - redirect_uri = - if is_binary(params["redirect_uri"]) do - params["redirect_uri"] - else - default_redirect_uri(app) - end - - if redirect_uri in String.split(app.redirect_uris) do - redirect_uri = redirect_uri(conn, redirect_uri) - url_params = %{access_token: token.token} - url_params = Maps.put_if_present(url_params, :state, params["state"]) - url = UriHelper.modify_uri_params(redirect_uri, url_params) - redirect(conn, external: url) - else - conn - |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) - |> redirect(external: redirect_uri(conn, redirect_uri)) - end - end - - def create_authorization( - %Plug.Conn{} = conn, - %{"authorization" => _} = params, - opts \\ [] - ) do - with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), - {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do - after_create_authorization(conn, auth, params) - else - error -> - handle_create_authorization_error(conn, error, params) - end - end - - def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ - "authorization" => %{"redirect_uri" => @oob_token_redirect_uri} - }) do - # Enforcing the view to reuse the template when calling from other controllers - conn - |> put_view(OAuthView) - |> render("oob_authorization_created.html", %{auth: auth}) - end - - def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ - "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs - }) do - app = Repo.preload(auth, :app).app - - # An extra safety measure before we redirect (also done in `do_create_authorization/2`) - if redirect_uri in String.split(app.redirect_uris) do - redirect_uri = redirect_uri(conn, redirect_uri) - url_params = %{code: auth.token} - url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"]) - url = UriHelper.modify_uri_params(redirect_uri, url_params) - redirect(conn, external: url) - else - conn - |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) - |> redirect(external: redirect_uri(conn, redirect_uri)) - end - end - - defp handle_create_authorization_error( - %Plug.Conn{} = conn, - {:error, scopes_issue}, - %{"authorization" => _} = params - ) - when scopes_issue in [:unsupported_scopes, :missing_scopes] do - # Per https://github.com/tootsuite/mastodon/blob/ - # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39 - conn - |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes")) - |> put_status(:unauthorized) - |> authorize(params) - end - - defp handle_create_authorization_error( - %Plug.Conn{} = conn, - {:account_status, :confirmation_pending}, - %{"authorization" => _} = params - ) do - conn - |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) - |> put_status(:forbidden) - |> authorize(params) - end - - defp handle_create_authorization_error( - %Plug.Conn{} = conn, - {:mfa_required, user, auth, _}, - params - ) do - {:ok, token} = MFA.Token.create(user, auth) - - data = %{ - "mfa_token" => token.token, - "redirect_uri" => params["authorization"]["redirect_uri"], - "state" => params["authorization"]["state"] - } - - MFAController.show(conn, data) - end - - defp handle_create_authorization_error( - %Plug.Conn{} = conn, - {:account_status, :password_reset_pending}, - %{"authorization" => _} = params - ) do - conn - |> put_flash(:error, dgettext("errors", "Password reset is required")) - |> put_status(:forbidden) - |> authorize(params) - end - - defp handle_create_authorization_error( - %Plug.Conn{} = conn, - {:account_status, :deactivated}, - %{"authorization" => _} = params - ) do - conn - |> put_flash(:error, dgettext("errors", "Your account is currently disabled")) - |> put_status(:forbidden) - |> authorize(params) - end - - defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do - Authenticator.handle_error(conn, error) - end - - @doc "Renew access_token with refresh_token" - def token_exchange( - %Plug.Conn{} = conn, - %{"grant_type" => "refresh_token", "refresh_token" => token} = _params - ) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token), - {:ok, token} <- RefreshToken.grant(token) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) - else - _error -> render_invalid_credentials_error(conn) - end - end - - def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - fixed_token = Token.Utils.fix_padding(params["code"]), - {:ok, auth} <- Authorization.get_by_token(app, fixed_token), - %User{} = user <- User.get_cached_by_id(auth.user_id), - {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) - else - error -> - handle_token_exchange_error(conn, error) - end - end - - def token_exchange( - %Plug.Conn{} = conn, - %{"grant_type" => "password"} = params - ) do - with {:ok, %User{} = user} <- Authenticator.get_user(conn), - {:ok, app} <- Token.Utils.fetch_app(conn), - requested_scopes <- Scopes.fetch_scopes(params, app.scopes), - {:ok, token} <- login(user, app, requested_scopes) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) - else - error -> - handle_token_exchange_error(conn, error) - end - end - - def token_exchange( - %Plug.Conn{} = conn, - %{"grant_type" => "password", "name" => name, "password" => _password} = params - ) do - params = - params - |> Map.delete("name") - |> Map.put("username", name) - - token_exchange(conn, params) - end - - def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, auth} <- Authorization.create_authorization(app, %User{}), - {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{token: token})) - else - _error -> - handle_token_exchange_error(conn, :invalid_credentails) - end - end - - # Bad request - def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) - - defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do - conn - |> put_status(:forbidden) - |> json(build_and_response_mfa_token(user, auth)) - end - - defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do - render_error( - conn, - :forbidden, - "Your account is currently disabled", - %{}, - "account_is_disabled" - ) - end - - defp handle_token_exchange_error( - %Plug.Conn{} = conn, - {:account_status, :password_reset_pending} - ) do - render_error( - conn, - :forbidden, - "Password reset is required", - %{}, - "password_reset_required" - ) - end - - defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do - render_error( - conn, - :forbidden, - "Your login is missing a confirmed e-mail address", - %{}, - "missing_confirmed_email" - ) - end - - defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do - render_error( - conn, - :forbidden, - "Your account is awaiting approval.", - %{}, - "awaiting_approval" - ) - end - - defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do - render_invalid_credentials_error(conn) - end - - def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, _token} <- RevokeToken.revoke(app, params) do - json(conn, %{}) - else - _error -> - # RFC 7009: invalid tokens [in the request] do not cause an error response - json(conn, %{}) - end - end - - def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params) - - # Response for bad request - defp bad_request(%Plug.Conn{} = conn, _) do - render_error(conn, :internal_server_error, "Bad request") - end - - @doc "Prepares OAuth request to provider for Ueberauth" - def prepare_request(%Plug.Conn{} = conn, %{ - "provider" => provider, - "authorization" => auth_attrs - }) do - scope = - auth_attrs - |> Scopes.fetch_scopes([]) - |> Scopes.to_string() - - state = - auth_attrs - |> Map.delete("scopes") - |> Map.put("scope", scope) - |> Jason.encode!() - - params = - auth_attrs - |> Map.drop(~w(scope scopes client_id redirect_uri)) - |> Map.put("state", state) - - # Handing the request to Ueberauth - redirect(conn, to: o_auth_path(conn, :request, provider, params)) - end - - def request(%Plug.Conn{} = conn, params) do - message = - if params["provider"] do - dgettext("errors", "Unsupported OAuth provider: %{provider}.", - provider: params["provider"] - ) - else - dgettext("errors", "Bad OAuth request.") - end - - conn - |> put_flash(:error, message) - |> redirect(to: "/") - end - - def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do - params = callback_params(params) - messages = for e <- Map.get(failure, :errors, []), do: e.message - message = Enum.join(messages, "; ") - - conn - |> put_flash( - :error, - dgettext("errors", "Failed to authenticate: %{message}.", message: message) - ) - |> redirect(external: redirect_uri(conn, params["redirect_uri"])) - end - - def callback(%Plug.Conn{} = conn, params) do - params = callback_params(params) - - with {:ok, registration} <- Authenticator.get_registration(conn) do - auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state)) - - case Repo.get_assoc(registration, :user) do - {:ok, user} -> - create_authorization(conn, %{"authorization" => auth_attrs}, user: user) - - _ -> - registration_params = - Map.merge(auth_attrs, %{ - "nickname" => Registration.nickname(registration), - "email" => Registration.email(registration) - }) - - conn - |> put_session_registration_id(registration.id) - |> registration_details(%{"authorization" => registration_params}) - end - else - error -> - Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns])) - - conn - |> put_flash(:error, dgettext("errors", "Failed to set up user account.")) - |> redirect(external: redirect_uri(conn, params["redirect_uri"])) - end - end - - defp callback_params(%{"state" => state} = params) do - Map.merge(params, Jason.decode!(state)) - end - - def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do - render(conn, "register.html", %{ - client_id: auth_attrs["client_id"], - redirect_uri: auth_attrs["redirect_uri"], - state: auth_attrs["state"], - scopes: Scopes.fetch_scopes(auth_attrs, []), - nickname: auth_attrs["nickname"], - email: auth_attrs["email"] - }) - end - - def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do - with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), - %Registration{} = registration <- Repo.get(Registration, registration_id), - {_, {:ok, auth, _user}} <- - {:create_authorization, do_create_authorization(conn, params)}, - %User{} = user <- Repo.preload(auth, :user).user, - {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do - conn - |> put_session_registration_id(nil) - |> after_create_authorization(auth, params) - else - {:create_authorization, error} -> - {:register, handle_create_authorization_error(conn, error, params)} - - _ -> - {:register, :generic_error} - end - end - - def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do - with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn), - %Registration{} = registration <- Repo.get(Registration, registration_id), - {:ok, user} <- Authenticator.create_from_registration(conn, registration) do - conn - |> put_session_registration_id(nil) - |> create_authorization( - params, - user: user - ) - else - {:error, changeset} -> - message = - Enum.map(changeset.errors, fn {field, {error, _}} -> - "#{field} #{error}" - end) - |> Enum.join("; ") - - message = - String.replace( - message, - "ap_id has already been taken", - "nickname has already been taken" - ) - - conn - |> put_status(:forbidden) - |> put_flash(:error, "Error: #{message}.") - |> registration_details(params) - - _ -> - {:register, :generic_error} - end - end - - defp do_create_authorization(conn, auth_attrs, user \\ nil) - - defp do_create_authorization( - %Plug.Conn{} = conn, - %{ - "authorization" => - %{ - "client_id" => client_id, - "redirect_uri" => redirect_uri - } = auth_attrs - }, - user - ) do - with {_, {:ok, %User{} = user}} <- - {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, - %App{} = app <- Repo.get_by(App, client_id: client_id), - true <- redirect_uri in String.split(app.redirect_uris), - requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes), - {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do - {:ok, auth, user} - end - end - - defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes) - when is_list(requested_scopes) do - with {:account_status, :active} <- {:account_status, User.account_status(user)}, - {:ok, scopes} <- validate_scopes(app, requested_scopes), - {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do - {:ok, auth} - end - end - - # Note: intended to be a private function but opened for AccountController that logs in on signup - @doc "If checks pass, creates authorization and token for given user, app and requested scopes." - def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do - with {:ok, auth} <- do_create_authorization(user, app, requested_scopes), - {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, - {:ok, token} <- Token.exchange_token(app, auth) do - {:ok, token} - end - end - - # Special case: Local MastodonFE - defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) - - defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri - - defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id) - - defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), - do: put_session(conn, :registration_id, registration_id) - - defp build_and_response_mfa_token(user, auth) do - with {:ok, token} <- MFA.Token.create(user, auth) do - MFAView.render("mfa_response.json", %{token: token, user: user}) - end - end - - @spec validate_scopes(App.t(), map() | list()) :: - {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params) when is_map(params) do - requested_scopes = Scopes.fetch_scopes(params, app.scopes) - validate_scopes(app, requested_scopes) - end - - defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do - Scopes.validate(requested_scopes, app.scopes) - end - - def default_redirect_uri(%App{} = app) do - app.redirect_uris - |> String.split() - |> Enum.at(0) - end - - defp render_invalid_credentials_error(conn) do - render_error(conn, :bad_request, "Invalid credentials") - end -end diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex deleted file mode 100644 index f55247ebd..000000000 --- a/lib/pleroma/web/oauth/oauth_view.ex +++ /dev/null @@ -1,30 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.OAuthView do - use Pleroma.Web, :view - import Phoenix.HTML.Form - - alias Pleroma.Web.OAuth.Token.Utils - - def render("token.json", %{token: token} = opts) do - response = %{ - token_type: "Bearer", - access_token: token.token, - refresh_token: token.refresh_token, - expires_in: expires_in(), - scope: Enum.join(token.scopes, " "), - created_at: Utils.format_created_at(token) - } - - if user = opts[:user] do - response - |> Map.put(:me, user.ap_id) - else - response - end - end - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) -end diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex deleted file mode 100644 index 6f06f1431..000000000 --- a/lib/pleroma/web/oauth/scopes.ex +++ /dev/null @@ -1,76 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Scopes do - @moduledoc """ - Functions for dealing with scopes. - """ - - alias Pleroma.Plugs.OAuthScopesPlug - - @doc """ - Fetch scopes from request params. - - Note: `scopes` is used by Mastodon — supporting it but sticking to - OAuth's standard `scope` wherever we control it - """ - @spec fetch_scopes(map() | struct(), list()) :: list() - - def fetch_scopes(params, default) do - parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default) - end - - def parse_scopes(scopes, _default) when is_list(scopes) do - Enum.filter(scopes, &(&1 not in [nil, ""])) - end - - def parse_scopes(scopes, default) when is_binary(scopes) do - scopes - |> to_list - |> parse_scopes(default) - end - - def parse_scopes(_, default) do - default - end - - @doc """ - Convert scopes string to list - """ - @spec to_list(binary()) :: [binary()] - def to_list(nil), do: [] - - def to_list(str) do - str - |> String.trim() - |> String.split(~r/[\s,]+/) - end - - @doc """ - Convert scopes list to string - """ - @spec to_string(list()) :: binary() - def to_string(scopes), do: Enum.join(scopes, " ") - - @doc """ - Validates scopes. - """ - @spec validate(list() | nil, list()) :: - {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []], - do: {:error, :missing_scopes} - - def validate(scopes, app_scopes) do - case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do - ^scopes -> {:ok, scopes} - _ -> {:error, :unsupported_scopes} - end - end - - def contains_admin_scopes?(scopes) do - scopes - |> OAuthScopesPlug.filter_descendants(["admin"]) - |> Enum.any?() - end -end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex deleted file mode 100644 index de37998f2..000000000 --- a/lib/pleroma/web/oauth/token.ex +++ /dev/null @@ -1,135 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token do - use Ecto.Schema - - import Ecto.Changeset - - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.OAuth.Token.Query - - @type t :: %__MODULE__{} - - schema "oauth_tokens" do - field(:token, :string) - field(:refresh_token, :string) - field(:scopes, {:array, :string}, default: []) - field(:valid_until, :naive_datetime_usec) - belongs_to(:user, User, type: FlakeId.Ecto.CompatType) - belongs_to(:app, App) - - timestamps() - end - - @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 - 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 - Query.get_by_app(app_id) - |> Query.get_by_refresh_token(token) - |> Query.preload([:user]) - |> Repo.find_resource() - end - - @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()} - def exchange_token(app, auth) do - with {:ok, auth} <- Authorization.use_token(auth), - true <- auth.app_id == app.id do - user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{} - - create( - app, - user, - %{scopes: auth.scopes} - ) - end - end - - defp put_token(changeset) do - changeset - |> change(%{token: Token.Utils.generate_token()}) - |> validate_required([:token]) - |> unique_constraint(:token) - end - - defp put_refresh_token(changeset, attrs) do - refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token()) - - changeset - |> change(%{refresh_token: refresh_token}) - |> validate_required([:refresh_token]) - |> unique_constraint(:refresh_token) - end - - defp put_valid_until(changeset, attrs) do - expires_in = - Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in())) - - changeset - |> change(%{valid_until: expires_in}) - |> validate_required([:valid_until]) - end - - @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} - def create(%App{} = app, %User{} = user, attrs \\ %{}) do - with {:ok, token} <- do_create(app, user, attrs) do - if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ - token_id: token.id, - valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), - mod: __MODULE__ - }) - end - - {:ok, token} - end - end - - defp do_create(app, user, attrs) do - %__MODULE__{user_id: user.id, app_id: app.id} - |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes]) - |> validate_required([:scopes, :app_id]) - |> put_valid_until(attrs) - |> put_token() - |> put_refresh_token(attrs) - |> Repo.insert() - end - - def delete_user_tokens(%User{id: user_id}) do - Query.get_by_user(user_id) - |> Repo.delete_all() - end - - def delete_user_token(%User{id: user_id}, token_id) do - Query.get_by_user(user_id) - |> Query.get_by_id(token_id) - |> Repo.delete_all() - end - - def get_user_tokens(%User{id: user_id}) do - Query.get_by_user(user_id) - |> Query.preload([:app]) - |> Repo.all() - end - - def is_expired?(%__MODULE__{valid_until: valid_until}) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 - end - - def is_expired?(_), do: false - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) -end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex deleted file mode 100644 index fd6d9b112..000000000 --- a/lib/pleroma/web/oauth/token/query.ex +++ /dev/null @@ -1,49 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 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_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/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex deleted file mode 100644 index 625b0fde2..000000000 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ /dev/null @@ -1,58 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do - @moduledoc """ - Functions for dealing with refresh token strategy. - """ - - alias Pleroma.Config - alias Pleroma.Repo - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.OAuth.Token.Strategy.Revoke - - @doc """ - Will grant access token by refresh token. - """ - @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()} - def grant(token) do - access_token = Repo.preload(token, [:user, :app]) - - result = - Repo.transaction(fn -> - token_params = %{ - app: access_token.app, - user: access_token.user, - scopes: access_token.scopes - } - - access_token - |> revoke_access_token() - |> create_access_token(token_params) - end) - - case result do - {:ok, {:error, reason}} -> {:error, reason} - {:ok, {:ok, token}} -> {:ok, token} - {:error, reason} -> {:error, reason} - end - end - - defp revoke_access_token(token) do - Revoke.revoke(token) - end - - defp create_access_token({:error, error}, _), do: {:error, error} - - defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do - Token.create(app, user, add_refresh_token(token_params, token.refresh_token)) - end - - defp add_refresh_token(params, token) do - case Config.get([:oauth2, :issue_new_refresh_token], false) do - true -> Map.put(params, :refresh_token, token) - false -> params - end - end -end diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex deleted file mode 100644 index 069c1ee21..000000000 --- a/lib/pleroma/web/oauth/token/strategy/revoke.ex +++ /dev/null @@ -1,26 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do - @moduledoc """ - Functions for dealing with revocation. - """ - - alias Pleroma.Repo - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Token - - @doc "Finds and revokes access token for app and by token" - @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()} - def revoke(%App{} = app, %{"token" => token} = _attrs) do - with {:ok, token} <- Token.get_by_token(app, token), - do: revoke(token) - end - - @doc "Revokes access token" - @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} - def revoke(%Token{} = token) do - Repo.delete(token) - end -end diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex deleted file mode 100644 index 43aeab6b0..000000000 --- a/lib/pleroma/web/oauth/token/utils.ex +++ /dev/null @@ -1,72 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.Utils do - @moduledoc """ - Auxiliary functions for dealing with tokens. - """ - - alias Pleroma.Repo - alias Pleroma.Web.OAuth.App - - @doc "Fetch app by client credentials from request" - @spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found} - def fetch_app(conn) do - res = - conn - |> fetch_client_credentials() - |> fetch_client - - case res do - %App{} = app -> {:ok, app} - _ -> {:error, :not_found} - end - end - - defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do - Repo.get_by(App, client_id: id, client_secret: secret) - end - - defp fetch_client({_id, _secret}), do: nil - - defp fetch_client_credentials(conn) do - # Per RFC 6749, HTTP Basic is preferred to body params - with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"), - {:ok, decoded} <- Base.decode64(encoded), - [id, secret] <- - Enum.map( - String.split(decoded, ":"), - fn s -> URI.decode_www_form(s) end - ) do - {id, secret} - else - _ -> {conn.params["client_id"], conn.params["client_secret"]} - end - end - - @doc "convert token inserted_at to unix timestamp" - def format_created_at(%{inserted_at: inserted_at} = _token) do - inserted_at - |> DateTime.from_naive!("Etc/UTC") - |> DateTime.to_unix() - end - - @doc false - @spec generate_token(keyword()) :: binary() - def generate_token(opts \\ []) do - opts - |> Keyword.get(:size, 32) - |> :crypto.strong_rand_bytes() - |> Base.url_encode64(padding: false) - end - - # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be - # decoding it. Investigate sometime. - def fix_padding(token) do - token - |> URI.decode() - |> Base.url_decode64!(padding: false) - |> Base.url_encode64(padding: false) - end -end -- cgit v1.2.3 From e8e4034c4879ebf0bb7fcc7606c97a3957a0ba06 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 23 Jun 2020 19:55:55 +0300 Subject: metadata providers consistency --- lib/pleroma/web/metadata/feed.ex | 23 ---- lib/pleroma/web/metadata/opengraph.ex | 119 --------------------- lib/pleroma/web/metadata/provider.ex | 7 -- lib/pleroma/web/metadata/providers/feed.ex | 23 ++++ lib/pleroma/web/metadata/providers/opengraph.ex | 119 +++++++++++++++++++++ lib/pleroma/web/metadata/providers/provider.ex | 7 ++ lib/pleroma/web/metadata/providers/rel_me.ex | 19 ++++ .../web/metadata/providers/restrict_indexing.ex | 24 +++++ lib/pleroma/web/metadata/providers/twitter_card.ex | 112 +++++++++++++++++++ lib/pleroma/web/metadata/rel_me.ex | 19 ---- lib/pleroma/web/metadata/restrict_indexing.ex | 24 ----- lib/pleroma/web/metadata/twitter_card.ex | 112 ------------------- 12 files changed, 304 insertions(+), 304 deletions(-) delete mode 100644 lib/pleroma/web/metadata/feed.ex delete mode 100644 lib/pleroma/web/metadata/opengraph.ex delete mode 100644 lib/pleroma/web/metadata/provider.ex create mode 100644 lib/pleroma/web/metadata/providers/feed.ex create mode 100644 lib/pleroma/web/metadata/providers/opengraph.ex create mode 100644 lib/pleroma/web/metadata/providers/provider.ex create mode 100644 lib/pleroma/web/metadata/providers/rel_me.ex create mode 100644 lib/pleroma/web/metadata/providers/restrict_indexing.ex create mode 100644 lib/pleroma/web/metadata/providers/twitter_card.ex delete mode 100644 lib/pleroma/web/metadata/rel_me.ex delete mode 100644 lib/pleroma/web/metadata/restrict_indexing.ex delete mode 100644 lib/pleroma/web/metadata/twitter_card.ex (limited to 'lib') diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex deleted file mode 100644 index bd1459a17..000000000 --- a/lib/pleroma/web/metadata/feed.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.Feed do - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Metadata.Providers.Provider - alias Pleroma.Web.Router.Helpers - - @behaviour Provider - - @impl Provider - def build_tags(%{user: user}) do - [ - {:link, - [ - rel: "alternate", - type: "application/atom+xml", - href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" - ], []} - ] - end -end diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex deleted file mode 100644 index bb1b23208..000000000 --- a/lib/pleroma/web/metadata/opengraph.ex +++ /dev/null @@ -1,119 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.OpenGraph do - alias Pleroma.User - alias Pleroma.Web.Metadata - alias Pleroma.Web.Metadata.Providers.Provider - alias Pleroma.Web.Metadata.Utils - - @behaviour Provider - @media_types ["image", "audio", "video"] - - @impl Provider - def build_tags(%{ - object: object, - url: url, - user: user - }) do - attachments = build_attachments(object) - scrubbed_content = Utils.scrub_html_and_truncate(object) - # Zero width space - content = - if scrubbed_content != "" and scrubbed_content != "\u200B" do - ": “" <> scrubbed_content <> "”" - else - "" - end - - # Most previews only show og:title which is inconvenient. Instagram - # hacks this by putting the description in the title and making the - # description longer prefixed by how many likes and shares the post - # has. Here we use the descriptive nickname in the title, and expand - # the full account & nickname in the description. We also use the cute^Wevil - # smart quotes around the status text like Instagram, too. - [ - {:meta, - [ - property: "og:title", - content: "#{user.name}" <> content - ], []}, - {:meta, [property: "og:url", content: url], []}, - {:meta, - [ - property: "og:description", - content: "#{Utils.user_name_string(user)}" <> content - ], []}, - {:meta, [property: "og:type", content: "website"], []} - ] ++ - if attachments == [] or Metadata.activity_nsfw?(object) do - [ - {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], - []}, - {:meta, [property: "og:image:width", content: 150], []}, - {:meta, [property: "og:image:height", content: 150], []} - ] - else - attachments - end - end - - @impl Provider - def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do - [ - {:meta, - [ - property: "og:title", - content: Utils.user_name_string(user) - ], []}, - {:meta, [property: "og:url", content: user.uri || user.ap_id], []}, - {:meta, [property: "og:description", content: truncated_bio], []}, - {:meta, [property: "og:type", content: "website"], []}, - {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []}, - {:meta, [property: "og:image:width", content: 150], []}, - {:meta, [property: "og:image:height", content: 150], []} - ] - end - end - - defp build_attachments(%{data: %{"attachment" => attachments}}) do - Enum.reduce(attachments, [], fn attachment, acc -> - rendered_tags = - Enum.reduce(attachment["url"], [], fn url, acc -> - # TODO: Add additional properties to objects when we have the data available. - # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image - # object when a Video or GIF is attached it will display that in Whatsapp Rich Preview. - case Utils.fetch_media_type(@media_types, url["mediaType"]) do - "audio" -> - [ - {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []} - | acc - ] - - "image" -> - [ - {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []}, - {:meta, [property: "og:image:width", content: 150], []}, - {:meta, [property: "og:image:height", content: 150], []} - | acc - ] - - "video" -> - [ - {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []} - | acc - ] - - _ -> - acc - end - end) - - acc ++ rendered_tags - end) - end - - defp build_attachments(_), do: [] -end diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/provider.ex deleted file mode 100644 index 767288f9c..000000000 --- a/lib/pleroma/web/metadata/provider.ex +++ /dev/null @@ -1,7 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.Provider do - @callback build_tags(map()) :: list() -end diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex new file mode 100644 index 000000000..bd1459a17 --- /dev/null +++ b/lib/pleroma/web/metadata/providers/feed.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.Feed do + alias Pleroma.Web.Endpoint + alias Pleroma.Web.Metadata.Providers.Provider + alias Pleroma.Web.Router.Helpers + + @behaviour Provider + + @impl Provider + def build_tags(%{user: user}) do + [ + {:link, + [ + rel: "alternate", + type: "application/atom+xml", + href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" + ], []} + ] + end +end diff --git a/lib/pleroma/web/metadata/providers/opengraph.ex b/lib/pleroma/web/metadata/providers/opengraph.ex new file mode 100644 index 000000000..bb1b23208 --- /dev/null +++ b/lib/pleroma/web/metadata/providers/opengraph.ex @@ -0,0 +1,119 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.OpenGraph do + alias Pleroma.User + alias Pleroma.Web.Metadata + alias Pleroma.Web.Metadata.Providers.Provider + alias Pleroma.Web.Metadata.Utils + + @behaviour Provider + @media_types ["image", "audio", "video"] + + @impl Provider + def build_tags(%{ + object: object, + url: url, + user: user + }) do + attachments = build_attachments(object) + scrubbed_content = Utils.scrub_html_and_truncate(object) + # Zero width space + content = + if scrubbed_content != "" and scrubbed_content != "\u200B" do + ": “" <> scrubbed_content <> "”" + else + "" + end + + # Most previews only show og:title which is inconvenient. Instagram + # hacks this by putting the description in the title and making the + # description longer prefixed by how many likes and shares the post + # has. Here we use the descriptive nickname in the title, and expand + # the full account & nickname in the description. We also use the cute^Wevil + # smart quotes around the status text like Instagram, too. + [ + {:meta, + [ + property: "og:title", + content: "#{user.name}" <> content + ], []}, + {:meta, [property: "og:url", content: url], []}, + {:meta, + [ + property: "og:description", + content: "#{Utils.user_name_string(user)}" <> content + ], []}, + {:meta, [property: "og:type", content: "website"], []} + ] ++ + if attachments == [] or Metadata.activity_nsfw?(object) do + [ + {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], + []}, + {:meta, [property: "og:image:width", content: 150], []}, + {:meta, [property: "og:image:height", content: 150], []} + ] + else + attachments + end + end + + @impl Provider + def build_tags(%{user: user}) do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do + [ + {:meta, + [ + property: "og:title", + content: Utils.user_name_string(user) + ], []}, + {:meta, [property: "og:url", content: user.uri || user.ap_id], []}, + {:meta, [property: "og:description", content: truncated_bio], []}, + {:meta, [property: "og:type", content: "website"], []}, + {:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []}, + {:meta, [property: "og:image:width", content: 150], []}, + {:meta, [property: "og:image:height", content: 150], []} + ] + end + end + + defp build_attachments(%{data: %{"attachment" => attachments}}) do + Enum.reduce(attachments, [], fn attachment, acc -> + rendered_tags = + Enum.reduce(attachment["url"], [], fn url, acc -> + # TODO: Add additional properties to objects when we have the data available. + # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image + # object when a Video or GIF is attached it will display that in Whatsapp Rich Preview. + case Utils.fetch_media_type(@media_types, url["mediaType"]) do + "audio" -> + [ + {:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []} + | acc + ] + + "image" -> + [ + {:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []}, + {:meta, [property: "og:image:width", content: 150], []}, + {:meta, [property: "og:image:height", content: 150], []} + | acc + ] + + "video" -> + [ + {:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []} + | acc + ] + + _ -> + acc + end + end) + + acc ++ rendered_tags + end) + end + + defp build_attachments(_), do: [] +end diff --git a/lib/pleroma/web/metadata/providers/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex new file mode 100644 index 000000000..767288f9c --- /dev/null +++ b/lib/pleroma/web/metadata/providers/provider.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.Provider do + @callback build_tags(map()) :: list() +end diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex new file mode 100644 index 000000000..8905c9c72 --- /dev/null +++ b/lib/pleroma/web/metadata/providers/rel_me.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.RelMe do + alias Pleroma.Web.Metadata.Providers.Provider + @behaviour Provider + + @impl Provider + def build_tags(%{user: user}) do + bio_tree = Floki.parse_fragment!(user.bio) + + (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ + Floki.attribute(bio_tree, "a[rel~=me]", "href")) + |> Enum.map(fn link -> + {:link, [rel: "me", href: link], []} + end) + end +end diff --git a/lib/pleroma/web/metadata/providers/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex new file mode 100644 index 000000000..a1dcb6e15 --- /dev/null +++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do + @behaviour Pleroma.Web.Metadata.Providers.Provider + + @moduledoc """ + Restricts indexing of remote users. + """ + + @impl true + def build_tags(%{user: %{local: true, discoverable: true}}), do: [] + + def build_tags(_) do + [ + {:meta, + [ + name: "robots", + content: "noindex, noarchive" + ], []} + ] + end +end diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex new file mode 100644 index 000000000..df34b033f --- /dev/null +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -0,0 +1,112 @@ +# Pleroma: A lightweight social networking server + +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.TwitterCard do + alias Pleroma.User + alias Pleroma.Web.Metadata + alias Pleroma.Web.Metadata.Providers.Provider + alias Pleroma.Web.Metadata.Utils + + @behaviour Provider + @media_types ["image", "audio", "video"] + + @impl Provider + def build_tags(%{activity_id: id, object: object, user: user}) do + attachments = build_attachments(id, object) + scrubbed_content = Utils.scrub_html_and_truncate(object) + # Zero width space + content = + if scrubbed_content != "" and scrubbed_content != "\u200B" do + "“" <> scrubbed_content <> "”" + else + "" + end + + [ + title_tag(user), + {:meta, [property: "twitter:description", content: content], []} + ] ++ + if attachments == [] or Metadata.activity_nsfw?(object) do + [ + image_tag(user), + {:meta, [property: "twitter:card", content: "summary"], []} + ] + else + attachments + end + end + + @impl Provider + def build_tags(%{user: user}) do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do + [ + title_tag(user), + {:meta, [property: "twitter:description", content: truncated_bio], []}, + image_tag(user), + {:meta, [property: "twitter:card", content: "summary"], []} + ] + end + end + + defp title_tag(user) do + {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []} + end + + def image_tag(user) do + {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []} + end + + defp build_attachments(id, %{data: %{"attachment" => attachments}}) do + Enum.reduce(attachments, [], fn attachment, acc -> + rendered_tags = + Enum.reduce(attachment["url"], [], fn url, acc -> + # TODO: Add additional properties to objects when we have the data available. + case Utils.fetch_media_type(@media_types, url["mediaType"]) do + "audio" -> + [ + {:meta, [property: "twitter:card", content: "player"], []}, + {:meta, [property: "twitter:player:width", content: "480"], []}, + {:meta, [property: "twitter:player:height", content: "80"], []}, + {:meta, [property: "twitter:player", content: player_url(id)], []} + | acc + ] + + "image" -> + [ + {:meta, [property: "twitter:card", content: "summary_large_image"], []}, + {:meta, + [ + property: "twitter:player", + content: Utils.attachment_url(url["href"]) + ], []} + | acc + ] + + # TODO: Need the true width and height values here or Twitter renders an iFrame with + # a bad aspect ratio + "video" -> + [ + {:meta, [property: "twitter:card", content: "player"], []}, + {:meta, [property: "twitter:player", content: player_url(id)], []}, + {:meta, [property: "twitter:player:width", content: "480"], []}, + {:meta, [property: "twitter:player:height", content: "480"], []} + | acc + ] + + _ -> + acc + end + end) + + acc ++ rendered_tags + end) + end + + defp build_attachments(_id, _object), do: [] + + defp player_url(id) do + Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id) + end +end diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex deleted file mode 100644 index 8905c9c72..000000000 --- a/lib/pleroma/web/metadata/rel_me.ex +++ /dev/null @@ -1,19 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.RelMe do - alias Pleroma.Web.Metadata.Providers.Provider - @behaviour Provider - - @impl Provider - def build_tags(%{user: user}) do - bio_tree = Floki.parse_fragment!(user.bio) - - (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ - Floki.attribute(bio_tree, "a[rel~=me]", "href")) - |> Enum.map(fn link -> - {:link, [rel: "me", href: link], []} - end) - end -end diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/restrict_indexing.ex deleted file mode 100644 index a1dcb6e15..000000000 --- a/lib/pleroma/web/metadata/restrict_indexing.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do - @behaviour Pleroma.Web.Metadata.Providers.Provider - - @moduledoc """ - Restricts indexing of remote users. - """ - - @impl true - def build_tags(%{user: %{local: true, discoverable: true}}), do: [] - - def build_tags(_) do - [ - {:meta, - [ - name: "robots", - content: "noindex, noarchive" - ], []} - ] - end -end diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex deleted file mode 100644 index df34b033f..000000000 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ /dev/null @@ -1,112 +0,0 @@ -# Pleroma: A lightweight social networking server - -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Metadata.Providers.TwitterCard do - alias Pleroma.User - alias Pleroma.Web.Metadata - alias Pleroma.Web.Metadata.Providers.Provider - alias Pleroma.Web.Metadata.Utils - - @behaviour Provider - @media_types ["image", "audio", "video"] - - @impl Provider - def build_tags(%{activity_id: id, object: object, user: user}) do - attachments = build_attachments(id, object) - scrubbed_content = Utils.scrub_html_and_truncate(object) - # Zero width space - content = - if scrubbed_content != "" and scrubbed_content != "\u200B" do - "“" <> scrubbed_content <> "”" - else - "" - end - - [ - title_tag(user), - {:meta, [property: "twitter:description", content: content], []} - ] ++ - if attachments == [] or Metadata.activity_nsfw?(object) do - [ - image_tag(user), - {:meta, [property: "twitter:card", content: "summary"], []} - ] - else - attachments - end - end - - @impl Provider - def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do - [ - title_tag(user), - {:meta, [property: "twitter:description", content: truncated_bio], []}, - image_tag(user), - {:meta, [property: "twitter:card", content: "summary"], []} - ] - end - end - - defp title_tag(user) do - {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []} - end - - def image_tag(user) do - {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []} - end - - defp build_attachments(id, %{data: %{"attachment" => attachments}}) do - Enum.reduce(attachments, [], fn attachment, acc -> - rendered_tags = - Enum.reduce(attachment["url"], [], fn url, acc -> - # TODO: Add additional properties to objects when we have the data available. - case Utils.fetch_media_type(@media_types, url["mediaType"]) do - "audio" -> - [ - {:meta, [property: "twitter:card", content: "player"], []}, - {:meta, [property: "twitter:player:width", content: "480"], []}, - {:meta, [property: "twitter:player:height", content: "80"], []}, - {:meta, [property: "twitter:player", content: player_url(id)], []} - | acc - ] - - "image" -> - [ - {:meta, [property: "twitter:card", content: "summary_large_image"], []}, - {:meta, - [ - property: "twitter:player", - content: Utils.attachment_url(url["href"]) - ], []} - | acc - ] - - # TODO: Need the true width and height values here or Twitter renders an iFrame with - # a bad aspect ratio - "video" -> - [ - {:meta, [property: "twitter:card", content: "player"], []}, - {:meta, [property: "twitter:player", content: player_url(id)], []}, - {:meta, [property: "twitter:player:width", content: "480"], []}, - {:meta, [property: "twitter:player:height", content: "480"], []} - | acc - ] - - _ -> - acc - end - end) - - acc ++ rendered_tags - end) - end - - defp build_attachments(_id, _object), do: [] - - defp player_url(id) do - Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id) - end -end -- cgit v1.2.3 From fc7151a9c4cbd2fb122d717f54de4b30acffea36 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 23 Jun 2020 20:02:53 +0300 Subject: more files renamings --- lib/phoenix/transports/web_socket/raw.ex | 89 ++++++ lib/pleroma/web.ex | 234 +++++++++++++++ .../web/mongoose_im/mongoose_im_controller.ex | 46 +++ .../web/mongooseim/mongoose_im_controller.ex | 46 --- lib/pleroma/web/o_status/o_status_controller.ex | 151 ++++++++++ lib/pleroma/web/ostatus/ostatus_controller.ex | 151 ---------- lib/pleroma/web/plug.ex | 8 + lib/pleroma/web/push.ex | 37 +++ lib/pleroma/web/push/push.ex | 37 --- lib/pleroma/web/rich_media/parsers/o_embed.ex | 29 ++ .../web/rich_media/parsers/oembed_parser.ex | 29 -- lib/pleroma/web/streamer.ex | 331 +++++++++++++++++++++ lib/pleroma/web/streamer/streamer.ex | 331 --------------------- lib/pleroma/web/twitter_api/controller.ex | 100 +++++++ .../web/twitter_api/twitter_api_controller.ex | 100 ------- lib/pleroma/web/web.ex | 239 --------------- lib/pleroma/web/web_finger.ex | 201 +++++++++++++ lib/pleroma/web/web_finger/web_finger.ex | 201 ------------- lib/pleroma/web/xml.ex | 45 +++ lib/pleroma/web/xml/xml.ex | 45 --- lib/pleroma/xml_builder.ex | 49 +++ lib/transports.ex | 89 ------ lib/xml_builder.ex | 49 --- 23 files changed, 1320 insertions(+), 1317 deletions(-) create mode 100644 lib/phoenix/transports/web_socket/raw.ex create mode 100644 lib/pleroma/web.ex create mode 100644 lib/pleroma/web/mongoose_im/mongoose_im_controller.ex delete mode 100644 lib/pleroma/web/mongooseim/mongoose_im_controller.ex create mode 100644 lib/pleroma/web/o_status/o_status_controller.ex delete mode 100644 lib/pleroma/web/ostatus/ostatus_controller.ex create mode 100644 lib/pleroma/web/plug.ex create mode 100644 lib/pleroma/web/push.ex delete mode 100644 lib/pleroma/web/push/push.ex create mode 100644 lib/pleroma/web/rich_media/parsers/o_embed.ex delete mode 100644 lib/pleroma/web/rich_media/parsers/oembed_parser.ex create mode 100644 lib/pleroma/web/streamer.ex delete mode 100644 lib/pleroma/web/streamer/streamer.ex create mode 100644 lib/pleroma/web/twitter_api/controller.ex delete mode 100644 lib/pleroma/web/twitter_api/twitter_api_controller.ex delete mode 100644 lib/pleroma/web/web.ex create mode 100644 lib/pleroma/web/web_finger.ex delete mode 100644 lib/pleroma/web/web_finger/web_finger.ex create mode 100644 lib/pleroma/web/xml.ex delete mode 100644 lib/pleroma/web/xml/xml.ex create mode 100644 lib/pleroma/xml_builder.ex delete mode 100644 lib/transports.ex delete mode 100644 lib/xml_builder.ex (limited to 'lib') diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex new file mode 100644 index 000000000..aab7fad99 --- /dev/null +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Phoenix.Transports.WebSocket.Raw do + import Plug.Conn, + only: [ + fetch_query_params: 1, + send_resp: 3 + ] + + alias Phoenix.Socket.Transport + + def default_config do + [ + timeout: 60_000, + transport_log: false, + cowboy: Phoenix.Endpoint.CowboyWebSocket + ] + end + + def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do + {_, opts} = handler.__transport__(transport) + + conn = + conn + |> fetch_query_params + |> Transport.transport_log(opts[:transport_log]) + |> Transport.force_ssl(handler, endpoint, opts) + |> Transport.check_origin(handler, endpoint, opts) + + case conn do + %{halted: false} = conn -> + case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do + {:ok, socket} -> + {:ok, conn, {__MODULE__, {socket, opts}}} + + :error -> + send_resp(conn, :forbidden, "") + {:error, conn} + end + + _ -> + {:error, conn} + end + end + + def init(conn, _) do + send_resp(conn, :bad_request, "") + {:error, conn} + end + + def ws_init({socket, config}) do + Process.flag(:trap_exit, true) + {:ok, %{socket: socket}, config[:timeout]} + end + + def ws_handle(op, data, state) do + state.socket.handler + |> apply(:handle, [op, data, state]) + |> case do + {op, data} -> + {:reply, {op, data}, state} + + {op, data, state} -> + {:reply, {op, data}, state} + + %{} = state -> + {:ok, state} + + _ -> + {:ok, state} + end + end + + def ws_info({_, _} = tuple, state) do + {:reply, tuple, state} + end + + def ws_info(_tuple, state), do: {:ok, state} + + def ws_close(state) do + ws_handle(:closed, :normal, state) + end + + def ws_terminate(reason, state) do + ws_handle(:closed, reason, state) + end +end diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex new file mode 100644 index 000000000..9ca52733d --- /dev/null +++ b/lib/pleroma/web.ex @@ -0,0 +1,234 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web do + @moduledoc """ + A module that keeps using definitions for controllers, + views and so on. + + This can be used in your application as: + + use Pleroma.Web, :controller + use Pleroma.Web, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. + """ + + alias Pleroma.Plugs.EnsureAuthenticatedPlug + alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug + alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.PlugHelper + + def controller do + quote do + use Phoenix.Controller, namespace: Pleroma.Web + + import Plug.Conn + + import Pleroma.Web.Gettext + import Pleroma.Web.Router.Helpers + import Pleroma.Web.TranslationHelpers + + plug(:set_put_layout) + + defp set_put_layout(conn, _) do + put_layout(conn, Pleroma.Config.get(:app_layout, "app.html")) + end + + # Marks plugs intentionally skipped and blocks their execution if present in plugs chain + defp skip_plug(conn, plug_modules) do + plug_modules + |> List.wrap() + |> Enum.reduce( + conn, + fn plug_module, conn -> + try do + plug_module.skip_plug(conn) + rescue + UndefinedFunctionError -> + raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code." + end + end + ) + end + + # Executed just before actual controller action, invokes before-action hooks (callbacks) + defp action(conn, params) do + with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn), + %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn), + %{halted: false} = conn <- maybe_perform_authenticated_check(conn), + %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do + super(conn, params) + end + end + + # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored + # (neither performed nor explicitly skipped) + defp maybe_drop_authentication_if_oauth_check_ignored(conn) do + if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and + not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do + OAuthScopesPlug.drop_auth_info(conn) + else + conn + end + end + + # Ensures instance is public -or- user is authenticated if such check was scheduled + defp maybe_perform_public_or_authenticated_check(conn) do + if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do + EnsurePublicOrAuthenticatedPlug.call(conn, %{}) + else + conn + end + end + + # Ensures user is authenticated if such check was scheduled + # Note: runs prior to action even if it was already executed earlier in plug chain + # (since OAuthScopesPlug has option of proceeding unauthenticated) + defp maybe_perform_authenticated_check(conn) do + if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do + EnsureAuthenticatedPlug.call(conn, %{}) + else + conn + end + end + + # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check + defp maybe_halt_on_missing_oauth_scopes_check(conn) do + if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and + not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do + conn + |> render_error( + :forbidden, + "Security violation: OAuth scopes check was neither handled nor explicitly skipped." + ) + |> halt() + else + conn + end + end + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/pleroma/web/templates", + namespace: Pleroma.Web + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] + + import Pleroma.Web.ErrorHelpers + import Pleroma.Web.Gettext + import Pleroma.Web.Router.Helpers + + require Logger + + @doc "Same as `render/3` but wrapped in a rescue block" + def safe_render(view, template, assigns \\ %{}) do + Phoenix.View.render(view, template, assigns) + rescue + error -> + Logger.error( + "#{__MODULE__} failed to render #{inspect({view, template})}\n" <> + Exception.format(:error, error, __STACKTRACE__) + ) + + nil + end + + @doc """ + Same as `render_many/4` but wrapped in rescue block. + """ + def safe_render_many(collection, view, template, assigns \\ %{}) do + Enum.map(collection, fn resource -> + as = Map.get(assigns, :as) || view.__resource__ + assigns = Map.put(assigns, as, resource) + safe_render(view, template, assigns) + end) + |> Enum.filter(& &1) + end + end + end + + def router do + quote do + use Phoenix.Router + # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse + use Phoenix.Channel + import Pleroma.Web.Gettext + end + end + + def plug do + quote do + @behaviour Pleroma.Web.Plug + @behaviour Plug + + @doc """ + Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain. + """ + def skip_plug(conn) do + PlugHelper.append_to_private_list( + conn, + PlugHelper.skipped_plugs_list_id(), + __MODULE__ + ) + end + + @impl Plug + @doc """ + Before-plug hook that + * ensures the plug is not skipped + * processes `:if_func` / `:unless_func` functional pre-run conditions + * adds plug to the list of called plugs and calls `perform/2` if checks are passed + + Note: multiple invocations of the same plug (with different or same options) are allowed. + """ + def call(%Plug.Conn{} = conn, options) do + if PlugHelper.plug_skipped?(conn, __MODULE__) || + (options[:if_func] && !options[:if_func].(conn)) || + (options[:unless_func] && options[:unless_func].(conn)) do + conn + else + conn = + PlugHelper.append_to_private_list( + conn, + PlugHelper.called_plugs_list_id(), + __MODULE__ + ) + + apply(__MODULE__, :perform, [conn, options]) + end + end + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end + + def base_url do + Pleroma.Web.Endpoint.url() + end +end diff --git a/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex new file mode 100644 index 000000000..6cbbe8fd8 --- /dev/null +++ b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MongooseIM.MongooseIMController do + use Pleroma.Web, :controller + + alias Pleroma.Plugs.AuthenticationPlug + alias Pleroma.Plugs.RateLimiter + alias Pleroma.Repo + alias Pleroma.User + + plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password]) + plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password) + + def user_exists(conn, %{"user" => username}) do + with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) 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, deactivated: false} <- + Repo.get_by(User, nickname: username, local: true), + true <- AuthenticationPlug.checkpw(password, password_hash) do + conn + |> json(true) + else + false -> + conn + |> put_status(:forbidden) + |> json(false) + + _ -> + conn + |> put_status(:not_found) + |> json(false) + end + end +end diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex deleted file mode 100644 index 6cbbe8fd8..000000000 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ /dev/null @@ -1,46 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MongooseIM.MongooseIMController do - use Pleroma.Web, :controller - - alias Pleroma.Plugs.AuthenticationPlug - alias Pleroma.Plugs.RateLimiter - alias Pleroma.Repo - alias Pleroma.User - - plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password]) - plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password) - - def user_exists(conn, %{"user" => username}) do - with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) 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, deactivated: false} <- - Repo.get_by(User, nickname: username, local: true), - true <- AuthenticationPlug.checkpw(password, password_hash) do - conn - |> json(true) - else - false -> - conn - |> put_status(:forbidden) - |> json(false) - - _ -> - conn - |> put_status(:not_found) - |> json(false) - end - end -end diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex new file mode 100644 index 000000000..de1b0b3f0 --- /dev/null +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OStatus.OStatusController do + use Pleroma.Web, :controller + + alias Fallback.RedirectController + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Plugs.RateLimiter + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPubController + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.Endpoint + alias Pleroma.Web.Metadata.PlayerView + alias Pleroma.Web.Router + + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/1 + ) + + plug( + RateLimiter, + [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] + ) + + plug( + Pleroma.Plugs.SetFormatPlug + when action in [:object, :activity, :notice] + ) + + action_fallback(:errors) + + def object(%{assigns: %{format: format}} = conn, _params) + when format in ["json", "activity+json"] do + ActivityPubController.call(conn, :object) + end + + def object(%{assigns: %{format: format}} = conn, _params) do + with id <- Endpoint.url() <> conn.request_path, + {_, %Activity{} = activity} <- + {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, + {_, true} <- {:public?, Visibility.is_public?(activity)} do + case format do + _ -> redirect(conn, to: "/notice/#{activity.id}") + end + else + reason when reason in [{:public?, false}, {:activity, nil}] -> + {:error, :not_found} + + e -> + e + end + end + + def activity(%{assigns: %{format: format}} = conn, _params) + when format in ["json", "activity+json"] do + ActivityPubController.call(conn, :activity) + end + + def activity(%{assigns: %{format: format}} = conn, _params) do + with id <- Endpoint.url() <> conn.request_path, + {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, + {_, true} <- {:public?, Visibility.is_public?(activity)} do + case format do + _ -> redirect(conn, to: "/notice/#{activity.id}") + end + else + reason when reason in [{:public?, false}, {:activity, nil}] -> + {:error, :not_found} + + e -> + e + end + end + + def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do + with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, + {_, true} <- {:public?, Visibility.is_public?(activity)}, + %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do + cond do + format in ["json", "activity+json"] -> + if activity.local do + %{data: %{"id" => redirect_url}} = Object.normalize(activity) + redirect(conn, external: redirect_url) + else + {:error, :not_found} + end + + activity.data["type"] == "Create" -> + %Object{} = object = Object.normalize(activity) + + RedirectController.redirector_with_meta( + conn, + %{ + activity_id: activity.id, + object: object, + url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id), + user: user + } + ) + + true -> + RedirectController.redirector(conn, nil) + end + else + reason when reason in [{:public?, false}, {:activity, nil}] -> + conn + |> put_status(404) + |> RedirectController.redirector(nil, 404) + + e -> + e + end + end + + # Returns an HTML embedded