diff options
Diffstat (limited to 'lib/pleroma/web')
18 files changed, 210 insertions, 120 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c0e3d1478..55315d66e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -189,6 +189,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end) end + def stream_out_participations(%Object{data: %{"context" => context}}, user) do + with %Conversation{} = conversation <- Conversation.get_for_ap_id(context), + conversation = Repo.preload(conversation, :participations), + last_activity_id = + fetch_latest_activity_id_for_context(conversation.ap_id, %{ + "user" => user, + "blocking_user" => user + }) do + if last_activity_id do + stream_out_participations(conversation.participations) + end + end + end + + def stream_out_participations(_, _), do: :noop + def stream_out(activity) do public = "https://www.w3.org/ns/activitystreams#Public" @@ -401,7 +417,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "to" => to, "deleted_activity_id" => activity && activity.id }, - {:ok, activity} <- insert(data, local), + {:ok, activity} <- insert(data, local, false), + stream_out_participations(object, user), _ <- decrease_replies_count_if_reply(object), # Changing note count prior to enqueuing federation task in order to avoid # race conditions on updating user.info diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 10ff572a2..514266cee 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -151,16 +151,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do def create_context(context) do context = context || generate_id("contexts") - changeset = Object.context_mapping(context) - case Repo.insert(changeset) do - {:ok, object} -> - object + # Ecto has problems accessing the constraint inside the jsonb, + # so we explicitly check for the existed object before insert + object = Object.get_cached_by_ap_id(context) - # This should be solved by an upsert, but it seems ecto - # has problems accessing the constraint inside the jsonb. - {:error, _} -> - Object.get_cached_by_ap_id(context) + with true <- is_nil(object), + changeset <- Object.context_mapping(context), + {:ok, inserted_object} <- Repo.insert(changeset) do + inserted_object + else + _ -> + object end end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 03dfdca82..498beb56a 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -377,18 +377,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do if Pleroma.Config.get([:instance, :dynamic_configuration]) do updated = Enum.map(configs, fn - %{"key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{key: key, value: value}) + %{"group" => group, "key" => key, "value" => value} -> + {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) config - %{"key" => key, "delete" => "true"} -> - {:ok, _} = Config.delete(key) + %{"group" => group, "key" => key, "delete" => "true"} -> + {:ok, _} = Config.delete(%{group: group, key: key}) nil end) |> Enum.reject(&is_nil(&1)) Pleroma.Config.TransferTask.load_and_update_env() - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env)]) + Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"]) updated else [] diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index b7072f050..8b9b658a9 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -12,26 +12,27 @@ defmodule Pleroma.Web.AdminAPI.Config do schema "config" do field(:key, :string) + field(:group, :string) field(:value, :binary) timestamps() end - @spec get_by_key(String.t()) :: Config.t() | nil - def get_by_key(key), do: Repo.get_by(Config, key: key) + @spec get_by_params(map()) :: Config.t() | nil + def get_by_params(params), do: Repo.get_by(Config, params) @spec changeset(Config.t(), map()) :: Changeset.t() def changeset(config, params \\ %{}) do config - |> cast(params, [:key, :value]) - |> validate_required([:key, :value]) - |> unique_constraint(:key) + |> cast(params, [:key, :group, :value]) + |> validate_required([:key, :group, :value]) + |> unique_constraint(:key, name: :config_group_key_index) end @spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def create(%{key: key, value: value}) do + def create(params) do %Config{} - |> changeset(%{key: key, value: transform(value)}) + |> changeset(Map.put(params, :value, transform(params[:value]))) |> Repo.insert() end @@ -43,20 +44,20 @@ defmodule Pleroma.Web.AdminAPI.Config do end @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def update_or_create(%{key: key} = params) do - with %Config{} = config <- Config.get_by_key(key) do + def update_or_create(params) do + with %Config{} = config <- Config.get_by_params(Map.take(params, [:group, :key])) do Config.update(config, params) else nil -> Config.create(params) end end - @spec delete(String.t()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def delete(key) do - with %Config{} = config <- Config.get_by_key(key) do + @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} + def delete(params) do + with %Config{} = config <- Config.get_by_params(params) do Repo.delete(config) else - nil -> {:error, "Config with key #{key} not found"} + nil -> {:error, "Config with params #{inspect(params)} not found"} end end @@ -77,10 +78,21 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_convert({k, v} = value) when is_tuple(value), do: %{k => do_convert(v)} - defp do_convert(value) when is_binary(value) or is_atom(value) or is_map(value), - do: value + defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))} + + defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value + + defp do_convert(value) when is_atom(value) do + string = to_string(value) + + if String.starts_with?(string, "Elixir."), + do: String.trim_leading(string, "Elixir."), + else: value + end @spec transform(any()) :: binary() + def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity)) + def transform(entity) when is_map(entity) do tuples = for {k, v} <- entity, @@ -101,11 +113,16 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_transform(%Regex{} = value) when is_map(value), do: value + defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do + {do_transform(k), do_transform(values)} + end + + defp do_transform(%{"tuple" => values}) do + Enum.reduce(values, {}, 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)} + values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)} Enum.sort(values) end @@ -117,28 +134,27 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity) defp do_transform(value) when is_binary(value) do - value = String.trim(value) + String.trim(value) + |> do_transform_string() + end - case String.length(value) do - 0 -> - nil + defp do_transform(value), do: value - _ -> - cond do - String.starts_with?(value, "Pleroma") -> - String.to_existing_atom("Elixir." <> value) + defp do_transform_string(value) when byte_size(value) == 0, do: nil - String.starts_with?(value, ":") -> - String.replace(value, ":", "") |> String.to_existing_atom() + 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) - String.starts_with?(value, "i:") -> - String.replace(value, "i:", "") |> String.to_integer() + String.starts_with?(value, ":") -> + String.replace(value, ":", "") |> String.to_existing_atom() - true -> - value - end + String.starts_with?(value, "i:") -> + String.replace(value, "i:", "") |> String.to_integer() + + true -> + value end end - - defp do_transform(value), do: value end diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index c8560033e..3ccc9ca46 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do def render("show.json", %{config: config}) do %{ key: config.key, + group: config.group, value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value) } end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 42b78494d..f8df1e2ea 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -247,6 +247,7 @@ defmodule Pleroma.Web.CommonAPI do res else + {:private_to_public, true} -> {:error, "The message visibility must be direct"} {:error, _} = e -> e e -> {:error, e} end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 0c22790f2..7cdba4cc0 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -561,18 +561,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else params = Map.drop(params, ["scheduled_at"]) - case get_cached_status_or_post(conn, params) do - {:ignore, message} -> - conn - |> put_status(422) - |> json(%{error: message}) - + case CommonAPI.post(user, params) do {:error, message} -> conn - |> put_status(422) + |> put_status(:unprocessable_entity) |> json(%{error: message}) - {_, activity} -> + {:ok, activity} -> conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -580,21 +575,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do - idempotency_key = - case get_req_header(conn, "idempotency-key") do - [key] -> key - _ -> Ecto.UUID.generate() - end - - Cachex.fetch(:idempotency_cache, idempotency_key, fn _ -> - case CommonAPI.post(user, params) do - {:ok, activity} -> activity - {:error, message} -> {:ignore, message} - end - end) - end - def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do json(conn, %{}) @@ -844,7 +824,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conn |> put_view(AccountView) - |> render(AccountView, "accounts.json", %{for: user, users: users, as: :user}) + |> render("accounts.json", %{for: user, users: users, as: :user}) else _ -> json(conn, []) end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index 18973413e..d53e20d12 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -76,14 +76,16 @@ defmodule Pleroma.Web.OAuth.Authorization do def use_token(%Authorization{used: true}), do: {:error, "already used"} @spec delete_user_authorizations(User.t()) :: {integer(), any()} - def delete_user_authorizations(%User{id: user_id}) do - from( - a in Pleroma.Web.OAuth.Authorization, - where: a.user_id == ^user_id - ) + def delete_user_authorizations(%User{} = user) do + user + |> delete_by_user_query |> Repo.delete_all() end + def delete_by_user_query(%User{id: user_id}) do + from(a in __MODULE__, where: a.user_id == ^user_id) + end + @doc "gets auth for app by token" @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(%App{id: app_id} = _app, token) do diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 94f56f70d..6506de46c 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -4,27 +4,53 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser + @spec validate_page_url(any()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld] - if AutoLinker.Parser.url?(page_url, scheme: true, validate_tld: validate_tld) do - URI.parse(page_url) |> validate_page_url - else - :error + page_url + |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld) + |> parse_uri(page_url) + end + + defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority}) + when scheme == "https" and not is_nil(authority) do + cond do + host in Config.get([:rich_media, :ignore_hosts], []) -> + :error + + get_tld(host) in Config.get([:rich_media, :ignore_tld], []) -> + :error + + true -> + :ok end end - defp validate_page_url(%URI{authority: nil}), do: :error - defp validate_page_url(%URI{scheme: nil}), do: :error - defp validate_page_url(%URI{}), do: :ok defp validate_page_url(_), do: :error + defp parse_uri(true, url) do + url + |> URI.parse() + |> validate_page_url + end + + defp parse_uri(_, _), do: :error + + defp get_tld(host) do + host + |> String.split(".") + |> Enum.reverse() + |> hd + end + def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do - with true <- Pleroma.Config.get([:rich_media, :enabled]), + with true <- Config.get([:rich_media, :enabled]), %Object{} = object <- Object.normalize(activity), false <- object.data["sensitive"] || false, {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), 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 4a7c5eae0..fb79630e4 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -1,15 +1,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do - with elements = [_ | _] <- get_elements(html, key_name, prefix), - meta_data = - Enum.reduce(elements, data, fn el, acc -> - attributes = normalize_attributes(el, prefix, key_name, value_name) + meta_data = + html + |> get_elements(key_name, prefix) + |> Enum.reduce(data, fn el, acc -> + attributes = normalize_attributes(el, prefix, key_name, value_name) - Map.merge(acc, attributes) - end) do - {:ok, meta_data} + Map.merge(acc, attributes) + end) + |> maybe_put_title(html) + + if Enum.empty?(meta_data) do + {:error, error_message} else - _e -> {:error, error_message} + {:ok, meta_data} end end @@ -27,4 +31,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do %{String.to_atom(data[key_name]) => data[value_name]} end + + defp maybe_put_title(%{title: _} = meta, _), do: meta + + defp maybe_put_title(meta, html) when meta != %{} do + case get_page_title(html) do + "" -> meta + title -> Map.put_new(meta, :title, title) + end + end + + defp maybe_put_title(meta, _), do: meta + + defp get_page_title(html) do + Floki.find(html, "title") |> Floki.text() + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 837153ed4..055289dc5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.UserEnabledPlug) plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Plugs.IdempotencyPlug) end pipeline :authenticated_api do @@ -41,6 +42,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.UserEnabledPlug) plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug) + plug(Pleroma.Plugs.IdempotencyPlug) end pipeline :admin_api do @@ -57,6 +59,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.UserIsAdminPlug) + plug(Pleroma.Plugs.IdempotencyPlug) end pipeline :mastodon_html do @@ -133,8 +136,8 @@ defmodule Pleroma.Web.Router do scope "/api/pleroma", Pleroma.Web.TwitterAPI do pipe_through(:pleroma_api) - get("/password_reset/:token", UtilController, :show_password_reset) - post("/password_reset", UtilController, :password_reset) + get("/password_reset/:token", PasswordController, :reset, as: :reset_password) + post("/password_reset", PasswordController, :do_reset, as: :reset_password) get("/emoji", UtilController, :emoji) get("/captcha", UtilController, :captcha) get("/healthcheck", UtilController, :healthcheck) diff --git a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex index ee84750c7..ee84750c7 100644 --- a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex index a3facf017..7d3ef6b0d 100644 --- a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex @@ -1,5 +1,5 @@ <h2>Password Reset for <%= @user.nickname %></h2> -<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %> +<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %> <div class="form-row"> <%= label f, :password, "Password" %> <%= password_input f, :password %> diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex index df037c01e..df037c01e 100644 --- a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex index f30ba3274..f30ba3274 100644 --- a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex new file mode 100644 index 000000000..1941e6143 --- /dev/null +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.PasswordController do + @moduledoc """ + The module containts functions for reset password. + """ + + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.PasswordResetToken + alias Pleroma.Repo + alias Pleroma.User + + def reset(conn, %{"token" => token}) do + with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), + %User{} = user <- User.get_cached_by_id(token.user_id) do + render(conn, "reset.html", %{ + token: token, + user: user + }) + else + _e -> render(conn, "invalid_token.html") + end + end + + def do_reset(conn, %{"data" => data}) do + with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do + render(conn, "reset_success.html") + else + _e -> render(conn, "reset_failed.html") + end + end +end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 489170d80..b1863528f 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Activity alias Pleroma.Emoji alias Pleroma.Notification - alias Pleroma.PasswordResetToken - alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web.OStatus alias Pleroma.Web.WebFinger - def show_password_reset(conn, %{"token" => token}) do - with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- User.get_cached_by_id(token.user_id) do - render(conn, "password_reset.html", %{ - token: token, - user: user - }) - else - _e -> render(conn, "invalid_token.html") - end - end - - def password_reset(conn, %{"data" => data}) do - with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do - render(conn, "password_reset_success.html") - else - _e -> render(conn, "password_reset_failed.html") - end - end - def help_test(conn, _params) do json(conn, "ok") end diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex new file mode 100644 index 000000000..b166b925d --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/password_view.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.PasswordView do + use Pleroma.Web, :view + import Phoenix.HTML.Form +end |