diff options
author | Roman Chvanikov <chvanikoff@pm.me> | 2019-07-12 18:08:27 +0300 |
---|---|---|
committer | Roman Chvanikov <chvanikoff@pm.me> | 2019-07-12 18:08:27 +0300 |
commit | eae991b06a22bf7fc3eef7a0d5b409c931c1f6cb (patch) | |
tree | 77cd19ea54b8efa69e45b2d37876e7d5084cf38e /lib | |
parent | 371d39e160efa51f2fe608e1788f6b11b89d9839 (diff) | |
parent | db75288b71e7531f8e5033f56fc0b9a7d1d8efb3 (diff) | |
download | pleroma-eae991b06a22bf7fc3eef7a0d5b409c931c1f6cb.tar.gz |
merge develop
Diffstat (limited to 'lib')
77 files changed, 869 insertions, 593 deletions
diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex index 32aafc210..f97d14432 100644 --- a/lib/healthcheck.ex +++ b/lib/healthcheck.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Healthcheck do @moduledoc """ Module collects metrics about app and assign healthy status. diff --git a/lib/jason_types.ex b/lib/jason_types.ex index d1a7bc7ac..c558aef57 100644 --- a/lib/jason_types.ex +++ b/lib/jason_types.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + Postgrex.Types.define( Pleroma.PostgresTypes, [] ++ Ecto.Adapters.Postgres.extensions(), diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index d43db7b35..5222cce80 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Benchmark do import Mix.Pleroma use Mix.Task diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index faa605d9b..a71bcd447 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Config do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index f08e5ff3f..b9b1991c2 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do - `--db-configurable Y/N` - Allow/disallow configuring instance from admin part - `--uploads-dir` - the directory uploads go in when using a local uploader - `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.) + - `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1 + - `--listen-port` - the port the app should listen to, defaults to 4000 """ def run(["gen" | rest]) do @@ -56,7 +58,9 @@ defmodule Mix.Tasks.Pleroma.Instance do indexable: :string, db_configurable: :string, uploads_dir: :string, - static_dir: :string + static_dir: :string, + listen_ip: :string, + listen_port: :string ], aliases: [ o: :output, @@ -146,6 +150,22 @@ defmodule Mix.Tasks.Pleroma.Instance do "n" ) === "y" + listen_port = + get_option( + options, + :listen_port, + "What port will the app listen to (leave it if you are using the default setup with nginx)?", + 4000 + ) + + listen_ip = + get_option( + options, + :listen_ip, + "What ip will the app listen to (leave it if you are using the default setup with nginx)?", + "127.0.0.1" + ) + uploads_dir = get_option( options, @@ -188,7 +208,9 @@ defmodule Mix.Tasks.Pleroma.Instance do db_configurable?: db_configurable?, static_dir: static_dir, uploads_dir: uploads_dir, - rum_enabled: rum_enabled + rum_enabled: rum_enabled, + listen_ip: listen_ip, + listen_port: listen_port ) result_psql = diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 6db41fe6e..46552c7be 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -344,5 +344,5 @@ defmodule Pleroma.Activity do ) end - defdelegate search(user, query), to: Pleroma.Activity.Search + defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search end diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index 0aa2aab23..0cc3770a7 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -5,14 +5,17 @@ defmodule Pleroma.Activity.Search do alias Pleroma.Activity alias Pleroma.Object.Fetcher - alias Pleroma.Repo + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.ActivityPub.Visibility import Ecto.Query - def search(user, search_query) do + def search(user, search_query, options \\ []) do index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin + limit = Enum.min([Keyword.get(options, :limit), 40]) + offset = Keyword.get(options, :offset, 0) + author = Keyword.get(options, :author) Activity |> Activity.with_preloaded_object() @@ -20,15 +23,23 @@ defmodule Pleroma.Activity.Search do |> restrict_public() |> query_with(index_type, search_query) |> maybe_restrict_local(user) - |> Repo.all() + |> maybe_restrict_author(author) + |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) |> maybe_fetch(user, search_query) end + def maybe_restrict_author(query, %User{} = author) do + from([a, o] in query, + where: a.actor == ^author.ap_id + ) + end + + def maybe_restrict_author(query, _), do: query + defp restrict_public(q) do from([a, o] in q, where: fragment("?->>'type' = 'Create'", a.data), - where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, - limit: 40 + where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients ) end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 8887e3935..29cd14477 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -155,11 +155,7 @@ defmodule Pleroma.Application do start: {Pleroma.Web.Endpoint, :start_link, []}, type: :supervisor }, - %{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}, - %{ - id: Pleroma.User.SynchronizationWorker, - start: {Pleroma.User.SynchronizationWorker, :start_link, []} - } + %{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}} ] # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex index a2c153720..79f133ea6 100644 --- a/lib/pleroma/bbs/authenticator.ex +++ b/lib/pleroma/bbs/authenticator.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.BBS.Authenticator do use Sshd.PasswordAuthenticator alias Comeonin.Pbkdf2 diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex index f34be961f..0a381f592 100644 --- a/lib/pleroma/bbs/handler.ex +++ b/lib/pleroma/bbs/handler.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.BBS.Handler do use Sshd.ShellHandler alias Pleroma.Activity diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index 7f8fd43b6..d976f949c 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Bookmark do use Ecto.Schema diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index f105cbb25..a73b87251 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha do + import Pleroma.Web.Gettext + alias Calendar.DateTime alias Plug.Crypto.KeyGenerator alias Plug.Crypto.MessageEncryptor @@ -83,10 +85,11 @@ defmodule Pleroma.Captcha do with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do try do - if DateTime.before?(at, valid_if_after), do: throw({:error, "CAPTCHA expired"}) + if DateTime.before?(at, valid_if_after), + do: throw({:error, dgettext("errors", "CAPTCHA expired")}) if not is_nil(Cachex.get!(:used_captcha_cache, token)), - do: throw({:error, "CAPTCHA already used"}) + do: throw({:error, dgettext("errors", "CAPTCHA already used")}) res = method().validate(token, captcha, answer_md5) # Throw if an error occurs @@ -101,7 +104,7 @@ defmodule Pleroma.Captcha do :throw, e -> e end else - _ -> {:error, "Invalid answer data"} + _ -> {:error, dgettext("errors", "Invalid answer data")} end {:reply, result, state} diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 18931d5a0..4e1a07c59 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Kocaptcha do + import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -12,7 +13,7 @@ defmodule Pleroma.Captcha.Kocaptcha do case Tesla.get(endpoint <> "/new") do {:error, _} -> - %{error: "Kocaptcha service unavailable"} + %{error: dgettext("errors", "Kocaptcha service unavailable")} {:ok, res} -> json_resp = Jason.decode!(res.body) @@ -32,6 +33,6 @@ defmodule Pleroma.Captcha.Kocaptcha do if not is_nil(captcha) and :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), do: :ok, - else: {:error, "Invalid CAPTCHA"} + else: {:error, dgettext("errors", "Invalid CAPTCHA")} end end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index cf880aa22..3c13a0558 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Config.TransferTask do use Task alias Pleroma.Web.AdminAPI.Config diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 53f5a661c..2e4657b7c 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -3,11 +3,58 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Emails.Mailer do - use Swoosh.Mailer, otp_app: :pleroma + @moduledoc """ + Defines the Pleroma mailer. + The module contains functions to delivery email using Swoosh.Mailer. + """ + + alias Swoosh.DeliveryError + + @otp_app :pleroma + @mailer_config [otp: :pleroma] + + @spec enabled?() :: boolean() + def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled]) + + @doc "add email to queue" def deliver_async(email, config \\ []) do PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config]) end + @doc "callback to perform send email from queue" def perform(:deliver_async, email, config), do: deliver(email, config) + + @spec deliver(Swoosh.Email.t(), Keyword.t()) :: {:ok, term} | {:error, term} + def deliver(email, config \\ []) + + def deliver(email, config) do + case enabled?() do + true -> Swoosh.Mailer.deliver(email, parse_config(config)) + false -> {:error, :deliveries_disabled} + end + end + + @spec deliver!(Swoosh.Email.t(), Keyword.t()) :: term | no_return + def deliver!(email, config \\ []) + + def deliver!(email, config) do + case deliver(email, config) do + {:ok, result} -> result + {:error, reason} -> raise DeliveryError, reason: reason + end + end + + @on_load :validate_dependency + + @doc false + def validate_dependency do + parse_config([]) + |> Keyword.get(:adapter) + |> Swoosh.Mailer.validate_dependency() + end + + defp parse_config(config) do + Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config) + end end diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex index fa5043bc5..1b05d573c 100644 --- a/lib/pleroma/instances.ex +++ b/lib/pleroma/instances.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Instances do @moduledoc "Instances context." diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 420803a8f..4d7ed4ca1 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Instances.Instance do @moduledoc "Instance." diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index b8647dd26..c8d339c19 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -44,7 +44,15 @@ defmodule Pleroma.Object do Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) end + defp warn_on_no_object_preloaded(ap_id) do + "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object" + |> Logger.debug() + + Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") + end + def normalize(_, fetch_remote \\ true, options \\ []) + # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! def normalize(%Object{} = object, _, _), do: object @@ -55,25 +63,15 @@ defmodule Pleroma.Object do %Object{id: "pleroma:fake_object_id", data: data} end - # Catch and log Object.normalize() calls where the Activity's child object is not - # preloaded. + # No preloaded object def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do - Logger.debug( - "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" - ) - - Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") - + warn_on_no_object_preloaded(ap_id) normalize(ap_id, fetch_remote) end + # No preloaded object def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do - Logger.debug( - "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" - ) - - Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") - + warn_on_no_object_preloaded(ap_id) normalize(ap_id, fetch_remote) end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index fffbf2bbb..101c21f96 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Object.Fetcher do alias Pleroma.HTTP alias Pleroma.Object diff --git a/lib/pleroma/object_tombstone.ex b/lib/pleroma/object_tombstone.ex index 64d836d3e..fe947ffd3 100644 --- a/lib/pleroma/object_tombstone.ex +++ b/lib/pleroma/object_tombstone.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.ObjectTombstone do @enforce_keys [:id, :formerType, :deleted] defstruct [:id, :formerType, :deleted, type: "Tombstone"] diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index f435e5c9c..2b869ccdc 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Pagination do @moduledoc """ Implements Mastodon-compatible pagination. @@ -10,16 +14,28 @@ defmodule Pleroma.Pagination do @default_limit 20 - def fetch_paginated(query, params) do + def fetch_paginated(query, params, type \\ :keyset) + + def fetch_paginated(query, params, :keyset) do options = cast_params(params) query - |> paginate(options) + |> paginate(options, :keyset) |> Repo.all() |> enforce_order(options) end - def paginate(query, options) do + def fetch_paginated(query, params, :offset) do + options = cast_params(params) + + query + |> paginate(options, :offset) + |> Repo.all() + end + + def paginate(query, options, method \\ :keyset) + + def paginate(query, options, :keyset) do query |> restrict(:min_id, options) |> restrict(:since_id, options) @@ -28,11 +44,18 @@ defmodule Pleroma.Pagination do |> restrict(:limit, options) end + def paginate(query, options, :offset) do + query + |> restrict(:offset, options) + |> restrict(:limit, options) + end + defp cast_params(params) do param_types = %{ min_id: :string, since_id: :string, max_id: :string, + offset: :integer, limit: :integer } @@ -66,6 +89,10 @@ defmodule Pleroma.Pagination do order_by(query, [u], fragment("? desc nulls last", u.id)) end + defp restrict(query, :offset, %{offset: offset}) do + offset(query, ^offset) + end + defp restrict(query, :limit, options) do limit = Map.get(options, :limit, @default_limit) diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex index 11c4342c4..27cd41aec 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_authenticated_plug.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do import Plug.Conn + import Pleroma.Web.TranslationHelpers alias Pleroma.User def init(options) do @@ -16,8 +17,7 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do def call(conn, _) do conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{error: "Invalid credentials."})) + |> render_error(:forbidden, "Invalid credentials.") |> halt end end diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex index 317fd5445..a16f61435 100644 --- a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex +++ b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do + import Pleroma.Web.TranslationHelpers import Plug.Conn alias Pleroma.Config alias Pleroma.User @@ -23,8 +24,7 @@ defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do {false, _} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."})) + |> render_error(:forbidden, "This resource requires authentication.") |> halt end end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index f2bfa2b1a..b508628a9 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do import Plug.Conn + import Pleroma.Web.Gettext @behaviour Plug @@ -30,11 +31,14 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do true -> missing_scopes = scopes -- token.scopes - error_message = "Insufficient permissions: #{Enum.join(missing_scopes, " #{op} ")}." + permissions = Enum.join(missing_scopes, " #{op} ") + + error_message = + dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions) conn |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{error: error_message})) + |> send_resp(:forbidden, Jason.encode!(%{error: error_message})) |> halt() end end diff --git a/lib/pleroma/plugs/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter.ex index 9ba5875fa..c5e0957e8 100644 --- a/lib/pleroma/plugs/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter.ex @@ -44,8 +44,7 @@ defmodule Pleroma.Plugs.RateLimiter do ... end """ - - import Phoenix.Controller, only: [json: 2] + import Pleroma.Web.TranslationHelpers import Plug.Conn alias Pleroma.User @@ -63,7 +62,7 @@ defmodule Pleroma.Plugs.RateLimiter do def call(conn, opts) do case check_rate(conn, opts) do {:ok, _count} -> conn - {:error, _count} -> render_error(conn) + {:error, _count} -> render_throttled_error(conn) end end @@ -85,10 +84,9 @@ defmodule Pleroma.Plugs.RateLimiter do |> Enum.join(".") end - defp render_error(conn) do + defp render_throttled_error(conn) do conn - |> put_status(:too_many_requests) - |> json(%{error: "Throttled"}) + |> render_error(:too_many_requests, "Throttled") |> halt() end end diff --git a/lib/pleroma/plugs/set_locale_plug.ex b/lib/pleroma/plugs/set_locale_plug.ex new file mode 100644 index 000000000..8646cb30d --- /dev/null +++ b/lib/pleroma/plugs/set_locale_plug.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +# NOTE: this module is based on https://github.com/smeevil/set_locale +defmodule Pleroma.Plugs.SetLocalePlug do + import Plug.Conn, only: [get_req_header: 2, assign: 3] + + def init(_), do: nil + + def call(conn, _) do + locale = get_locale_from_header(conn) || Gettext.get_locale() + Gettext.put_locale(locale) + assign(conn, :locale, locale) + end + + defp get_locale_from_header(conn) do + conn + |> extract_accept_language() + |> Enum.find(&supported_locale?/1) + end + + defp extract_accept_language(conn) do + case get_req_header(conn, "accept-language") do + [value | _] -> + value + |> String.split(",") + |> Enum.map(&parse_language_option/1) + |> Enum.sort(&(&1.quality > &2.quality)) + |> Enum.map(& &1.tag) + |> Enum.reject(&is_nil/1) + |> ensure_language_fallbacks() + + _ -> + [] + end + end + + defp supported_locale?(locale) do + Pleroma.Web.Gettext + |> Gettext.known_locales() + |> Enum.member?(locale) + end + + defp parse_language_option(string) do + captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string) + + quality = + case Float.parse(captures["quality"] || "1.0") do + {val, _} -> val + :error -> 1.0 + end + + %{tag: captures["tag"], quality: quality} + end + + defp ensure_language_fallbacks(tags) do + Enum.flat_map(tags, fn tag -> + [language | _] = String.split(tag, "-") + if Enum.member?(tags, language), do: [tag], else: [tag, language] + end) + end +end diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex index 8d0fac7ee..69c1ab942 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/plugs/uploaded_media.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Plugs.UploadedMedia do """ import Plug.Conn + import Pleroma.Web.Gettext require Logger @behaviour Plug @@ -45,7 +46,7 @@ defmodule Pleroma.Plugs.UploadedMedia do else _ -> conn - |> send_resp(500, "Failed") + |> send_resp(:internal_server_error, dgettext("errors", "Failed")) |> halt() end end @@ -64,7 +65,7 @@ defmodule Pleroma.Plugs.UploadedMedia do conn else conn - |> send_resp(404, "Not found") + |> send_resp(:not_found, dgettext("errors", "Not found")) |> halt() end end @@ -84,7 +85,7 @@ defmodule Pleroma.Plugs.UploadedMedia do Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}") conn - |> send_resp(500, "Internal Error") + |> send_resp(:internal_server_error, dgettext("errors", "Internal Error")) |> halt() end end diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index 04329e919..4c4b3d610 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Plugs.UserIsAdminPlug do + import Pleroma.Web.TranslationHelpers import Plug.Conn alias Pleroma.User @@ -16,8 +17,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do def call(conn, _) do conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{error: "User is not admin."})) + |> render_error(:forbidden, "User is not admin.") |> halt end end diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex index 57c2d2cfd..776c4794c 100644 --- a/lib/pleroma/reverse_proxy/client.ex +++ b/lib/pleroma/reverse_proxy/client.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.ReverseProxy.Client do @callback request(atom(), String.t(), [tuple()], String.t(), list()) :: {:ok, pos_integer(), [tuple()], reference() | map()} diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index bf15389fc..0af76bc59 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Uploaders.Uploader do + import Pleroma.Web.Gettext + @moduledoc """ Defines the contract to put and get an uploaded file to any backend. """ @@ -66,7 +68,7 @@ defmodule Pleroma.Uploaders.Uploader do {:error, error} end after - 30_000 -> {:error, "Uploader callback timeout"} + 30_000 -> {:error, dgettext("errors", "Uploader callback timeout")} end end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 791676ee2..9eb13084f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -52,6 +52,7 @@ defmodule Pleroma.User do field(:avatar, :map) field(:local, :boolean, default: true) field(:follower_address, :string) + field(:following_address, :string) field(:search_rank, :float, virtual: true) field(:search_type, :integer, virtual: true) field(:tags, {:array, :string}, default: []) @@ -108,6 +109,10 @@ defmodule Pleroma.User do def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" + @spec ap_following(User.t()) :: Sring.t() + def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa + def ap_following(%User{} = user), do: "#{ap_id(user)}/following" + def user_info(%User{} = user, args \\ %{}) do following_count = if args[:following_count], do: args[:following_count], else: following_count(user) @@ -129,6 +134,7 @@ defmodule Pleroma.User do Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args)) end + @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t() def restrict_deactivated(query) do from(u in query, where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info) @@ -163,9 +169,10 @@ defmodule Pleroma.User do if changes.valid? do case info_cng.changes[:source_data] do - %{"followers" => followers} -> + %{"followers" => followers, "following" => following} -> changes |> put_change(:follower_address, followers) + |> put_change(:following_address, following) _ -> followers = User.ap_followers(%User{nickname: changes.changes[:nickname]}) @@ -197,7 +204,14 @@ defmodule Pleroma.User do |> User.Info.user_upgrade(params[:info]) struct - |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at]) + |> cast(params, [ + :bio, + :name, + :follower_address, + :following_address, + :avatar, + :last_refreshed_at + ]) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) @@ -938,6 +952,8 @@ defmodule Pleroma.User do @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do + {:ok, _user} = ActivityPub.delete(user) + # Remove all relationships {:ok, followers} = User.get_followers(user) @@ -954,8 +970,8 @@ defmodule Pleroma.User do end) delete_user_activities(user) - - {:ok, _user} = Repo.delete(user) + invalidate_cache(user) + Repo.delete(user) end @spec perform(atom(), User.t()) :: {:ok, User.t()} @@ -1011,42 +1027,20 @@ defmodule Pleroma.User do ) end - @spec sync_follow_counter() :: :ok - def sync_follow_counter, - do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters]) - - @spec perform(:sync_follow_counters) :: :ok - def perform(:sync_follow_counters) do - {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors) - config = Pleroma.Config.get([:instance, :external_user_synchronization]) - - :ok = sync_follow_counters(config) - Agent.stop(:domain_errors) - end - - @spec sync_follow_counters(keyword()) :: :ok - def sync_follow_counters(opts \\ []) do - users = external_users(opts) - - if length(users) > 0 do - errors = Agent.get(:domain_errors, fn state -> state end) - {last, updated_errors} = User.Synchronization.call(users, errors, opts) - Agent.update(:domain_errors, fn _state -> updated_errors end) - sync_follow_counters(max_id: last.id, limit: opts[:limit]) - else - :ok - end + @spec external_users_query() :: Ecto.Query.t() + def external_users_query do + User.Query.build(%{ + external: true, + active: true, + order_by: :id + }) end @spec external_users(keyword()) :: [User.t()] def external_users(opts \\ []) do query = - User.Query.build(%{ - external: true, - active: true, - order_by: :id, - select: [:id, :ap_id, :info] - }) + external_users_query() + |> select([u], struct(u, [:id, :ap_id, :info])) query = if opts[:max_id], diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 64eb6d2bc..46620b89a 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User.Search do + alias Pleroma.Pagination alias Pleroma.Repo alias Pleroma.User import Ecto.Query @@ -18,8 +19,7 @@ defmodule Pleroma.User.Search do for_user = Keyword.get(opts, :for_user) - # Strip the beginning @ off if there is a query - query_string = String.trim_leading(query_string, "@") + query_string = format_query(query_string) maybe_resolve(resolve, for_user, query_string) @@ -33,13 +33,24 @@ defmodule Pleroma.User.Search do query_string |> search_query(for_user, following) - |> paginate(result_limit, offset) - |> Repo.all() + |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) end) results end + defp format_query(query_string) do + # Strip the beginning @ off if there is a query + query_string = String.trim_leading(query_string, "@") + + with [name, domain] <- String.split(query_string, "@"), + formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:]+/, "") do + name <> "@" <> to_string(:idna.encode(formatted_domain)) + else + _ -> query_string + end + end + defp search_query(query_string, for_user, following) do for_user |> base_query(following) @@ -76,10 +87,6 @@ defmodule Pleroma.User.Search do defp filter_blocked_domains(query, _), do: query - defp paginate(query, limit, offset) do - from(q in query, limit: ^limit, offset: ^offset) - end - defp union_subqueries({fts_subquery, trigram_subquery}) do from(s in trigram_subquery, union_all: ^fts_subquery) end @@ -151,7 +158,7 @@ defmodule Pleroma.User.Search do defp fts_search_subquery(query, term) do processed_query = String.trim_trailing(term, "@" <> local_domain()) - |> String.replace(~r/\W+/, " ") + |> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ") |> String.trim() |> String.split() |> Enum.map(&(&1 <> ":*")) diff --git a/lib/pleroma/user/synchronization.ex b/lib/pleroma/user/synchronization.ex deleted file mode 100644 index 93660e08c..000000000 --- a/lib/pleroma/user/synchronization.ex +++ /dev/null @@ -1,60 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.User.Synchronization do - alias Pleroma.HTTP - alias Pleroma.User - - @spec call([User.t()], map(), keyword()) :: {User.t(), map()} - def call(users, errors, opts \\ []) do - do_call(users, errors, opts) - end - - defp do_call([user | []], errors, opts) do - updated = fetch_counters(user, errors, opts) - {user, updated} - end - - defp do_call([user | others], errors, opts) do - updated = fetch_counters(user, errors, opts) - do_call(others, updated, opts) - end - - defp fetch_counters(user, errors, opts) do - %{host: host} = URI.parse(user.ap_id) - - info = %{} - {following, errors} = fetch_counter(user.ap_id <> "/following", host, errors, opts) - info = if following, do: Map.put(info, :following_count, following), else: info - - {followers, errors} = fetch_counter(user.ap_id <> "/followers", host, errors, opts) - info = if followers, do: Map.put(info, :follower_count, followers), else: info - - User.set_info_cache(user, info) - errors - end - - defp available_domain?(domain, errors, opts) do - max_retries = Keyword.get(opts, :max_retries, 3) - not (Map.has_key?(errors, domain) && errors[domain] >= max_retries) - end - - defp fetch_counter(url, host, errors, opts) do - with true <- available_domain?(host, errors, opts), - {:ok, %{body: body, status: code}} when code in 200..299 <- - HTTP.get( - url, - [{:Accept, "application/activity+json"}] - ), - {:ok, data} <- Jason.decode(body) do - {data["totalItems"], errors} - else - false -> - {nil, errors} - - _ -> - {nil, Map.update(errors, host, 1, &(&1 + 1))} - end - end -end diff --git a/lib/pleroma/user/synchronization_worker.ex b/lib/pleroma/user/synchronization_worker.ex deleted file mode 100644 index ba9cc3556..000000000 --- a/lib/pleroma/user/synchronization_worker.ex +++ /dev/null @@ -1,32 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-onl - -defmodule Pleroma.User.SynchronizationWorker do - use GenServer - - def start_link do - config = Pleroma.Config.get([:instance, :external_user_synchronization]) - - if config[:enabled] do - GenServer.start_link(__MODULE__, interval: config[:interval]) - else - :ignore - end - end - - def init(opts) do - schedule_next(opts) - {:ok, opts} - end - - def handle_info(:sync_follow_counters, opts) do - Pleroma.User.sync_follow_counter() - schedule_next(opts) - {:noreply, opts} - end - - defp schedule_next(opts) do - Process.send_after(self(), :sync_follow_counters, opts[:interval]) - end -end diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex index 2ba65b75a..99fba729e 100644 --- a/lib/pleroma/user/welcome_message.ex +++ b/lib/pleroma/user/welcome_message.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.User.WelcomeMessage do alias Pleroma.User alias Pleroma.Web.CommonAPI diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 55315d66e..a3174a787 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -405,6 +405,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do + with data <- %{ + "to" => [follower_address], + "type" => "Delete", + "actor" => ap_id, + "object" => %{"type" => "Person", "id" => ap_id} + }, + {:ok, activity} <- insert(data, true, true), + :ok <- maybe_federate(activity) do + {:ok, user} + end + end + def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do user = User.get_cached_by_ap_id(actor) to = (object.data["to"] || []) ++ (object.data["cc"] || []) @@ -981,6 +994,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do avatar: avatar, name: data["name"], follower_address: data["followers"], + following_address: data["following"], bio: data["summary"] } diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 0182bda46..cf5176201 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -31,9 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn else conn - |> put_status(404) - |> json(%{error: "not found"}) - |> halt + |> render_error(:not_found, "not found") + |> halt() end end @@ -190,7 +189,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do Logger.info(inspect(conn.req_headers)) end - json(conn, "error") + json(conn, dgettext("errors", "error")) end def relay(conn, _params) do @@ -218,9 +217,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]})) else + err = + dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}", + nickname: nickname, + as_nickname: user.nickname + ) + conn |> put_status(:forbidden) - |> json("can't read inbox of #{nickname} as #{user.nickname}") + |> json(err) end end @@ -246,7 +251,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} else - _ -> {:error, "Can't delete object"} + _ -> {:error, dgettext("errors", "Can't delete object")} end end @@ -255,12 +260,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {:ok, activity, _object} <- ActivityPub.like(user, object) do {:ok, activity} else - _ -> {:error, "Can't like object"} + _ -> {:error, dgettext("errors", "Can't like object")} end end def handle_user_activity(_, _) do - {:error, "Unhandled activity type"} + {:error, dgettext("errors", "Unhandled activity type")} end def update_outbox( @@ -288,22 +293,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> json(message) end else + err = + dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}", + nickname: nickname, + as_nickname: user.nickname + ) + conn |> put_status(:forbidden) - |> json("can't update outbox of #{nickname} as #{user.nickname}") + |> json(err) end end def errors(conn, {:error, :not_found}) do conn - |> put_status(404) - |> json("Not found") + |> put_status(:not_found) + |> json(dgettext("errors", "Not found")) end def errors(conn, _e) do conn - |> put_status(500) - |> json("error") + |> put_status(:internal_server_error) + |> json(dgettext("errors", "error")) end defp set_requester_reachable(%Plug.Conn{} = conn, _) do diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index 15d8514be..2d03df68a 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -9,8 +9,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do @behaviour Pleroma.Web.ActivityPub.MRF @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless]) + def filter_by_summary( - %{"summary" => parent_summary} = _parent, + %{data: %{"summary" => parent_summary}} = _in_reply_to, %{"summary" => child_summary} = child ) when not is_nil(child_summary) and byte_size(child_summary) > 0 and @@ -24,17 +25,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do end end - def filter_by_summary(_parent, child), do: child - - def filter(%{"type" => activity_type} = object) when activity_type == "Create" do - child = object["object"] - in_reply_to = Object.normalize(child["inReplyTo"]) + def filter_by_summary(_in_reply_to, child), do: child + def filter(%{"type" => "Create", "object" => child_object} = object) do child = - if(in_reply_to, - do: filter_by_summary(in_reply_to.data, child), - else: child - ) + child_object["inReplyTo"] + |> Object.normalize(child_object["inReplyTo"]) + |> filter_by_summary(child_object) object = Map.put(object, "object", child) diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex index f30fee0d5..86a48bda5 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex @@ -10,19 +10,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do def filter( %{ "type" => "Create", - "object" => %{"content" => content, "attachment" => _attachment} = child_object + "object" => %{"content" => content, "attachment" => _} = _child_object } = object ) when content in [".", "<p>.</p>"] do - child_object = - child_object - |> Map.put("content", "") - - object = - object - |> Map.put("object", child_object) - - {:ok, object} + {:ok, put_in(object, ["object", "content"], "")} end @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index 9c87c6963..c269d0f89 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -8,18 +8,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do @behaviour Pleroma.Web.ActivityPub.MRF - def filter(%{"type" => activity_type} = object) when activity_type == "Create" do + def filter(%{"type" => "Create", "object" => child_object} = object) do scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) - child = object["object"] - content = - child["content"] + child_object["content"] |> HTML.filter_tags(scrub_policy) - child = Map.put(child, "content", content) - - object = Map.put(object, "object", child) + object = put_in(object, ["object", "content"], content) {:ok, object} end diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index ea3df1b4d..da13fd7c7 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -3,46 +3,42 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do - alias Pleroma.User @moduledoc "Rejects non-public (followers-only, direct) activities" + + alias Pleroma.Config + alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF + @public "https://www.w3.org/ns/activitystreams#Public" + @impl true def filter(%{"type" => "Create"} = object) do user = User.get_cached_by_ap_id(object["actor"]) - public = "https://www.w3.org/ns/activitystreams#Public" # Determine visibility visibility = cond do - public in object["to"] -> "public" - public in object["cc"] -> "unlisted" + @public in object["to"] -> "public" + @public in object["cc"] -> "unlisted" user.follower_address in object["to"] -> "followers" true -> "direct" end - policy = Pleroma.Config.get(:mrf_rejectnonpublic) + policy = Config.get(:mrf_rejectnonpublic) + + cond do + visibility in ["public", "unlisted"] -> + {:ok, object} - case visibility do - "public" -> + visibility == "followers" and Keyword.get(policy, :allow_followersonly) -> {:ok, object} - "unlisted" -> + visibility == "direct" and Keyword.get(policy, :allow_direct) -> {:ok, object} - "followers" -> - with true <- Keyword.get(policy, :allow_followersonly) do - {:ok, object} - else - _e -> {:reject, nil} - end - - "direct" -> - with true <- Keyword.get(policy, :allow_direct) do - {:ok, object} - else - _e -> {:reject, nil} - end + true -> + {:reject, nil} end end diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index 6683b8d8e..b42c4ed76 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -19,12 +19,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do - `mrf_tag:disable-any-subscription`: Reject any follow requests """ + @public "https://www.w3.org/ns/activitystreams#Public" + defp get_tags(%User{tags: tags}) when is_list(tags), do: tags defp get_tags(_), do: [] defp process_tag( "mrf_tag:media-force-nsfw", - %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message + %{ + "type" => "Create", + "object" => %{"attachment" => child_attachment} = object + } = message ) when length(child_attachment) > 0 do tags = (object["tag"] || []) ++ ["nsfw"] @@ -41,7 +46,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do defp process_tag( "mrf_tag:media-strip", - %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message + %{ + "type" => "Create", + "object" => %{"attachment" => child_attachment} = object + } = message ) when length(child_attachment) > 0 do object = Map.delete(object, "attachment") @@ -52,19 +60,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do defp process_tag( "mrf_tag:force-unlisted", - %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message ) do user = User.get_cached_by_ap_id(actor) - if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do - to = - List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address] - - cc = - List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"] + if Enum.member?(to, @public) do + to = List.delete(to, @public) ++ [user.follower_address] + cc = List.delete(cc, user.follower_address) ++ [@public] object = - message["object"] + object |> Map.put("to", to) |> Map.put("cc", cc) @@ -82,19 +93,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do defp process_tag( "mrf_tag:sandbox", - %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message ) do user = User.get_cached_by_ap_id(actor) - if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or - Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do - to = - List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address] - - cc = List.delete(cc, "https://www.w3.org/ns/activitystreams#Public") + if Enum.member?(to, @public) or Enum.member?(cc, @public) do + to = List.delete(to, @public) ++ [user.follower_address] + cc = List.delete(cc, @public) object = - message["object"] + object |> Map.put("to", to) |> Map.put("cc", cc) @@ -123,7 +137,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do end end - defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil} + defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), + do: {:reject, nil} defp process_tag(_, message), do: {:ok, message} diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex index 47663414a..e35d2c422 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex @@ -21,7 +21,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do @impl true def filter(%{"actor" => actor} = object) do actor_info = URI.parse(actor) - allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], []) + + allow_list = + Config.get( + [:mrf_user_allowlist, String.to_atom(actor_info.host)], + [] + ) filter_by_list(object, allow_list) end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 543d4bb7d..d14490bb5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -641,7 +641,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # an error or a tombstone. This would allow us to verify that a deletion actually took # place. def handle_incoming( - %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data, + %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data, _options ) do object_id = Utils.get_ap_id(object_id) @@ -653,7 +653,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else - _e -> :error + nil -> + case User.get_cached_by_ap_id(object_id) do + %User{ap_id: ^actor} = user -> + {:ok, followers} = User.get_followers(user) + + Enum.each(followers, fn follower -> + User.unfollow(follower, user) + end) + + {:ok, friends} = User.get_friends(user) + + Enum.each(friends, fn followed -> + User.unfollow(user, followed) + end) + + User.invalidate_cache(user) + Repo.delete(user) + + nil -> + :error + end + + _e -> + :error end end @@ -1064,6 +1087,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user]) end + if Pleroma.Config.get([:instance, :external_user_synchronization]) do + update_following_followers_counters(user) + end + {:ok, user} else %User{} = user -> {:ok, user} @@ -1096,4 +1123,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do data |> maybe_fix_user_url end + + def update_following_followers_counters(user) do + info = %{} + + following = fetch_counter(user.following_address) + info = if following, do: Map.put(info, :following_count, following), else: info + + followers = fetch_counter(user.follower_address) + info = if followers, do: Map.put(info, :follower_count, followers), else: info + + User.set_info_cache(user, info) + end + + defp fetch_counter(url) do + with {:ok, %{body: body, status: code}} when code in 200..299 <- + Pleroma.HTTP.get( + url, + [{:Accept, "application/activity+json"}] + ), + {:ok, data} <- Jason.decode(body) do + data["totalItems"] + end + end end diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 8965e3253..9908a2e75 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.Visibility do alias Pleroma.Activity alias Pleroma.Object diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 0a2482a8c..4a0bf4823 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -160,9 +160,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def right_add(conn, _) do - conn - |> put_status(404) - |> json(%{error: "No such permission_group"}) + render_error(conn, :not_found, "No such permission_group") end def right_get(conn, %{"nickname" => nickname}) do @@ -184,9 +182,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ) when permission_group in ["moderator", "admin"] do if admin_nickname == nickname do - conn - |> put_status(403) - |> json(%{error: "You can't revoke your own admin status."}) + render_error(conn, :forbidden, "You can't revoke your own admin status.") else user = User.get_cached_by_nickname(nickname) @@ -207,9 +203,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def right_delete(conn, _) do - conn - |> put_status(404) - |> json(%{error: "No such permission_group"}) + render_error(conn, :not_found, "No such permission_group") end def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do @@ -377,13 +371,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do if Pleroma.Config.get([:instance, :dynamic_configuration]) do updated = Enum.map(configs, fn - %{"group" => group, "key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) - config - %{"group" => group, "key" => key, "delete" => "true"} -> {:ok, _} = Config.delete(%{group: group, key: key}) nil + + %{"group" => group, "key" => key, "value" => value} -> + {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) + config end) |> Enum.reject(&is_nil(&1)) @@ -401,26 +395,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def errors(conn, {:error, :not_found}) do conn - |> put_status(404) - |> json("Not found") + |> put_status(:not_found) + |> json(dgettext("errors", "Not found")) end def errors(conn, {:error, reason}) do conn - |> put_status(400) + |> put_status(:bad_request) |> json(reason) end def errors(conn, {:param_cast, _}) do conn - |> put_status(400) - |> json("Invalid parameters") + |> put_status(:bad_request) + |> json(dgettext("errors", "Invalid parameters")) end def errors(conn, _) do conn - |> put_status(500) - |> json("Something went wrong") + |> put_status(:internal_server_error) + |> json(dgettext("errors", "Something went wrong")) end defp page_params(params) do diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index 8b9b658a9..b4eb8e002 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.AdminAPI.Config do use Ecto.Schema import Ecto.Changeset + import Pleroma.Web.Gettext alias __MODULE__ alias Pleroma.Repo @@ -57,104 +58,95 @@ defmodule Pleroma.Web.AdminAPI.Config do with %Config{} = config <- Config.get_by_params(params) do Repo.delete(config) else - nil -> {:error, "Config with params #{inspect(params)} not found"} + nil -> + err = + dgettext("errors", "Config with params %{params} not found", params: inspect(params)) + + {:error, err} end end @spec from_binary(binary()) :: term() - def from_binary(value), do: :erlang.binary_to_term(value) + def from_binary(binary), do: :erlang.binary_to_term(binary) - @spec from_binary_to_map(binary()) :: any() - def from_binary_to_map(binary) do + @spec from_binary_with_convert(binary()) :: any() + def from_binary_with_convert(binary) do from_binary(binary) |> do_convert() end - defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1, - do: %{k => do_convert(v)} + defp do_convert(entity) when is_list(entity) do + for v <- entity, into: [], do: do_convert(v) + end - defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val)) + defp do_convert(entity) when is_map(entity) do + for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)} + end - defp do_convert({k, v} = value) when is_tuple(value), - do: %{k => do_convert(v)} + defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} - defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))} + defp do_convert(entity) when is_tuple(entity), + do: %{"tuple" => do_convert(Tuple.to_list(entity))} - defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value + defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), + do: entity - defp do_convert(value) when is_atom(value) do - string = to_string(value) + defp do_convert(entity) when is_atom(entity) do + string = to_string(entity) if String.starts_with?(string, "Elixir."), - do: String.trim_leading(string, "Elixir."), - else: value + do: do_convert(string), + else: ":" <> string end - @spec transform(any()) :: binary() - def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity)) + defp do_convert("Elixir." <> module_name), do: module_name - def transform(entity) when is_map(entity) do - tuples = - for {k, v} <- entity, - into: [], - do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)} + defp do_convert(entity) when is_binary(entity), do: entity - Enum.reject(tuples, fn {_k, v} -> is_nil(v) end) - |> Enum.sort() - |> :erlang.term_to_binary() - end - - def transform(entity) when is_list(entity) do - list = Enum.map(entity, &do_transform(&1)) - :erlang.term_to_binary(list) + @spec transform(any()) :: binary() + def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do + :erlang.term_to_binary(do_transform(entity)) end def transform(entity), do: :erlang.term_to_binary(entity) - defp do_transform(%Regex{} = value) when is_map(value), do: value + defp do_transform(%Regex{} = entity) when is_map(entity), do: entity - defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do - {do_transform(k), do_transform(values)} + defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do + cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") + {dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: []) + {:dispatch, [dispatch_settings]} end - defp do_transform(%{"tuple" => values}) do - Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) + defp do_transform(%{"tuple" => entity}) do + Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) end - defp do_transform(value) when is_map(value) do - values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)} - - Enum.sort(values) + defp do_transform(entity) when is_map(entity) do + for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)} end - defp do_transform(value) when is_list(value) do - Enum.map(value, &do_transform(&1)) + defp do_transform(entity) when is_list(entity) do + for v <- entity, into: [], do: do_transform(v) end - defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity) - - defp do_transform(value) when is_binary(value) do - String.trim(value) + defp do_transform(entity) when is_binary(entity) do + String.trim(entity) |> do_transform_string() end - defp do_transform(value), do: value - - defp do_transform_string(value) when byte_size(value) == 0, do: nil - - defp do_transform_string(value) do - cond do - String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") -> - String.to_existing_atom("Elixir." <> value) + defp do_transform(entity), do: entity - String.starts_with?(value, ":") -> - String.replace(value, ":", "") |> String.to_existing_atom() + defp do_transform_string("~r/" <> pattern) do + pattern = String.trim_trailing(pattern, "/") + ~r/#{pattern}/ + end - String.starts_with?(value, "i:") -> - String.replace(value, "i:", "") |> String.to_integer() + defp do_transform_string(":" <> atom), do: String.to_atom(atom) - true -> - value - end + defp do_transform_string(value) do + if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"), + do: String.to_existing_atom("Elixir." <> value), + else: value end end diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index 3ccc9ca46..49add0b6e 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.AdminAPI.ConfigView do use Pleroma.Web, :view @@ -11,7 +15,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do %{ key: config.key, group: config.group, - value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value) + value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value) } end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index f71c67a3d..f1450b113 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + import Pleroma.Web.Gettext import Pleroma.Web.CommonAPI.Utils def follow(follower, followed) do @@ -74,7 +75,7 @@ defmodule Pleroma.Web.CommonAPI do {:ok, delete} else _ -> - {:error, "Could not delete"} + {:error, dgettext("errors", "Could not delete")} end end @@ -85,7 +86,7 @@ defmodule Pleroma.Web.CommonAPI do ActivityPub.announce(user, object) else _ -> - {:error, "Could not repeat"} + {:error, dgettext("errors", "Could not repeat")} end end @@ -95,7 +96,7 @@ defmodule Pleroma.Web.CommonAPI do ActivityPub.unannounce(user, object) else _ -> - {:error, "Could not unrepeat"} + {:error, dgettext("errors", "Could not unrepeat")} end end @@ -106,7 +107,7 @@ defmodule Pleroma.Web.CommonAPI do ActivityPub.like(user, object) else _ -> - {:error, "Could not favorite"} + {:error, dgettext("errors", "Could not favorite")} end end @@ -116,7 +117,7 @@ defmodule Pleroma.Web.CommonAPI do ActivityPub.unlike(user, object) else _ -> - {:error, "Could not unfavorite"} + {:error, dgettext("errors", "Could not unfavorite")} end end @@ -148,10 +149,10 @@ defmodule Pleroma.Web.CommonAPI do object = Object.get_cached_by_ap_id(object.data["id"]) {:ok, answer_activities, object} else - {:author, _} -> {:error, "Poll's author can't vote"} - {:existing_votes, _} -> {:error, "Already voted"} - {:choice_check, {_, false}} -> {:error, "Invalid indices"} - {:count_check, false} -> {:error, "Too many choices"} + {:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")} + {:existing_votes, _} -> {:error, dgettext("errors", "Already voted")} + {:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")} + {:count_check, false} -> {:error, dgettext("errors", "Too many choices")} end end @@ -248,9 +249,14 @@ defmodule Pleroma.Web.CommonAPI do res else - {:private_to_public, true} -> {:error, "The message visibility must be direct"} - {:error, _} = e -> e - e -> {:error, e} + {:private_to_public, true} -> + {:error, dgettext("errors", "The message visibility must be direct")} + + {:error, _} = e -> + e + + e -> + {:error, e} end end @@ -301,7 +307,7 @@ defmodule Pleroma.Web.CommonAPI do {:error, err} _ -> - {:error, "Could not pin"} + {:error, dgettext("errors", "Could not pin")} end end @@ -318,7 +324,7 @@ defmodule Pleroma.Web.CommonAPI do {:error, err} _ -> - {:error, "Could not unpin"} + {:error, dgettext("errors", "Could not unpin")} end end @@ -326,7 +332,7 @@ defmodule Pleroma.Web.CommonAPI do with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do {:ok, activity} else - {:error, _} -> {:error, "conversation is already muted"} + {:error, _} -> {:error, dgettext("errors", "conversation is already muted")} end end @@ -371,8 +377,8 @@ defmodule Pleroma.Web.CommonAPI do {:ok, activity} else {:error, err} -> {:error, err} - {:account_id, %{}} -> {:error, "Valid `account_id` required"} - {:account, nil} -> {:error, "Account not found"} + {:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")} + {:account, nil} -> {:error, dgettext("errors", "Account not found")} end end @@ -381,14 +387,9 @@ defmodule Pleroma.Web.CommonAPI do {:ok, activity} <- Utils.update_report_state(activity, state) do {:ok, activity} else - nil -> - {:error, :not_found} - - {:error, reason} -> - {:error, reason} - - _ -> - {:error, "Could not update state"} + nil -> {:error, :not_found} + {:error, reason} -> {:error, reason} + _ -> {:error, dgettext("errors", "Could not update state")} end end @@ -398,11 +399,8 @@ defmodule Pleroma.Web.CommonAPI do {:ok, activity} <- set_visibility(activity, opts) do {:ok, activity} else - nil -> - {:error, :not_found} - - {:error, reason} -> - {:error, reason} + nil -> {:error, :not_found} + {:error, reason} -> {:error, reason} end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 8b9477927..8e482eef7 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI.Utils do + import Pleroma.Web.Gettext + alias Calendar.Strftime alias Comeonin.Pbkdf2 alias Pleroma.Activity @@ -372,7 +374,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do true <- Pbkdf2.checkpw(password, db_user.password_hash) do {:ok, db_user} else - _ -> {:error, "Invalid password."} + _ -> {:error, dgettext("errors", "Invalid password.")} end end @@ -455,7 +457,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do if String.length(comment) <= max_size do {:ok, format_input(comment, "text/plain")} else - {:error, "Comment must be up to #{max_size} characters"} + {:error, + dgettext("errors", "Comment must be up to %{max_size} characters", max_size: max_size)} end end @@ -490,7 +493,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do context else _e -> - {:error, "No such conversation"} + {:error, dgettext("errors", "No such conversation")} end end @@ -512,10 +515,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do if length > 0 or Enum.count(attachments) > 0 do :ok else - {:error, "Cannot post an empty status without attachments"} + {:error, dgettext("errors", "Cannot post an empty status without attachments")} end else - {:error, "The status is over the character limit"} + {:error, dgettext("errors", "The status is over the character limit")} end end end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index ddaf88f1d..c123530dc 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -7,13 +7,9 @@ defmodule Pleroma.Web.Endpoint do socket("/socket", Pleroma.Web.UserSocket) - # Serve at "/" the static files from "priv/static" directory. - # - # You should set gzip to true if you are running phoenix.digest - # when deploying your static files in production. + plug(Pleroma.Plugs.SetLocalePlug) plug(CORSPlug) plug(Pleroma.Plugs.HTTPSecurityPlug) - plug(Pleroma.Plugs.UploadedMedia) @static_cache_control "public, no-cache" @@ -30,6 +26,10 @@ defmodule Pleroma.Web.Endpoint do } ) + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phoenix.digest + # when deploying your static files in production. plug( Plug.Static, at: "/", diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 3a3ec7c2a..c82b20123 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MastodonAPI.MastodonAPI do import Ecto.Query import Ecto.Changeset diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 0d3a878bb..8c2033c3a 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -160,10 +160,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) ) else - _e -> - conn - |> put_status(403) - |> json(%{error: "Invalid request"}) + _e -> render_error(conn, :forbidden, "Invalid request") end end @@ -258,10 +255,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do account = AccountView.render("account.json", %{user: user, for: for_user}) json(conn, account) else - _e -> - conn - |> put_status(404) - |> json(%{error: "Can't find user"}) + _e -> render_error(conn, :not_found, "Can't find user") end end @@ -305,7 +299,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do "static_url" => url, "visible_in_picker" => true, "url" => url, - "tags" => tags + "tags" => tags, + # Assuming that a comma is authorized in the category name + "category" => (tags -- ["Custom"]) |> Enum.join(",") } end) end @@ -509,15 +505,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(StatusView) |> try_render("poll.json", %{object: object, for: user}) else - nil -> - conn - |> put_status(404) - |> json(%{error: "Record not found"}) - - false -> - conn - |> put_status(404) - |> json(%{error: "Record not found"}) + nil -> render_error(conn, :not_found, "Record not found") + false -> render_error(conn, :not_found, "Record not found") end end @@ -546,18 +535,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> try_render("poll.json", %{object: object, for: user}) else nil -> - conn - |> put_status(404) - |> json(%{error: "Record not found"}) + render_error(conn, :not_found, "Record not found") false -> - conn - |> put_status(404) - |> json(%{error: "Record not found"}) + render_error(conn, :not_found, "Record not found") {:error, message} -> conn - |> put_status(422) + |> put_status(:unprocessable_entity) |> json(%{error: message}) end end @@ -646,10 +631,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do json(conn, %{}) else - _e -> - conn - |> put_status(403) - |> json(%{error: "Can't delete this post"}) + _e -> render_error(conn, :forbidden, "Can't delete this post") end end @@ -697,8 +679,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, reason} -> conn - |> put_resp_content_type("application/json") - |> send_resp(:bad_request, Jason.encode!(%{"error" => reason})) + |> put_status(:bad_request) + |> json(%{"error" => reason}) end end @@ -774,8 +756,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, reason} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => reason})) + |> put_status(:forbidden) + |> json(%{"error" => reason}) end end @@ -790,8 +772,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, reason} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => reason})) + |> put_status(:forbidden) + |> json(%{"error" => reason}) end end @@ -869,9 +851,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conn |> json(rendered) else - conn - |> put_resp_content_type("application/json") - |> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"})) + render_error(conn, :unsupported_media_type, "mascots can only be images") end end end @@ -1000,8 +980,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1014,8 +994,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1032,8 +1012,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1050,8 +1030,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1080,8 +1060,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1094,8 +1074,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1116,8 +1096,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1131,8 +1111,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1166,8 +1146,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1180,8 +1160,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, message} -> conn - |> put_resp_content_type("application/json") - |> send_resp(403, Jason.encode!(%{"error" => message})) + |> put_status(:forbidden) + |> json(%{error: message}) end end @@ -1229,13 +1209,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(StatusView) |> render("index.json", %{activities: activities, for: for_user, as: :activity}) else - nil -> - {:error, :not_found} - - true -> - conn - |> put_status(403) - |> json(%{error: "Can't get favorites"}) + nil -> {:error, :not_found} + true -> render_error(conn, :forbidden, "Can't get favorites") end end @@ -1267,10 +1242,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do res = ListView.render("list.json", list: list) json(conn, res) else - _e -> - conn - |> put_status(404) - |> json(%{error: "Record not found"}) + _e -> render_error(conn, :not_found, "Record not found") end end @@ -1286,7 +1258,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) else _e -> - json(conn, "error") + json(conn, dgettext("errors", "error")) end end @@ -1337,7 +1309,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, res) else _e -> - json(conn, "error") + json(conn, dgettext("errors", "error")) end end @@ -1361,10 +1333,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) else - _e -> - conn - |> put_status(403) - |> json(%{error: "Error."}) + _e -> render_error(conn, :forbidden, "Error.") end end @@ -1483,8 +1452,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else e -> conn - |> put_resp_content_type("application/json") - |> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) + |> put_status(:internal_server_error) + |> json(%{error: inspect(e)}) end end @@ -1652,20 +1621,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.map_join(", ", fn {_k, v} -> v end) conn - |> put_status(422) + |> put_status(:unprocessable_entity) |> json(%{error: error_message}) end def errors(conn, {:error, :not_found}) do - conn - |> put_status(404) - |> json(%{error: "Record not found"}) + render_error(conn, :not_found, "Record not found") end def errors(conn, _) do conn - |> put_status(500) - |> json("Something went wrong") + |> put_status(:internal_server_error) + |> json(dgettext("errors", "Something went wrong")) end def suggestions(%{assigns: %{user: user}} = conn, _) do @@ -1785,21 +1752,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else {:error, errors} -> conn - |> put_status(400) - |> json(Jason.encode!(errors)) + |> put_status(:bad_request) + |> json(errors) end end def account_register(%{assigns: %{app: _app}} = conn, _params) do - conn - |> put_status(400) - |> json(%{error: "Missing parameters"}) + render_error(conn, :bad_request, "Missing parameters") end def account_register(conn, _) do - conn - |> put_status(403) - |> json(%{error: "Invalid credentials"}) + render_error(conn, :forbidden, "Invalid credentials") end def conversations(%{assigns: %{user: user}} = conn, params) do @@ -1829,21 +1792,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def try_render(conn, target, params) when is_binary(target) do - res = render(conn, target, params) - - if res == nil do - conn - |> put_status(501) - |> json(%{error: "Can't display this activity"}) - else - res + case render(conn, target, params) do + nil -> render_error(conn, :not_implemented, "Can't display this activity") + res -> res end end def try_render(conn, _, _) do - conn - |> put_status(501) - |> json(%{error: "Can't display this activity"}) + render_error(conn, :not_implemented, "Can't display this activity") end defp present?(nil), do: false diff --git a/lib/pleroma/web/mastodon_api/search_controller.ex b/lib/pleroma/web/mastodon_api/search_controller.ex index efa9cc788..9072aa7a4 100644 --- a/lib/pleroma/web/mastodon_api/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/search_controller.ex @@ -4,61 +4,18 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller + alias Pleroma.Activity + alias Pleroma.Plugs.RateLimiter + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web + alias Pleroma.Web.ControllerHelper alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.ControllerHelper - require Logger - - plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search]) - - def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do - accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, []) - statuses = with_fallback(fn -> Activity.search(user, query) end, []) - tags_path = Web.base_url() <> "/tag/" - - tags = - query - |> String.split() - |> Enum.uniq() - |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) - |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) - |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end) - - res = %{ - "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user), - "statuses" => - StatusView.render("index.json", activities: statuses, for: user, as: :activity), - "hashtags" => tags - } - - json(conn, res) - end - - def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do - accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, []) - statuses = with_fallback(fn -> Activity.search(user, query) end, []) - - tags = - query - |> String.split() - |> Enum.uniq() - |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) - |> Enum.map(fn tag -> String.slice(tag, 1..-1) end) - - res = %{ - "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user), - "statuses" => - StatusView.render("index.json", activities: statuses, for: user, as: :activity), - "hashtags" => tags - } - - json(conn, res) - end + plug(RateLimiter, :search when action in [:search, :search2, :account_search]) def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do accounts = User.search(query, search_options(params, user)) @@ -67,17 +24,86 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do json(conn, res) end + def search2(conn, params), do: do_search(:v2, conn, params) + def search(conn, params), do: do_search(:v1, conn, params) + + defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do + options = search_options(params, user) + timeout = Keyword.get(Repo.config(), :timeout, 15_000) + default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []} + + result = + default_values + |> Enum.map(fn {resource, default_value} -> + if params["type"] == nil or params["type"] == resource do + {resource, fn -> resource_search(version, resource, query, options) end} + else + {resource, fn -> default_value end} + end + end) + |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end, + timeout: timeout, + on_timeout: :kill_task + ) + |> Enum.reduce(default_values, fn + {:ok, {resource, result}}, acc -> + Map.put(acc, resource, result) + + _error, acc -> + acc + end) + + json(conn, result) + end + defp search_options(params, user) do [ resolve: params["resolve"] == "true", following: params["following"] == "true", limit: ControllerHelper.fetch_integer_param(params, "limit"), offset: ControllerHelper.fetch_integer_param(params, "offset"), + type: params["type"], + author: get_author(params), for_user: user ] + |> Enum.filter(&elem(&1, 1)) + end + + defp resource_search(_, "accounts", query, options) do + accounts = with_fallback(fn -> User.search(query, options) end) + AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user) + end + + defp resource_search(_, "statuses", query, options) do + statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) + StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity) + end + + defp resource_search(:v2, "hashtags", query, _options) do + tags_path = Web.base_url() <> "/tag/" + + query + |> prepare_tags() + |> Enum.map(fn tag -> + tag = String.trim_leading(tag, "#") + %{name: tag, url: tags_path <> tag} + end) + end + + defp resource_search(:v1, "hashtags", query, _options) do + query + |> prepare_tags() + |> Enum.map(fn tag -> String.trim_leading(tag, "#") end) end - defp with_fallback(f, fallback) do + defp prepare_tags(query) do + query + |> String.split() + |> Enum.uniq() + |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) + end + + defp with_fallback(f, fallback \\ []) do try do f.() rescue @@ -86,4 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do fallback end end + + defp get_author(%{"account_id" => account_id}) when is_binary(account_id), + do: User.get_cached_by_id(account_id) + + defp get_author(_params), do: nil end diff --git a/lib/pleroma/web/mastodon_api/subscription_controller.ex b/lib/pleroma/web/mastodon_api/subscription_controller.ex index b6c8ff808..255ee2f18 100644 --- a/lib/pleroma/web/mastodon_api/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/subscription_controller.ex @@ -59,13 +59,13 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do # def errors(conn, {:error, :not_found}) do conn - |> put_status(404) - |> json("Not found") + |> put_status(:not_found) + |> json(dgettext("errors", "Not found")) end def errors(conn, _) do conn - |> put_status(500) - |> json("Something went wrong") + |> put_status(:internal_server_error) + |> json(dgettext("errors", "Something went wrong")) end end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index af1dcf66d..38bdec737 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MastodonAPI.ConversationView do use Pleroma.Web, :view diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index ec582b919..06a7251d8 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1] # TODO: Add cached version. + defp get_replied_to_activities([]), do: %{} + defp get_replied_to_activities(activities) do activities |> Enum.map(fn @@ -147,8 +149,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do tags = object.data["tag"] || [] sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw") + tag_mentions = + tags + |> Enum.filter(fn tag -> is_map(tag) and tag["type"] == "Mention" end) + |> Enum.map(fn tag -> tag["href"] end) + mentions = - activity.recipients + (object.data["to"] ++ tag_mentions) + |> Enum.uniq() |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end) |> Enum.filter(& &1) |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end) diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex index e9a8cfc8d..4289ebdbd 100644 --- a/lib/pleroma/web/metadata/player_view.ex +++ b/lib/pleroma/web/metadata/player_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Metadata.PlayerView do use Pleroma.Web, :view import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2] diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex index 03af899c4..f87fc1973 100644 --- a/lib/pleroma/web/metadata/rel_me.ex +++ b/lib/pleroma/web/metadata/rel_me.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Metadata.Providers.RelMe do alias Pleroma.Web.Metadata.Providers.Provider @behaviour Provider diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex index 489d5d3a5..b786a521b 100644 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do else false -> conn - |> put_status(403) + |> put_status(:forbidden) |> json(false) _ -> diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 869dda5c5..cd9a4f4a8 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -201,8 +201,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do end def nodeinfo(conn, _) do - conn - |> put_status(404) - |> json(%{error: "Nodeinfo schema version not handled"}) + render_error(conn, :not_found, "Nodeinfo schema version not handled") end end diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex index e3984f009..dd7f08bf1 100644 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ b/lib/pleroma/web/oauth/fallback_controller.ex @@ -9,21 +9,24 @@ defmodule Pleroma.Web.OAuth.FallbackController do def call(conn, {:register, :generic_error}) do conn |> put_status(:internal_server_error) - |> put_flash(:error, "Unknown error, please check the details and try again.") + |> 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, "Invalid Username/Password") + |> 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, "Invalid Username/Password") + |> put_flash(:error, dgettext("errors", "Invalid Username/Password")) |> OAuthController.authorize(conn.params) end end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 3f8e3b074..ef53b7ae3 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -90,7 +90,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do redirect(conn, external: url) else conn - |> put_flash(:error, "Unlisted redirect_uri.") + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) |> redirect(external: redirect_uri(conn, redirect_uri)) end end @@ -128,7 +128,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do redirect(conn, external: url) else conn - |> put_flash(:error, "Unlisted redirect_uri.") + |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri.")) |> redirect(external: redirect_uri(conn, redirect_uri)) end end @@ -142,7 +142,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39 conn - |> put_flash(:error, "This action is outside the authorized scopes") + |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes")) |> put_status(:unauthorized) |> authorize(params) end @@ -155,7 +155,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn - |> put_flash(:error, "Your login is missing a confirmed e-mail address") + |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) |> put_status(:forbidden) |> authorize(params) end @@ -176,9 +176,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, Token.Response.build(user, token, response_attrs)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end @@ -192,9 +190,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, Token.Response.build(user, token, response_attrs)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end @@ -214,18 +210,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:auth_active, false} -> # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 - conn - |> put_status(:forbidden) - |> json(%{error: "Your login is missing a confirmed e-mail address"}) + render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address") {:user_active, false} -> - conn - |> put_status(:forbidden) - |> json(%{error: "Your account is currently disabled"}) + render_error(conn, :forbidden, "Your account is currently disabled") _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + render_invalid_credentials_error(conn) end end @@ -247,9 +238,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build_for_client_credentials(token)) else - _error -> - put_status(conn, 400) - |> json(%{error: "Invalid credentials"}) + _error -> render_invalid_credentials_error(conn) end end @@ -271,9 +260,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Response for bad request defp bad_request(%Plug.Conn{} = conn, _) do - conn - |> put_status(500) - |> json(%{error: "Bad request"}) + render_error(conn, :internal_server_error, "Bad request") end @doc "Prepares OAuth request to provider for Ueberauth" @@ -304,9 +291,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do def request(%Plug.Conn{} = conn, params) do message = if params["provider"] do - "Unsupported OAuth provider: #{params["provider"]}." + dgettext("errors", "Unsupported OAuth provider: %{provider}.", + provider: params["provider"] + ) else - "Bad OAuth request." + dgettext("errors", "Bad OAuth request.") end conn @@ -320,7 +309,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do message = Enum.join(messages, "; ") conn - |> put_flash(:error, "Failed to authenticate: #{message}.") + |> put_flash( + :error, + dgettext("errors", "Failed to authenticate: %{message}.", message: message) + ) |> redirect(external: redirect_uri(conn, params["redirect_uri"])) end @@ -350,7 +342,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns])) conn - |> put_flash(:error, "Failed to set up user account.") + |> put_flash(:error, dgettext("errors", "Failed to set up user account.")) |> redirect(external: redirect_uri(conn, params["redirect_uri"])) end end @@ -468,4 +460,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do |> 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/token/response.ex b/lib/pleroma/web/oauth/token/response.ex index 2648571ad..266110814 100644 --- a/lib/pleroma/web/oauth/token/response.ex +++ b/lib/pleroma/web/oauth/token/response.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.OAuth.Token.Response do @moduledoc false diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex index 7df0be14e..c620050c8 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do @moduledoc """ Functions for dealing with refresh token strategy. diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex index dea63ca54..983f095b4 100644 --- a/lib/pleroma/web/oauth/token/strategy/revoke.ex +++ b/lib/pleroma/web/oauth/token/strategy/revoke.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do @moduledoc """ Functions for dealing with revocation. diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex index 7a4fddafd..1e8765e93 100644 --- a/lib/pleroma/web/oauth/token/utils.ex +++ b/lib/pleroma/web/oauth/token/utils.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.OAuth.Token.Utils do @moduledoc """ Auxiliary functions for dealing with tokens. diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 2fb6ce41b..372d52899 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -245,14 +245,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do end def errors(conn, {:error, :not_found}) do - conn - |> put_status(404) - |> text("Not found") + render_error(conn, :not_found, "Not found") end def errors(conn, _) do - conn - |> put_status(500) - |> text("Something went wrong") + render_error(conn, :internal_server_error, "Something went wrong") end end diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex index fb79630e4..913975616 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do meta_data = diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex index 2530b8c9d..875637c4d 100644 --- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do def parse(html, _data) do with elements = [_ | _] <- get_discovery_data(html), diff --git a/lib/pleroma/web/rich_media/parsers/ogp.ex b/lib/pleroma/web/rich_media/parsers/ogp.ex index 0e1a0e719..d40fa009f 100644 --- a/lib/pleroma/web/rich_media/parsers/ogp.ex +++ b/lib/pleroma/web/rich_media/parsers/ogp.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parsers.OGP do def parse(html, data) do Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse( diff --git a/lib/pleroma/web/rich_media/parsers/twitter_card.ex b/lib/pleroma/web/rich_media/parsers/twitter_card.ex index a317c3e78..e4efe2dd0 100644 --- a/lib/pleroma/web/rich_media/parsers/twitter_card.ex +++ b/lib/pleroma/web/rich_media/parsers/twitter_card.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do def parse(html, data) do Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse( diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex new file mode 100644 index 000000000..8f5a43bf6 --- /dev/null +++ b/lib/pleroma/web/translation_helpers.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TranslationHelpers do + defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do + quote do + require Pleroma.Web.Gettext + + unquote(conn) + |> Plug.Conn.put_status(unquote(status)) + |> Phoenix.Controller.json(%{ + error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)) + }) + end + end +end diff --git a/lib/pleroma/web/uploader_controller.ex b/lib/pleroma/web/uploader_controller.ex index 5d8a77346..bf09775e6 100644 --- a/lib/pleroma/web/uploader_controller.ex +++ b/lib/pleroma/web/uploader_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.UploaderController do use Pleroma.Web, :controller @@ -8,7 +12,7 @@ defmodule Pleroma.Web.UploaderController do end def callbacks(conn, _) do - send_resp(conn, 400, "bad request") + render_error(conn, :bad_request, "bad request") end defp process_callback(conn, pid, params) when is_pid(pid) do @@ -20,6 +24,6 @@ defmodule Pleroma.Web.UploaderController do end defp process_callback(conn, _, _) do - send_resp(conn, 400, "bad request") + render_error(conn, :bad_request, "bad request") end end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 66813e4dd..b42f6887e 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -23,9 +23,11 @@ defmodule Pleroma.Web do 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) diff --git a/lib/transports.ex b/lib/transports.ex index 42f645b21..9f3fc535d 100644 --- a/lib/transports.ex +++ b/lib/transports.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Phoenix.Transports.WebSocket.Raw do import Plug.Conn, only: [ diff --git a/lib/xml_builder.ex b/lib/xml_builder.ex index b58602c7b..ceeef2755 100644 --- a/lib/xml_builder.ex +++ b/lib/xml_builder.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.XmlBuilder do def to_xml({tag, attributes, content}) do open_tag = make_open_tag(tag, attributes) |