diff options
Diffstat (limited to 'lib/pleroma/web')
180 files changed, 2388 insertions, 1263 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 673fc8a99..d9f30e629 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1,11 +1,12 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics alias Pleroma.Config + alias Pleroma.Constants alias Pleroma.Conversation alias Pleroma.Conversation.Participation alias Pleroma.Notification @@ -124,6 +125,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def increase_poll_votes_if_vote(_create_data), do: :noop + @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()} # TODO rewrite in with style @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(object, meta) do @@ -249,12 +251,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :noop end - def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do + @spec create(map(), boolean()) :: {:ok, Activity.t()} | {:error, any()} + def create(params, fake \\ false) do + with {:ok, result} <- Repo.transaction(fn -> do_create(params, fake) end) do + result + end + end + + defp do_create(%{to: to, actor: actor, context: context, object: object} = params, fake) do additional = params[:additional] || %{} # only accept false as false value local = !(params[:local] == false) published = params[:published] - quick_insert? = Pleroma.Config.get([:env]) == :benchmark + quick_insert? = Config.get([:env]) == :benchmark with create_data <- make_create_data( @@ -277,10 +286,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, activity} {:error, message} -> - {:error, message} + Repo.rollback(message) end end + @spec listen(map()) :: {:ok, Activity.t()} | {:error, any()} def listen(%{to: to, actor: actor, context: context, object: object} = params) do additional = params[:additional] || %{} # only accept false as false value @@ -295,20 +305,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, activity} <- insert(listen_data, local), :ok <- maybe_federate(activity) do {:ok, activity} - else - {:error, message} -> - {:error, message} end end + @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()} def accept(params) do accept_or_reject("Accept", params) end + @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()} def reject(params) do accept_or_reject("Reject", params) end + @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()} def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do local = Map.get(params, :local, true) activity_id = Map.get(params, :activity_id, nil) @@ -322,6 +332,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @spec update(map()) :: {:ok, Activity.t()} | {:error, any()} def update(%{to: to, cc: cc, actor: actor, object: object} = params) do local = !(params[:local] == false) activity_id = params[:activity_id] @@ -340,19 +351,40 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) :: + {:ok, Activity.t(), Object.t()} | {:error, any()} def react_with_emoji(user, object, emoji, options \\ []) do + with {:ok, result} <- + Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do + result + end + end + + defp do_react_with_emoji(user, object, emoji, options) do with local <- Keyword.get(options, :local, true), activity_id <- Keyword.get(options, :activity_id, nil), - Pleroma.Emoji.is_unicode_emoji?(emoji), + true <- Pleroma.Emoji.is_unicode_emoji?(emoji), reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), {:ok, activity} <- insert(reaction_data, local), {:ok, object} <- add_emoji_reaction_to_object(activity, object), :ok <- maybe_federate(activity) do {:ok, activity, object} + else + false -> {:error, false} + {:error, error} -> Repo.rollback(error) end end + @spec unreact_with_emoji(User.t(), String.t(), keyword()) :: + {:ok, Activity.t(), Object.t()} | {:error, any()} def unreact_with_emoji(user, reaction_id, options \\ []) do + with {:ok, result} <- + Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do + result + end + end + + defp do_unreact_with_emoji(user, reaction_id, options) do with local <- Keyword.get(options, :local, true), activity_id <- Keyword.get(options, :activity_id, nil), user_ap_id <- user.ap_id, @@ -363,16 +395,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), :ok <- maybe_federate(activity) do {:ok, activity, object} + else + {:error, error} -> Repo.rollback(error) end end # TODO: This is weird, maybe we shouldn't check here if we can make the activity. - def like( - %User{ap_id: ap_id} = user, - %Object{data: %{"id" => _}} = object, - activity_id \\ nil, - local \\ true - ) do + @spec like(User.t(), Object.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t(), Object.t()} | {:error, any()} + def like(user, object, activity_id \\ nil, local \\ true) do + with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do + result + end + end + + defp do_like( + %User{ap_id: ap_id} = user, + %Object{data: %{"id" => _}} = object, + activity_id, + local + ) do with nil <- get_existing_like(ap_id, object), like_data <- make_like_data(user, object, activity_id), {:ok, activity} <- insert(like_data, local), @@ -380,12 +422,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- maybe_federate(activity) do {:ok, activity, object} else - %Activity{} = activity -> {:ok, activity, object} - error -> {:error, error} + %Activity{} = activity -> + {:ok, activity, object} + + {:error, error} -> + Repo.rollback(error) end end + @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()} def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do + with {:ok, result} <- + Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do + result + end + end + + defp do_unlike(actor, object, activity_id, local) do with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), unlike_data <- make_unlike_data(actor, like_activity, activity_id), {:ok, unlike_activity} <- insert(unlike_data, local), @@ -394,10 +448,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- maybe_federate(unlike_activity) do {:ok, unlike_activity, like_activity, object} else - _e -> {:ok, object} + nil -> {:ok, object} + {:error, error} -> Repo.rollback(error) end end + @spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) :: + {:ok, Activity.t(), Object.t()} | {:error, any()} def announce( %User{ap_id: _} = user, %Object{data: %{"id" => _}} = object, @@ -405,6 +462,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do local \\ true, public \\ true ) do + with {:ok, result} <- + Repo.transaction(fn -> do_announce(user, object, activity_id, local, public) end) do + result + end + end + + defp do_announce(user, object, activity_id, local, public) do with true <- is_announceable?(object, user, public), announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), @@ -412,16 +476,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- maybe_federate(activity) do {:ok, activity, object} else - error -> {:error, error} + false -> {:error, false} + {:error, error} -> Repo.rollback(error) end end + @spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()} def unannounce( %User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true ) do + with {:ok, result} <- + Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do + result + end + end + + defp do_unannounce(actor, object, activity_id, local) do with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), {:ok, unannounce_activity} <- insert(unannounce_data, local), @@ -430,30 +504,61 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, object} <- remove_announce_from_object(announce_activity, object) do {:ok, unannounce_activity, object} else - _e -> {:ok, object} + nil -> {:ok, object} + {:error, error} -> Repo.rollback(error) end end + @spec follow(User.t(), User.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t()} | {:error, any()} def follow(follower, followed, activity_id \\ nil, local \\ true) do + with {:ok, result} <- + Repo.transaction(fn -> do_follow(follower, followed, activity_id, local) end) do + result + end + end + + defp do_follow(follower, followed, activity_id, local) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), :ok <- maybe_federate(activity), _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do {:ok, activity} + else + {:error, error} -> Repo.rollback(error) end end + @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t()} | nil | {:error, any()} def unfollow(follower, followed, activity_id \\ nil, local \\ true) do + with {:ok, result} <- + Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do + result + end + end + + defp do_unfollow(follower, followed, activity_id, local) do with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), {:ok, activity} <- insert(unfollow_data, local), :ok <- maybe_federate(activity) do {:ok, activity} + else + nil -> nil + {:error, error} -> Repo.rollback(error) + end + end + + @spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()} + def delete(entity, options \\ []) do + with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do + result end end - def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do + defp do_delete(%User{ap_id: ap_id, follower_address: follower_address} = user, _) do with data <- %{ "to" => [follower_address], "type" => "Delete", @@ -466,7 +571,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ []) do + defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do local = Keyword.get(options, :local, true) activity_id = Keyword.get(options, :activity_id, nil) actor = Keyword.get(options, :actor, actor) @@ -474,27 +579,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do user = User.get_cached_by_ap_id(actor) to = (object.data["to"] || []) ++ (object.data["cc"] || []) - with {:ok, object, activity} <- Object.delete(object), + with create_activity <- Activity.get_create_by_object_ap_id(id), data <- %{ "type" => "Delete", "actor" => actor, "object" => id, "to" => to, - "deleted_activity_id" => activity && activity.id + "deleted_activity_id" => create_activity && create_activity.id } |> maybe_put("id", activity_id), {:ok, activity} <- insert(data, local, false), + {:ok, object, _create_activity} <- Object.delete(object), stream_out_participations(object, user), _ <- decrease_replies_count_if_reply(object), {:ok, _actor} <- decrease_note_count_if_public(user, object), :ok <- maybe_federate(activity) do {:ok, activity} + else + {:error, error} -> + Repo.rollback(error) end end - @spec block(User.t(), User.t(), String.t() | nil, boolean) :: {:ok, Activity.t() | nil} + @spec block(User.t(), User.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t()} | {:error, any()} def block(blocker, blocked, activity_id \\ nil, local \\ true) do + with {:ok, result} <- + Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do + result + end + end + + defp do_block(blocker, blocked, activity_id, local) do outgoing_blocks = Config.get([:activitypub, :outgoing_blocks]) unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) @@ -509,20 +626,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- maybe_federate(activity) do {:ok, activity} else - _e -> {:ok, nil} + {:error, error} -> Repo.rollback(error) end end + @spec unblock(User.t(), User.t(), String.t() | nil, boolean()) :: + {:ok, Activity.t()} | {:error, any()} | nil def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do + with {:ok, result} <- + Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do + result + end + end + + defp do_unblock(blocker, blocked, activity_id, local) do with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked), unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id), {:ok, activity} <- insert(unblock_data, local), :ok <- maybe_federate(activity) do {:ok, activity} + else + nil -> nil + {:error, error} -> Repo.rollback(error) end end - @spec flag(map()) :: {:ok, Activity.t()} | any + @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()} def flag( %{ actor: actor, @@ -549,7 +678,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, activity} <- insert(flag_data, local), {:ok, stripped_activity} <- strip_report_status_data(activity), :ok <- maybe_federate(stripped_activity) do - Enum.each(User.all_superusers(), fn superuser -> + User.all_superusers() + |> Enum.filter(fn user -> not is_nil(user.email) end) + |> Enum.each(fn superuser -> superuser |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content) |> Pleroma.Emails.Mailer.deliver_async() @@ -559,6 +690,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @spec move(User.t(), User.t(), boolean()) :: {:ok, Activity.t()} | {:error, any()} def move(%User{} = origin, %User{} = target, local \\ true) do params = %{ "type" => "Move", @@ -584,7 +716,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp fetch_activities_for_context_query(context, opts) do - public = [Pleroma.Constants.as_public()] + public = [Constants.as_public()] recipients = if opts["user"], @@ -629,10 +761,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Repo.one() end + @spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()] def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do opts = Map.drop(opts, ["user"]) - [Pleroma.Constants.as_public()] + [Constants.as_public()] |> fetch_activities_query(opts) |> restrict_unlisted() |> Pagination.fetch_paginated(opts, pagination) @@ -745,7 +878,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do params |> Map.put("user", reading_user) |> Map.put("actor_id", user.ap_id) - |> Map.put("whole_db", true) recipients = user_activities_recipients(%{ @@ -763,9 +895,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Map.put("type", ["Create", "Announce"]) |> Map.put("user", reading_user) |> Map.put("actor_id", user.ap_id) - |> Map.put("whole_db", true) |> Map.put("pinned_activity_ids", user.pinned_activities) + params = + if User.blocks?(reading_user, user) do + params + else + params + |> Map.put("blocking_user", reading_user) + |> Map.put("muting_user", reading_user) + end + recipients = user_activities_recipients(%{ "godmode" => params["godmode"], @@ -776,14 +916,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end - def fetch_instance_activities(params) do + def fetch_statuses(reading_user, params) do params = params |> Map.put("type", ["Create", "Announce"]) - |> Map.put("instance", params["instance"]) - |> Map.put("whole_db", true) - fetch_activities([Pleroma.Constants.as_public()], params, :offset) + recipients = + user_activities_recipients(%{ + "godmode" => params["godmode"], + "reading_user" => reading_user + }) + + fetch_activities(recipients, params, :offset) |> Enum.reverse() end @@ -793,9 +937,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp user_activities_recipients(%{"reading_user" => reading_user}) do if reading_user do - [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)] + [Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)] else - [Pleroma.Constants.as_public()] + [Constants.as_public()] end end @@ -937,7 +1081,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do - mutes = user.mutes + mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user) query = from([activity] in query, @@ -954,26 +1098,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, _), do: query - defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do - blocks = user.blocks || [] + defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do + blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user) domain_blocks = user.domain_blocks || [] + following_ap_ids = User.get_friends_ap_ids(user) + query = if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query) from( [activity, object: o] in query, - where: fragment("not (? = ANY(?))", activity.actor, ^blocks), - where: fragment("not (? && ?)", activity.recipients, ^blocks), + where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), + where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), where: fragment( "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", activity.data, activity.data, - ^blocks + ^blocked_ap_ids ), - where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks), - where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks) + where: + fragment( + "(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)", + activity.actor, + ^domain_blocks, + activity.actor, + ^following_ap_ids + ), + where: + fragment( + "(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)", + o.data, + ^domain_blocks, + o.data, + ^following_ap_ids + ) ) end @@ -986,7 +1146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fragment( "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", activity.data, - ^[Pleroma.Constants.as_public()] + ^[Constants.as_public()] ) ) end @@ -997,8 +1157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_pinned(query, _), do: query - defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do - muted_reblogs = user.muted_reblogs || [] + defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do + muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user) from( activity in query, @@ -1060,6 +1220,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Activity.with_preloaded_bookmark(opts["user"]) end + defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do + query + |> Activity.with_preloaded_report_notes() + end + + defp maybe_preload_report_notes(query, _), do: query + defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query defp maybe_set_thread_muted_field(query, opts) do @@ -1079,7 +1246,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_order(query, _), do: query + defp fetch_activities_query_ap_ids_ops(opts) do + source_user = opts["muting_user"] + ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: [] + + ap_id_relations = + ap_id_relations ++ + if opts["blocking_user"] && opts["blocking_user"] == source_user do + [:block] + else + [] + end + + preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations) + + restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts) + restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts) + + restrict_muted_reblogs_opts = + Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts) + + {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} + end + def fetch_activities_query(recipients, opts \\ %{}) do + {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} = + fetch_activities_query_ap_ids_ops(opts) + config = %{ skip_thread_containment: Config.get([:instance, :skip_thread_containment]) } @@ -1087,6 +1280,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Activity |> maybe_preload_objects(opts) |> maybe_preload_bookmarks(opts) + |> maybe_preload_report_notes(opts) |> maybe_set_thread_muted_field(opts) |> maybe_order(opts) |> restrict_recipients(recipients, opts["user"]) @@ -1099,15 +1293,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_type(opts) |> restrict_state(opts) |> restrict_favorited_by(opts) - |> restrict_blocked(opts) - |> restrict_muted(opts) + |> restrict_blocked(restrict_blocked_opts) + |> restrict_muted(restrict_muted_opts) |> restrict_media(opts) |> restrict_visibility(opts) |> restrict_thread_visibility(opts, config) |> restrict_replies(opts) |> restrict_reblogs(opts) |> restrict_pinned(opts) - |> restrict_muted_reblogs(opts) + |> restrict_muted_reblogs(restrict_muted_reblogs_opts) |> restrict_instance(opts) |> Activity.restrict_deactivated_users() |> exclude_poll_votes(opts) @@ -1123,6 +1317,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> maybe_update_cc(list_memberships, opts["user"]) end + @doc """ + Fetch favorites activities of user with order by sort adds to favorites + """ + @spec fetch_favourites(User.t(), map(), Pagination.type()) :: list(Activity.t()) + def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do + user.ap_id + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Like") + |> Activity.with_joined_object() + |> Object.with_joined_activity() + |> select([_like, object, activity], %{activity | object: object}) + |> order_by([like, _, _], desc: like.id) + |> Pagination.fetch_paginated( + Map.merge(params, %{"skip_order" => true}), + pagination, + :object_activity + ) + end + defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id}) when is_list(list_memberships) and length(list_memberships) > 0 do Enum.map(activities, fn @@ -1145,7 +1358,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do where: fragment("? && ?", activity.recipients, ^recipients) or (fragment("? && ?", activity.recipients, ^recipients_with_public) and - ^Pleroma.Constants.as_public() in activity.recipients) + ^Constants.as_public() in activity.recipients) ) end @@ -1161,6 +1374,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end + @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do with {:ok, data} <- Upload.store(file, opts) do obj_data = @@ -1199,6 +1413,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do data = Transmogrifier.maybe_fix_user_object(data) discoverable = data["discoverable"] || false invisible = data["invisible"] || false + actor_type = data["type"] || "Person" user_data = %{ ap_id: data["id"], @@ -1214,6 +1429,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do follower_address: data["followers"], following_address: data["following"], bio: data["summary"], + actor_type: actor_type, also_known_as: Map.get(data, "alsoKnownAs", []) } @@ -1235,31 +1451,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def fetch_follow_information_for_user(user) do with {:ok, following_data} <- Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), - following_count when is_integer(following_count) <- following_data["totalItems"], {:ok, hide_follows} <- collection_private(following_data), {:ok, followers_data} <- Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address), - followers_count when is_integer(followers_count) <- followers_data["totalItems"], {:ok, hide_followers} <- collection_private(followers_data) do {:ok, %{ hide_follows: hide_follows, - follower_count: followers_count, - following_count: following_count, + follower_count: normalize_counter(followers_data["totalItems"]), + following_count: normalize_counter(following_data["totalItems"]), hide_followers: hide_followers }} else - {:error, _} = e -> - e - - e -> - {:error, e} + {:error, _} = e -> e + e -> {:error, e} end end + defp normalize_counter(counter) when is_integer(counter), do: counter + defp normalize_counter(_), do: 0 + defp maybe_update_follow_information(data) do - with {:enabled, true} <- - {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])}, + with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])}, {:ok, info} <- fetch_follow_information_for_user(data) do info = Map.merge(data[:info] || %{}, info) Map.put(data, :info, info) @@ -1276,24 +1489,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + defp collection_private(%{"first" => %{"type" => type}}) + when type in ["CollectionPage", "OrderedCollectionPage"], + do: {:ok, false} + defp collection_private(%{"first" => first}) do - if is_map(first) and - first["type"] in ["CollectionPage", "OrderedCollectionPage"] do + with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <- + Fetcher.fetch_and_contain_remote_object_from_id(first) do {:ok, false} else - with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <- - Fetcher.fetch_and_contain_remote_object_from_id(first) do - {:ok, false} - else - {:error, {:ok, %{status: code}}} when code in [401, 403] -> - {:ok, true} - - {:error, _} = e -> - e - - e -> - {:error, e} - end + {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true} + {:error, _} = e -> e + e -> {:error, e} end end @@ -1314,6 +1521,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do data <- maybe_update_follow_information(data) do {:ok, data} else + {:error, "Object has been deleted"} = e -> + Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}") + {:error, e} + e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") {:error, e} diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index dec5da0d3..8b9eb4a2c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPubController do @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Delivery alias Pleroma.Object alias Pleroma.Object.Fetcher + alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.InternalFetchActor @@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.FederatingPlug alias Pleroma.Web.Federator require Logger action_fallback(:errors) + @federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers] + + plug(FederatingPlug when action in @federating_only_actions) + + plug( + EnsureAuthenticatedPlug, + [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions + ) + + plug( + EnsureAuthenticatedPlug + when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers] + ) + plug( Pleroma.Plugs.Cache, [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] when action in [:activity, :object] ) - plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay]) plug(:set_requester_reachable when action in [:inbox]) plug(:relay_active? when action in [:relay]) - def relay_active?(conn, _) do + defp relay_active?(conn, _) do if Pleroma.Config.get([:instance, :allow_relay]) do conn else @@ -127,11 +142,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end # GET /relay/following - def following(%{assigns: %{relay: true}} = conn, _params) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("following.json", %{user: Relay.get_actor()}) + def relay_following(conn, _params) do + with %{halted: false} = conn <- FederatingPlug.call(conn, []) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("following.json", %{user: Relay.get_actor()}) + end end def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -164,11 +181,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end # GET /relay/followers - def followers(%{assigns: %{relay: true}} = conn, _params) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("followers.json", %{user: Relay.get_actor()}) + def relay_followers(conn, _params) do + with %{halted: false} = conn <- FederatingPlug.call(conn, []) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("followers.json", %{user: Relay.get_actor()}) + end end def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -200,13 +219,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) + def outbox( + %{assigns: %{user: for_user}} = conn, + %{"nickname" => nickname, "page" => page?} = params + ) when page? in [true, "true"] do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- User.ensure_keys_present(user) do activities = if params["max_id"] do - ActivityPub.fetch_user_activities(user, nil, %{ + ActivityPub.fetch_user_activities(user, for_user, %{ "max_id" => params["max_id"], # This is a hack because postgres generates inefficient queries when filtering by # 'Answer', poll votes will be hidden by the visibility filter in this case anyway @@ -214,7 +236,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do "limit" => 10 }) else - ActivityPub.fetch_user_activities(user, nil, %{ + ActivityPub.fetch_user_activities(user, for_user, %{ "limit" => 10, "include_poll_votes" => true }) @@ -255,9 +277,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do json(conn, "ok") end - # only accept relayed Creates - def inbox(conn, %{"type" => "Create"} = params) do - Logger.info( + # POST /relay/inbox -or- POST /internal/fetch/inbox + def inbox(conn, params) do + if params["type"] == "Create" && FederatingPlug.federating?() do + post_inbox_relayed_create(conn, params) + else + post_inbox_fallback(conn, params) + end + end + + defp post_inbox_relayed_create(conn, params) do + Logger.debug( "Signature missing or not from author, relayed Create message, fetching object from source" ) @@ -266,18 +296,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do json(conn, "ok") end - def inbox(conn, params) do + defp post_inbox_fallback(conn, params) do headers = Enum.into(conn.req_headers, %{}) - if String.contains?(headers["signature"], params["actor"]) do - Logger.info( + if headers["signature"] && params["actor"] && + String.contains?(headers["signature"], params["actor"]) do + Logger.debug( "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" ) - Logger.info(inspect(conn.req_headers)) + Logger.debug(inspect(conn.req_headers)) end - json(conn, dgettext("errors", "error")) + conn + |> put_status(:bad_request) + |> json(dgettext("errors", "error")) end defp represent_service_actor(%User{} = user, conn) do @@ -311,10 +344,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> render("user.json", %{user: user}) end - def whoami(_conn, _params), do: {:error, :not_found} - def read_inbox( - %{assigns: %{user: %{nickname: nickname} = user}} = conn, + %{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do @@ -337,7 +368,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do }) end - def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ + def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{ "nickname" => nickname }) do with {:ok, user} <- User.ensure_keys_present(user) do @@ -348,15 +379,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do - err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname) - - conn - |> put_status(:forbidden) - |> json(err) - end - - def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ + def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ "nickname" => nickname }) do err = @@ -370,7 +393,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> json(err) end - def handle_user_activity(user, %{"type" => "Create"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do object = params["object"] |> Map.merge(Map.take(params, ["to", "cc"])) @@ -386,7 +409,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do }) end - def handle_user_activity(user, %{"type" => "Delete"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do with %Object{} = object <- Object.normalize(params["object"]), true <- user.is_moderator || user.ap_id == object.data["actor"], {:ok, delete} <- ActivityPub.delete(object) do @@ -396,7 +419,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def handle_user_activity(user, %{"type" => "Like"} = params) do + defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do with %Object{} = object <- Object.normalize(params["object"]), {:ok, activity, _object} <- ActivityPub.like(user, object) do {:ok, activity} @@ -405,7 +428,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def handle_user_activity(_, _) do + defp handle_user_activity(_, _) do {:error, dgettext("errors", "Unhandled activity type")} end @@ -434,7 +457,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do + def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do err = dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}", nickname: nickname, @@ -446,13 +469,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> json(err) end - def errors(conn, {:error, :not_found}) do + defp errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> json(dgettext("errors", "Not found")) end - def errors(conn, _e) do + defp errors(conn, _e) do conn |> put_status(:internal_server_error) |> json(dgettext("errors", "error")) @@ -492,7 +515,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do - HTTP Code: 201 Created - HTTP Body: ActivityPub object to be inserted into another's `attachment` field """ - def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do + def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do with {:ok, object} <- ActivityPub.upload( file, diff --git a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex index 9213ddde7..c80272b8f 100644 --- a/lib/pleroma/web/activity_pub/internal_fetch_actor.ex +++ b/lib/pleroma/web/activity_pub/internal_fetch_actor.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.InternalFetchActor do diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index 263ed11af..a0b3af432 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF do diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex index 8abe18e29..9e7800997 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do @@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do # does the post contain links? defp contains_links?(%{"content" => content} = _object) do content + |> Floki.parse_fragment!() |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl") |> Floki.attribute("a", "href") |> length() > 0 diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index f7831bc3e..5ab9844ff 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do @impl true def filter(object) do - Logger.info("REJECTING #{inspect(object)}") + Logger.debug("REJECTING #{inspect(object)}") {:reject, object} end 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 3a3e72910..2627a0007 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index b3c742954..1764bc789 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index d6d1396bc..88b0d2b39 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 26b8539fe..d9a0acfd3 100644 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do @@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do ] def perform(:prefetch, url) do - Logger.info("Prefetching #{inspect(url)}") + Logger.debug("Prefetching #{inspect(url)}") url |> MediaProxy.url() diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex index ce8bc4580..06f003921 100644 --- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex index 878c57925..cc2ac9d08 100644 --- a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index daa4c88ad..7abae37ae 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index 8b36c1021..4a8bc91ae 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -1,16 +1,15 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do alias Pleroma.Config alias Pleroma.User - alias Pleroma.Web.ActivityPub.MRF require Pleroma.Constants @moduledoc "Filter activities depending on their age" - @behaviour MRF + @behaviour Pleroma.Web.ActivityPub.MRF defp check_date(%{"published" => published} = message) do with %DateTime{} = now <- DateTime.utc_now(), 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 5a809a321..3092f3272 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 8e53296e7..4edc007fd 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -1,12 +1,12 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF @moduledoc "Filter activities depending on their origin instance" - @behaviour MRF + @behaviour Pleroma.Web.ActivityPub.MRF require Pleroma.Constants diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex index 566c1e191..c9f20571f 100644 --- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do require Logger - @behaviour MRF + @behaviour Pleroma.Web.ActivityPub.MRF defp lookup_subchain(actor) do with matches <- Config.get([:mrf_subchain, :match_actor]), diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index c1801d2ec..c310462cb 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex index 7389d6a96..a927a4ed8 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index 4eaea00d8..6167a74e2 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), true <- - length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type), + Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type), false <- length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type), {:ok, _} <- filter(message["object"]) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 539be1143..cff924047 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -17,13 +17,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def validate(object, meta) def validate(%{"type" => "Like"} = object, meta) do - with {_, {:ok, object}} <- - {:validate_object, - object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert)} do + with {:ok, object} <- + object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object |> Map.from_struct()) {:ok, object, meta} - else - e -> {:error, e} end end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 4ea37fc7b..6c558e7f0 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Publisher do @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier @@ -47,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do * `id`: the ActivityStreams URI of the message """ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do - Logger.info("Federating #{id} to #{inbox}") + Logger.debug("Federating #{id} to #{inbox}") %{host: host, path: path} = URI.parse(inbox) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) @@ -188,31 +189,35 @@ defmodule Pleroma.Web.ActivityPub.Publisher do recipients = recipients(actor, activity) - recipients - |> Enum.filter(&User.ap_enabled?/1) - |> Enum.map(fn %{source_data: data} -> data["inbox"] end) - |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) - |> Instances.filter_reachable() - |> Enum.each(fn {inbox, unreachable_since} -> - %User{ap_id: ap_id} = - Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end) - - # Get all the recipients on the same host and add them to cc. Otherwise, a remote - # instance would only accept a first message for the first recipient and ignore the rest. - cc = get_cc_ap_ids(ap_id, recipients) - - json = - data - |> Map.put("cc", cc) - |> Jason.encode!() - - Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ - inbox: inbox, - json: json, - actor_id: actor.id, - id: activity.data["id"], - unreachable_since: unreachable_since - }) + inboxes = + recipients + |> Enum.filter(&User.ap_enabled?/1) + |> Enum.map(fn %{source_data: data} -> data["inbox"] end) + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) + |> Instances.filter_reachable() + + Repo.checkout(fn -> + Enum.each(inboxes, fn {inbox, unreachable_since} -> + %User{ap_id: ap_id} = + Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end) + + # Get all the recipients on the same host and add them to cc. Otherwise, a remote + # instance would only accept a first message for the first recipient and ignore the rest. + cc = get_cc_ap_ids(ap_id, recipients) + + json = + data + |> Map.put("cc", cc) + |> Jason.encode!() + + Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ + inbox: inbox, + json: json, + actor_id: actor.id, + id: activity.data["id"], + unreachable_since: unreachable_since + }) + end) end) end @@ -223,7 +228,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do public = is_public?(activity) if public && Config.get([:instance, :allow_relay]) do - Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) + Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end) Relay.publish(activity) end @@ -259,6 +264,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do "rel" => "self", "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "href" => user.ap_id + }, + %{ + "rel" => "http://ostatus.org/schema/1.0/subscribe", + "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" } ] end diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index 99a804568..729c23af7 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Relay do @@ -9,10 +9,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do alias Pleroma.Web.ActivityPub.ActivityPub require Logger + @relay_nickname "relay" + def get_actor do actor = relay_ap_id() - |> User.get_or_create_service_actor_by_ap_id() + |> User.get_or_create_service_actor_by_ap_id(@relay_nickname) actor end @@ -58,15 +60,28 @@ defmodule Pleroma.Web.ActivityPub.Relay do def publish(_), do: {:error, "Not implemented"} - @spec list() :: {:ok, [String.t()]} | {:error, any()} - def list do + @spec list(boolean()) :: {:ok, [String.t()]} | {:error, any()} + def list(with_not_accepted \\ false) do with %User{} = user <- get_actor() do - list = + accepted = user |> User.following() |> Enum.map(fn entry -> URI.parse(entry).host end) |> Enum.uniq() + list = + if with_not_accepted do + without_accept = + user + |> Pleroma.Activity.following_requests_for_actor() + |> Enum.map(fn a -> URI.parse(a.data["object"]).host <> " (no Accept received)" end) + |> Enum.uniq() + + accepted ++ without_accept + else + accepted + end + {:ok, list} else error -> format_error(error) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6603e4929..dbb14e9aa 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Transmogrifier do @@ -159,10 +159,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do when not is_nil(in_reply_to) do in_reply_to_id = prepare_in_reply_to(in_reply_to) object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) + depth = (options[:depth] || 0) + 1 - if Federator.allowed_incoming_reply_depth?(options[:depth]) do + if Federator.allowed_thread_distance?(depth) do with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options), - %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do + %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do object |> Map.put("inReplyTo", replied_object.data["id"]) |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) @@ -315,7 +316,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) when is_binary(reply_id) do - with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), + with true <- Federator.allowed_thread_distance?(options[:depth]), {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do Map.put(object, "type", "Answer") else @@ -390,7 +391,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming(%{"id" => nil}, _options), do: :error def handle_incoming(%{"id" => ""}, _options), do: :error # length of https:// = 8, should validate better, but good enough for now. - def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8), + def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8, do: :error # TODO: validate those with a Ecto scheme @@ -400,7 +401,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, options ) - when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do + when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer"] do actor = Containment.get_actor(data) data = @@ -409,8 +410,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with nil <- Activity.get_create_by_object_ap_id(object["id"]), {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do - options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) - object = fix_object(data["object"], options) + object = fix_object(object, options) params = %{ to: data["to"], @@ -427,7 +427,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ]) } - ActivityPub.create(params) + with {:ok, created_activity} <- ActivityPub.create(params) do + reply_depth = (options[:depth] || 0) + 1 + + if Federator.allowed_thread_distance?(reply_depth) do + for reply_id <- replies(object) do + Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ + "id" => reply_id, + "depth" => reply_depth + }) + end + end + + {:ok, created_activity} + end else %Activity{} = activity -> {:ok, activity} _e -> :error @@ -445,7 +458,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_addressing with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do - options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) + reply_depth = (options[:depth] || 0) + 1 + options = Keyword.put(options, :depth, reply_depth) object = fix_object(object, options) params = %{ @@ -583,7 +597,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "star" => "⭐" } - @doc "Rewrite misskey likes into EmojiReactions" + @doc "Rewrite misskey likes into EmojiReacts" def handle_incoming( %{ "type" => "Like", @@ -592,7 +606,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do options ) do data - |> Map.put("type", "EmojiReaction") + |> Map.put("type", "EmojiReact") |> Map.put("content", @misskey_reactions[reaction] || reaction) |> handle_incoming(options) end @@ -617,7 +631,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{ - "type" => "EmojiReaction", + "type" => "EmojiReact", "object" => object_id, "actor" => _actor, "id" => id, @@ -665,24 +679,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) - locked = new_user_data[:locked] || false - attachment = get_in(new_user_data, [:source_data, "attachment"]) || [] - invisible = new_user_data[:invisible] || false - - fields = - attachment - |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) - |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) - - update_data = - new_user_data - |> Map.take([:avatar, :banner, :bio, :name, :also_known_as]) - |> Map.put(:fields, fields) - |> Map.put(:locked, locked) - |> Map.put(:invisible, invisible) - actor - |> User.upgrade_changeset(update_data, true) + |> User.upgrade_changeset(new_user_data, true) |> User.update_and_set_cache() ActivityPub.update(%{ @@ -774,7 +772,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{ "type" => "Undo", - "object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id}, + "object" => %{"type" => "EmojiReact", "id" => reaction_activity_id}, "actor" => _actor, "id" => id } = data, @@ -926,6 +924,50 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_reply_to_uri(obj), do: obj + @doc """ + Serialized Mastodon-compatible `replies` collection containing _self-replies_. + Based on Mastodon's ActivityPub::NoteSerializer#replies. + """ + def set_replies(obj_data) do + replies_uris = + with limit when limit > 0 <- + Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0), + %Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do + object + |> Object.self_replies() + |> select([o], fragment("?->>'id'", o.data)) + |> limit(^limit) + |> Repo.all() + else + _ -> [] + end + + set_replies(obj_data, replies_uris) + end + + defp set_replies(obj, []) do + obj + end + + defp set_replies(obj, replies_uris) do + replies_collection = %{ + "type" => "Collection", + "items" => replies_uris + } + + Map.merge(obj, %{"replies" => replies_collection}) + end + + def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do + items + end + + def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do + items + end + + def replies(_), do: [] + # Prepares the object of an outgoing create activity. def prepare_object(object) do object @@ -937,6 +979,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> prepare_attachments |> set_conversation |> set_reply_to_uri + |> set_replies |> strip_internal_fields |> strip_internal_tags |> set_type diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 01aacbde3..15dd2ed45 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Utils do @@ -22,7 +22,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do require Logger require Pleroma.Constants - @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"] + @supported_object_types [ + "Article", + "Note", + "Event", + "Video", + "Page", + "Question", + "Answer", + "Audio" + ] @strip_status_report_states ~w(closed resolved) @supported_report_states ~w(open closed resolved) @valid_visibilities ~w(public unlisted private direct) @@ -36,8 +45,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do Map.put(params, "actor", get_ap_id(params["actor"])) end - @spec determine_explicit_mentions(map()) :: map() - def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do + @spec determine_explicit_mentions(map()) :: [any] + def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do Enum.flat_map(tag, fn %{"type" => "Mention", "href" => href} -> [href] _ -> [] @@ -299,23 +308,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do def make_emoji_reaction_data(user, object, emoji, activity_id) do make_like_data(user, object, activity_id) - |> Map.put("type", "EmojiReaction") + |> Map.put("type", "EmojiReact") |> Map.put("content", emoji) end - @spec update_element_in_object(String.t(), list(any), Object.t()) :: + @spec update_element_in_object(String.t(), list(any), Object.t(), integer() | nil) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} - def update_element_in_object(property, element, object) do + def update_element_in_object(property, element, object, count \\ nil) do length = - if is_map(element) do - element - |> Map.values() - |> List.flatten() - |> length() - else - element - |> length() - end + count || + length(element) data = Map.merge( @@ -335,29 +337,60 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = [actor | emoji_actors] |> Enum.uniq() - new_reactions = Map.put(reactions, emoji, new_emoji_actors) - update_element_in_object("reaction", new_reactions, object) + reactions = get_cached_emoji_reactions(object) + + new_reactions = + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions ++ [[emoji, [actor]]] + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end + ) + end + + count = emoji_count(new_reactions) + + update_element_in_object("reaction", new_reactions, object, count) + end + + def emoji_count(reactions_list) do + Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end) end def remove_emoji_reaction_from_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = List.delete(emoji_actors, actor) + reactions = get_cached_emoji_reactions(object) new_reactions = - if new_emoji_actors == [] do - Map.delete(reactions, emoji) - else - Map.put(reactions, emoji, new_emoji_actors) + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, List.delete(users, actor)] end + ) + |> Enum.reject(fn [_, users] -> Enum.empty?(users) end) end - update_element_in_object("reaction", new_reactions, object) + count = emoji_count(new_reactions) + update_element_in_object("reaction", new_reactions, object, count) + end + + def get_cached_emoji_reactions(object) do + if is_list(object.data["reactions"]) do + object.data["reactions"] + else + [] + end end @spec add_like_to_object(Activity.t(), Object.t()) :: @@ -394,7 +427,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Updates a follow activity's state (for locked accounts). """ - @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()} + @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil} def update_follow_state_for_all( %Activity{data: %{"actor" => actor, "object" => object}} = activity, state @@ -457,10 +490,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Repo.one() end + def fetch_latest_undo(%User{ap_id: ap_id}) do + "Undo" + |> Activity.Queries.by_type() + |> where(actor: ^ap_id) + |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> limit(1) + |> Repo.one() + end + def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id) - "EmojiReaction" + "EmojiReact" |> Activity.Queries.by_type() |> where(actor: ^ap_id) |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) @@ -722,65 +764,33 @@ defmodule Pleroma.Web.ActivityPub.Utils do act when is_binary(act) -> act end - activity = Activity.get_by_ap_id_with_object(id) - actor = User.get_by_ap_id(activity.object.data["actor"]) + case Activity.get_by_ap_id_with_object(id) do + %Activity{} = activity -> + %{ + "type" => "Note", + "id" => activity.data["id"], + "content" => activity.object.data["content"], + "published" => activity.object.data["published"], + "actor" => + AccountView.render("show.json", %{ + user: User.get_by_ap_id(activity.object.data["actor"]) + }) + } - %{ - "type" => "Note", - "id" => activity.data["id"], - "content" => activity.object.data["content"], - "published" => activity.object.data["published"], - "actor" => AccountView.render("show.json", %{user: actor}) - } + _ -> + %{"id" => id, "deleted" => true} + end end defp build_flag_object(_), do: [] - @doc """ - Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after - the first one to `pages_left` pages. - If the amount of pages is higher than the collection has, it returns whatever was there. - """ - def fetch_ordered_collection(from, pages_left, acc \\ []) do - with {:ok, response} <- Tesla.get(from), - {:ok, collection} <- Jason.decode(response.body) do - case collection["type"] do - "OrderedCollection" -> - # If we've encountered the OrderedCollection and not the page, - # just call the same function on the page address - fetch_ordered_collection(collection["first"], pages_left) - - "OrderedCollectionPage" -> - if pages_left > 0 do - # There are still more pages - if Map.has_key?(collection, "next") do - # There are still more pages, go deeper saving what we have into the accumulator - fetch_ordered_collection( - collection["next"], - pages_left - 1, - acc ++ collection["orderedItems"] - ) - else - # No more pages left, just return whatever we already have - acc ++ collection["orderedItems"] - end - else - # Got the amount of pages needed, add them all to the accumulator - acc ++ collection["orderedItems"] - end - - _ -> - {:error, "Not an OrderedCollection or OrderedCollectionPage"} - end - end - end - #### Report-related helpers def get_reports(params, page, page_size) do params = params |> Map.put("type", "Flag") |> Map.put("skip_preload", true) + |> Map.put("preload_report_notes", true) |> Map.put("total", true) |> Map.put("limit", page_size) |> Map.put("offset", (page - 1) * page_size) @@ -788,63 +798,76 @@ defmodule Pleroma.Web.ActivityPub.Utils do ActivityPub.fetch_activities([], params, :offset) end - @spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{ - required(:groups) => [ - %{ - required(:date) => String.t(), - required(:account) => %{}, - required(:status) => %{}, - required(:actors) => [%User{}], - required(:reports) => [%Activity{}] - } - ], - required(:total) => integer - } - def get_reports_grouped_by_status(groups) do - parsed_groups = - groups - |> Enum.map(fn entry -> - activity = - case Jason.decode(entry.activity) do - {:ok, activity} -> activity - _ -> build_flag_object(entry.activity) - end - - parse_report_group(activity) - end) - - %{ - groups: parsed_groups - } - end - def parse_report_group(activity) do reports = get_reports_by_status_id(activity["id"]) max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"])) actors = Enum.map(reports, & &1.user_actor) + [%{data: %{"object" => [account_id | _]}} | _] = reports + + account = + AccountView.render("show.json", %{ + user: User.get_by_ap_id(account_id) + }) + + status = get_status_data(activity) %{ date: max_date.data["published"], - account: activity["actor"], - status: %{ - id: activity["id"], - content: activity["content"], - published: activity["published"] - }, + account: account, + status: status, actors: Enum.uniq(actors), reports: reports } end + defp get_status_data(status) do + case status["deleted"] do + true -> + %{ + "id" => status["id"], + "deleted" => true + } + + _ -> + Activity.get_by_ap_id(status["id"]) + end + end + def get_reports_by_status_id(ap_id) do from(a in Activity, where: fragment("(?)->>'type' = 'Flag'", a.data), - where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]) + where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]), + or_where: fragment("(?)->'object' @> ?", a.data, ^[ap_id]) ) |> Activity.with_preloaded_user_actor() |> Repo.all() end + @spec get_reports_grouped_by_status([String.t()]) :: %{ + required(:groups) => [ + %{ + required(:date) => String.t(), + required(:account) => %{}, + required(:status) => %{}, + required(:actors) => [%User{}], + required(:reports) => [%Activity{}] + } + ] + } + def get_reports_grouped_by_status(activity_ids) do + parsed_groups = + activity_ids + |> Enum.map(fn id -> + id + |> build_flag_object() + |> parse_report_group() + end) + + %{ + groups: parsed_groups + } + end + @spec get_reported_activities() :: [ %{ required(:activity) => String.t(), @@ -852,17 +875,23 @@ defmodule Pleroma.Web.ActivityPub.Utils do } ] def get_reported_activities do - from(a in Activity, - where: fragment("(?)->>'type' = 'Flag'", a.data), + reported_activities_query = + from(a in Activity, + where: fragment("(?)->>'type' = 'Flag'", a.data), + select: %{ + activity: fragment("jsonb_array_elements((? #- '{object,0}')->'object')", a.data) + }, + group_by: fragment("activity") + ) + + from(a in subquery(reported_activities_query), + distinct: true, select: %{ - date: fragment("max(?->>'published') date", a.data), - activity: - fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity", a.data) - }, - group_by: fragment("activity"), - order_by: fragment("date DESC") + id: fragment("COALESCE(?->>'id'::text, ? #>> '{}')", a.activity, a.activity) + } ) |> Repo.all() + |> Enum.map(& &1.id) end def update_report_state(%Activity{} = activity, state) diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index d8a3ec288..e555e9999 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectView do diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index cf08045c9..bc21ac6c7 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UserView do @@ -73,6 +73,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do {:ok, _, public_key} = Keys.keys_from_pem(user.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) + user = User.sanitize_html(user) endpoints = render("endpoints.json", %{user: user}) @@ -81,17 +82,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do fields = user |> User.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => Pleroma.HTML.strip_tags(name), - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) |> Enum.map(&Map.put(&1, "type", "PropertyValue")) %{ "id" => user.ap_id, - "type" => "Person", + "type" => user.actor_type, "following" => "#{user.ap_id}/following", "followers" => "#{user.ap_id}/followers", "inbox" => "#{user.ap_id}/inbox", @@ -201,7 +196,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do %{ "id" => "#{user.ap_id}/followers", "type" => "OrderedCollection", - "totalItems" => total, "first" => if showing_items do collection(followers, "#{user.ap_id}/followers", 1, showing_items, total) @@ -209,6 +203,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "#{user.ap_id}/followers?page=1" end } + |> maybe_put_total_items(showing_count, total) |> Map.merge(Utils.make_json_ld_header()) end @@ -251,6 +246,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Map.merge(Utils.make_json_ld_header()) end + defp maybe_put_total_items(map, false, _total), do: map + + defp maybe_put_total_items(map, true, total) do + Map.put(map, "totalItems", total) + end + def collection(collection, iri, page, show_items \\ true, total \\ nil) do offset = (page - 1) * 10 items = Enum.slice(collection, offset, 10) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index e172f6d3f..6f226fc92 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Visibility do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 24fdc3c82..175260bc2 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -1,19 +1,25 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.ConfigDB alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.ReportNote + alias Pleroma.Stats alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI.AccountView - alias Pleroma.Web.AdminAPI.Config alias Pleroma.Web.AdminAPI.ConfigView alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Report @@ -24,26 +30,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.Router - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - require Logger + @descriptions_json Pleroma.Docs.JSON.compile() + @users_page_size 50 + plug( OAuthScopesPlug, - %{scopes: ["read:accounts"]} - when action in [:list_users, :user_show, :right_get, :invites] + %{scopes: ["read:accounts"], admin: true} + when action in [:list_users, :user_show, :right_get] ) plug( OAuthScopesPlug, - %{scopes: ["write:accounts"]} + %{scopes: ["write:accounts"], admin: true} when action in [ - :get_invite_token, - :revoke_invite, - :email_invite, :get_password_reset, - :user_follow, - :user_unfollow, :user_delete, :users_create, :user_toggle_activation, @@ -56,41 +58,55 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ] ) + plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) + plug( OAuthScopesPlug, - %{scopes: ["read:reports"]} when action in [:list_reports, :report_show] + %{scopes: ["write:invites"], admin: true} + when action in [:create_invite_token, :revoke_invite, :email_invite] ) plug( OAuthScopesPlug, - %{scopes: ["write:reports"]} - when action in [:report_update_state, :report_respond] + %{scopes: ["write:follows"], admin: true} + when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow] ) plug( OAuthScopesPlug, - %{scopes: ["read:statuses"]} when action == :list_user_statuses + %{scopes: ["read:reports"], admin: true} + when action in [:list_reports, :report_show] ) plug( OAuthScopesPlug, - %{scopes: ["write:statuses"]} - when action in [:status_update, :status_delete] + %{scopes: ["write:reports"], admin: true} + when action in [:reports_update] ) plug( OAuthScopesPlug, - %{scopes: ["read"]} - when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log] + %{scopes: ["read:statuses"], admin: true} + when action == :list_user_statuses ) plug( OAuthScopesPlug, - %{scopes: ["write"]} - when action in [:relay_follow, :relay_unfollow, :config_update] + %{scopes: ["write:statuses"], admin: true} + when action in [:status_update, :status_delete] ) - @users_page_size 50 + plug( + OAuthScopesPlug, + %{scopes: ["read"], admin: true} + when action in [:config_show, :list_log, :stats] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action == :config_update + ) action_fallback(:errors) @@ -228,21 +244,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def list_instance_statuses(conn, %{"instance" => instance} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true {page, page_size} = page_params(params) activities = - ActivityPub.fetch_instance_activities(%{ + ActivityPub.fetch_statuses(nil, %{ "instance" => instance, "limit" => page_size, - "offset" => (page - 1) * page_size + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" }) conn - |> put_view(StatusView) + |> put_view(Pleroma.Web.AdminAPI.StatusView) |> render("index.json", %{activities: activities, as: :activity}) end def list_user_statuses(conn, %{"nickname" => nickname} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true godmode = params["godmode"] == "true" || params["godmode"] == true with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do @@ -251,7 +270,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do activities = ActivityPub.fetch_user_activities(user, nil, %{ "limit" => page_size, - "godmode" => godmode + "godmode" => godmode, + "exclude_reblogs" => !with_reblogs && "true" }) conn @@ -556,8 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do @doc "Sends registration invite via email" def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do with true <- - Pleroma.Config.get([:instance, :invites_enabled]) && - !Pleroma.Config.get([:instance, :registrations_open]), + Config.get([:instance, :invites_enabled]) && + !Config.get([:instance, :registrations_open]), {:ok, invite_token} <- UserInviteToken.create_invite(), email <- Pleroma.Emails.UserEmail.user_invitation_email( @@ -627,7 +647,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - Enum.map(users, &User.force_password_reset_async/1) + Enum.each(users, &User.force_password_reset_async/1) ModerationLog.insert_log(%{ actor: admin, @@ -641,17 +661,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def list_reports(conn, params) do {page, page_size} = page_params(params) + reports = Utils.get_reports(params, page, page_size) + conn |> put_view(ReportView) - |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)}) + |> render("index.json", %{reports: reports}) end def list_grouped_reports(conn, _params) do - reports = Utils.get_reported_activities() + statuses = Utils.get_reported_activities() conn |> put_view(ReportView) - |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports)) + |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses)) end def report_show(conn, %{"id" => id}) do @@ -687,35 +709,62 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do - with false <- is_nil(params["status"]), - %Activity{} <- Activity.get_by_id(id) do - params = - params - |> Map.put("in_reply_to_status_id", id) - |> Map.put("visibility", "direct") + def report_notes_create(%{assigns: %{user: user}} = conn, %{ + "id" => report_id, + "content" => content + }) do + with {:ok, _} <- ReportNote.create(user.id, report_id, content) do + ModerationLog.insert_log(%{ + action: "report_note", + actor: user, + subject: Activity.get_by_id(report_id), + text: content + }) - {:ok, activity} = CommonAPI.post(user, params) + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end + def report_notes_delete(%{assigns: %{user: user}} = conn, %{ + "id" => note_id, + "report_id" => report_id + }) do + with {:ok, note} <- ReportNote.destroy(note_id) do ModerationLog.insert_log(%{ - action: "report_response", + action: "report_note_delete", actor: user, - subject: activity, - text: params["status"] + subject: Activity.get_by_id(report_id), + text: note.content }) - conn - |> put_view(StatusView) - |> render("show.json", %{activity: activity}) + json_response(conn, :no_content, "") else - true -> - {:param_cast, nil} - - nil -> - {:error, :not_found} + _ -> json_response(conn, :bad_request, "") end end + def list_statuses(%{assigns: %{user: _admin}} = conn, params) do + godmode = params["godmode"] == "true" || params["godmode"] == true + local_only = params["local_only"] == "true" || params["local_only"] == true + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true + {page, page_size} = page_params(params) + + activities = + ActivityPub.fetch_statuses(nil, %{ + "godmode" => godmode, + "local_only" => local_only, + "limit" => page_size, + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" + }) + + conn + |> put_view(Pleroma.Web.AdminAPI.StatusView) + |> render("index.json", %{activities: activities, as: :activity}) + end + def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) @@ -764,49 +813,135 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> render("index.json", %{log: log}) end - def migrate_to_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) - json(conn, %{}) + def config_descriptions(conn, _params) do + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, @descriptions_json) end - def migrate_from_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"]) - json(conn, %{}) + def config_show(conn, %{"only_db" => true}) do + with :ok <- configurable_from_database(conn) do + configs = Pleroma.Repo.all(ConfigDB) + + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: configs}) + end end def config_show(conn, _params) do - configs = Pleroma.Repo.all(Config) + with :ok <- configurable_from_database(conn) do + configs = ConfigDB.get_all_as_keyword() + + merged = + Config.Holder.default_config() + |> ConfigDB.merge(configs) + |> Enum.map(fn {group, values} -> + Enum.map(values, fn {key, value} -> + db = + if configs[group][key] do + ConfigDB.get_db_keys(configs[group][key], key) + end + + db_value = configs[group][key] + + merged_value = + if !is_nil(db_value) and Keyword.keyword?(db_value) and + ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do + ConfigDB.merge_group(group, key, value, db_value) + else + value + end + + setting = %{ + group: ConfigDB.convert(group), + key: ConfigDB.convert(key), + value: ConfigDB.convert(merged_value) + } + + if db, do: Map.put(setting, :db, db), else: setting + end) + end) + |> List.flatten() - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: configs}) + response = %{configs: merged} + + response = + if Restarter.Pleroma.need_reboot?() do + Map.put(response, :need_reboot, true) + else + response + end + + json(conn, response) + end end def config_update(conn, %{"configs" => configs}) do - updated = - if Pleroma.Config.get([:instance, :dynamic_configuration]) do - updated = - Enum.map(configs, fn - %{"group" => group, "key" => key, "delete" => "true"} = params -> - {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) - config - - %{"group" => group, "key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) - config + with :ok <- configurable_from_database(conn) do + {_errors, results} = + Enum.map(configs, fn + %{"group" => group, "key" => key, "delete" => true} = params -> + ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) + + %{"group" => group, "key" => key, "value" => value} -> + ConfigDB.update_or_create(%{group: group, key: key, value: value}) + end) + |> Enum.split_with(fn result -> elem(result, 0) == :error end) + + {deleted, updated} = + results + |> Enum.map(fn {:ok, config} -> + Map.put(config, :db, ConfigDB.get_db_keys(config)) + end) + |> Enum.split_with(fn config -> + Ecto.get_meta(config, :state) == :deleted + end) + + Config.TransferTask.load_and_update_env(deleted, false) + + need_reboot? = + Restarter.Pleroma.need_reboot?() || + Enum.any?(updated, fn config -> + group = ConfigDB.from_string(config.group) + key = ConfigDB.from_string(config.key) + value = ConfigDB.from_binary(config.value) + Config.TransferTask.pleroma_need_restart?(group, key, value) 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), "false"]) - updated - else - [] - end + response = %{configs: updated} - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: updated}) + response = + if need_reboot? do + Restarter.Pleroma.need_reboot() + Map.put(response, :need_reboot, need_reboot?) + else + response + end + + conn + |> put_view(ConfigView) + |> render("index.json", response) + end + end + + def restart(conn, _params) do + with :ok <- configurable_from_database(conn) do + Restarter.Pleroma.restart(Config.get(:env), 50) + + json(conn, %{}) + end + end + + defp configurable_from_database(conn) do + if Config.get(:configurable_from_database) do + :ok + else + errors( + conn, + {:error, "To use this endpoint you need to enable configuration from database."} + ) + end end def reload_emoji(conn, _params) do @@ -843,6 +978,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json("") end + def stats(conn, _) do + count = Stats.get_status_visibility_count() + + conn + |> json(%{"status_visibility" => count}) + end + def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex deleted file mode 100644 index 1917a5580..000000000 --- a/lib/pleroma/web/admin_api/config.ex +++ /dev/null @@ -1,182 +0,0 @@ -# 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.Config do - use Ecto.Schema - import Ecto.Changeset - import Pleroma.Web.Gettext - alias __MODULE__ - alias Pleroma.Repo - - @type t :: %__MODULE__{} - - schema "config" do - field(:key, :string) - field(:group, :string) - field(:value, :binary) - - timestamps() - end - - @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, :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(params) do - %Config{} - |> changeset(Map.put(params, :value, transform(params[:value]))) - |> Repo.insert() - end - - @spec update(Config.t(), map()) :: {:ok, Config} | {:error, Changeset.t()} - def update(%Config{} = config, %{value: value}) do - config - |> change(value: transform(value)) - |> Repo.update() - end - - @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - 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(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def delete(params) do - with %Config{} = config <- Config.get_by_params(Map.delete(params, :subkeys)) do - if params[:subkeys] do - updated_value = - Keyword.drop( - :erlang.binary_to_term(config.value), - Enum.map(params[:subkeys], &do_transform_string(&1)) - ) - - Config.update(config, %{value: updated_value}) - else - Repo.delete(config) - {:ok, nil} - end - else - 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(binary), do: :erlang.binary_to_term(binary) - - @spec from_binary_with_convert(binary()) :: any() - def from_binary_with_convert(binary) do - from_binary(binary) - |> do_convert() - end - - defp do_convert(entity) when is_list(entity) do - for v <- entity, into: [], do: do_convert(v) - end - - defp do_convert(%Regex{} = entity), do: inspect(entity) - - 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({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} - defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} - - defp do_convert(entity) when is_tuple(entity), - do: %{"tuple" => do_convert(Tuple.to_list(entity))} - - defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), - do: entity - - defp do_convert(entity) when is_atom(entity) do - string = to_string(entity) - - if String.starts_with?(string, "Elixir."), - do: do_convert(string), - else: ":" <> string - end - - defp do_convert("Elixir." <> module_name), do: module_name - - defp do_convert(entity) when is_binary(entity), do: entity - - @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{} = entity), do: entity - - defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do - {dispatch_settings, []} = do_eval(entity) - {:dispatch, [dispatch_settings]} - end - - defp do_transform(%{"tuple" => [":partial_chain", entity]}) do - {partial_chain, []} = do_eval(entity) - {:partial_chain, partial_chain} - end - - defp do_transform(%{"tuple" => entity}) do - Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) - end - - 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(entity) when is_list(entity) do - for v <- entity, into: [], do: do_transform(v) - end - - defp do_transform(entity) when is_binary(entity) do - String.trim(entity) - |> do_transform_string() - end - - defp do_transform(entity), do: entity - - defp do_transform_string("~r/" <> pattern) do - modificator = String.split(pattern, "/") |> List.last() - pattern = String.trim_trailing(pattern, "/" <> modificator) - - case modificator do - "" -> ~r/#{pattern}/ - "i" -> ~r/#{pattern}/i - "u" -> ~r/#{pattern}/u - "s" -> ~r/#{pattern}/s - end - end - - defp do_transform_string(":" <> atom), do: String.to_atom(atom) - - 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 - - defp do_eval(entity) do - cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") - Code.eval_string(cleaned_string, [], requires: [], macros: []) - end -end diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex index 9c3468570..8660d6520 100644 --- a/lib/pleroma/web/admin_api/report.ex +++ b/lib/pleroma/web/admin_api/report.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.Report do diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index ed919833e..29cea1f44 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.Search do @@ -18,7 +18,11 @@ defmodule Pleroma.Web.AdminAPI.Search do @spec user(map()) :: {:ok, [User.t()], pos_integer()} def user(params \\ %{}) do - query = User.Query.build(params) |> order_by([u], u.nickname) + query = + params + |> Map.drop([:page, :page_size]) + |> User.Query.build() + |> order_by([u], u.nickname) paginated_query = User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index d9dba5c51..1e03849de 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -1,11 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.AccountView do use Pleroma.Web, :view - alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.MediaProxy @@ -26,7 +25,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do def render("show.json", %{user: user}) do avatar = User.avatar_url(user) |> MediaProxy.url() - display_name = HTML.strip_tags(user.name || user.nickname) + display_name = Pleroma.HTML.strip_tags(user.name || user.nickname) + user = User.sanitize_html(user, FastSanitize.Sanitizer.StripTags) %{ "id" => user.id, diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index 49add0b6e..587ef760e 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -1,21 +1,33 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.ConfigView do use Pleroma.Web, :view - def render("index.json", %{configs: configs}) do - %{ + def render("index.json", %{configs: configs} = params) do + map = %{ configs: render_many(configs, __MODULE__, "show.json", as: :config) } + + if params[:need_reboot] do + Map.put(map, :need_reboot, true) + else + map + end end def render("show.json", %{config: config}) do - %{ + map = %{ key: config.key, group: config.group, - value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value) + value: Pleroma.ConfigDB.from_binary_with_convert(config.value) } + + if config.db != [] do + Map.put(map, :db, config.db) + else + map + end end end diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex index e7752d1f3..112f9e0e1 100644 --- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex +++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.ModerationLogView do diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca88595c7..fc8733ce8 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -1,9 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.ReportView do use Pleroma.Web, :view + alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.AdminAPI.Report @@ -38,17 +39,24 @@ defmodule Pleroma.Web.AdminAPI.ReportView do content: content, created_at: created_at, statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), - state: report.data["state"] + state: report.data["state"], + notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) } end def render("index_grouped.json", %{groups: groups}) do reports = Enum.map(groups, fn group -> + status = + case group.status do + %Activity{} = activity -> StatusView.render("show.json", %{activity: activity}) + _ -> group.status + end + %{ date: group[:date], account: group[:account], - status: group[:status], + status: Map.put_new(status, "deleted", false), actors: Enum.map(group[:actors], &merge_account_views/1), reports: group[:reports] @@ -62,6 +70,28 @@ defmodule Pleroma.Web.AdminAPI.ReportView do } end + def render("index_notes.json", %{notes: notes}) when is_list(notes) do + Enum.map(notes, &render(__MODULE__, "show_note.json", &1)) + end + + def render("index_notes.json", _), do: [] + + def render("show_note.json", %{ + id: id, + content: content, + user_id: user_id, + inserted_at: inserted_at + }) do + user = User.get_by_id(user_id) + + %{ + id: id, + content: content, + user: merge_account_views(user), + created_at: Utils.to_masto_date(inserted_at) + } + end + defp merge_account_views(%User{} = user) do Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex new file mode 100644 index 000000000..360ddc22c --- /dev/null +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusView do + use Pleroma.Web, :view + + require Pleroma.Constants + + alias Pleroma.User + + def render("index.json", opts) do + safe_render_many(opts.activities, __MODULE__, "show.json", opts) + end + + def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + user = get_user(activity.data["actor"]) + + Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts) + |> Map.merge(%{account: merge_account_views(user)}) + end + + defp merge_account_views(%User{} = user) do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) + |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) + end + + defp merge_account_views(_), do: %{} + + defp get_user(ap_id) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + true -> + User.error_user(ap_id) + end + end +end diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index dd49987f7..b4db312fb 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.Authenticator do diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 177c05636..f63a66c03 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.LDAPAuthenticator do diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index f4234b743..cb09664ce 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.PleromaAuthenticator do diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 8e2759e3b..306ef1916 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.UserSocket do diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index 08841a3e8..38ec774f7 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ChatChannel do @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ChatChannel do def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do text = String.trim(text) - if String.length(text) > 0 do + if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do author = User.get_cached_by_nickname(user_name) author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) message = ChatChannelState.add_message(%{text: text, author: author}) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index f7da81b34..c4356f93b 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI.ActivityDraft do diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index a95fffcfc..f882f9fcb 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI do @@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User + alias Pleroma.UserRelationship alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline @@ -35,7 +36,7 @@ defmodule Pleroma.Web.CommonAPI do def unfollow(follower, unfollowed) do with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed), {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed), - {:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do + {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do {:ok, follower} end end @@ -72,34 +73,41 @@ defmodule Pleroma.Web.CommonAPI do end def delete(activity_id, user) do - with %Activity{data: %{"object" => _}} = activity <- - Activity.get_by_id_with_object(activity_id), + with {_, %Activity{data: %{"object" => _}} = activity} <- + {:find_activity, Activity.get_by_id_with_object(activity_id)}, %Object{} = object <- Object.normalize(activity), true <- User.superuser?(user) || user.ap_id == object.data["actor"], {:ok, _} <- unpin(activity_id, user), {:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} else + {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not delete")} end end def repeat(id_or_ap_id, user, params \\ %{}) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)}, object <- Object.normalize(activity), - nil <- Utils.get_existing_announce(user.ap_id, object), + announce_activity <- Utils.get_existing_announce(user.ap_id, object), public <- public_announce?(object, params) do - ActivityPub.announce(user, object, nil, true, public) + if announce_activity do + {:ok, announce_activity, object} + else + ActivityPub.announce(user, object, nil, true, public) + end else + {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not repeat")} end end def unrepeat(id_or_ap_id, user) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do + with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do object = Object.normalize(activity) ActivityPub.unannounce(user, object) else + {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not unrepeat")} end end @@ -113,17 +121,39 @@ defmodule Pleroma.Web.CommonAPI do Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do {:ok, activity} else + {:find_object, _} -> + {:error, :not_found} + + {:common_pipeline, + { + :error, + { + :validate_object, + { + :error, + changeset + } + } + }} = e -> + if {:object, {"already liked by this actor", []}} in changeset.errors do + {:ok, :already_liked} + else + Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") + {:error, dgettext("errors", "Could not favorite"), e} + end + e -> Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") - {:error, dgettext("errors", "Could not favorite")} + {:error, dgettext("errors", "Could not favorite"), e} end end def unfavorite(id_or_ap_id, user) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do + with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do object = Object.normalize(activity) ActivityPub.unlike(user, object) else + {:find_activity, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not unfavorite")} end end @@ -314,8 +344,9 @@ defmodule Pleroma.Web.CommonAPI do with %Activity{ actor: ^user_ap_id, data: %{"type" => "Create"}, - object: %Object{data: %{"type" => "Note"}} + object: %Object{data: %{"type" => object_type}} } = activity <- get_by_id_or_ap_id(id_or_ap_id), + true <- object_type in ["Note", "Article", "Question"], true <- Visibility.is_public?(activity), {:ok, _user} <- User.add_pinnned_activity(user, activity) do {:ok, activity} @@ -428,15 +459,11 @@ defmodule Pleroma.Web.CommonAPI do defp set_visibility(activity, _), do: {:ok, activity} - def hide_reblogs(user, %{ap_id: ap_id} = _muted) do - if ap_id not in user.muted_reblogs do - User.add_reblog_mute(user, ap_id) - end + def hide_reblogs(%User{} = user, %User{} = target) do + UserRelationship.create_reblog_mute(user, target) end - def show_reblogs(user, %{ap_id: ap_id} = _muted) do - if ap_id in user.muted_reblogs do - User.remove_reblog_mute(user, ap_id) - end + def show_reblogs(%User{} = user, %User{} = target) do + UserRelationship.delete_reblog_mute(user, target) end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index cbb64f8d2..635e7cd38 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI.Utils do @@ -179,9 +179,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do end) end_time = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(expires_in) - |> NaiveDateTime.to_iso8601() + DateTime.utc_now() + |> DateTime.add(expires_in) + |> DateTime.to_iso8601() key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf" poll = %{"type" => "Question", key => option_notes, "closed" => end_time} @@ -228,9 +228,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do data, visibility ) do - no_attachment_links = + attachment_links = data - |> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links])) + |> Map.get("attachment_links", Config.get([:instance, :attachment_links])) |> truthy_param?() content_type = get_content_type(data["content_type"]) @@ -244,7 +244,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do status |> format_input(content_type, options) - |> maybe_add_attachments(attachments, no_attachment_links) + |> maybe_add_attachments(attachments, attachment_links) |> maybe_add_nsfw_tag(data) end @@ -270,7 +270,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_context(%Activity{data: %{"context" => context}}, _), do: context def make_context(_, _), do: Utils.generate_context_id() - def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed + def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do text = add_attachments(text, attachments) @@ -331,7 +331,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def format_input(text, "text/markdown", options) do text |> Formatter.mentions_escape(options) - |> Earmark.as_html!() + |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer}) |> Formatter.linkify(options) |> Formatter.html_escape("text/html") end @@ -494,7 +494,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do with %User{} = user <- User.get_cached_by_ap_id(actor) do subscriber_ids = user - |> User.subscribers() + |> User.subscriber_users() |> Enum.filter(&Visibility.visible_for_user?(activity, &1)) |> Enum.map(& &1.ap_id) @@ -591,7 +591,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do limit = Pleroma.Config.get([:instance, :limit]) length = String.length(full_payload) - if length < limit do + if length <= limit do :ok else {:error, dgettext("errors", "The status is over the character limit")} diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 9a4e322c9..c9a3a2585 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ControllerHelper do @@ -76,8 +76,7 @@ defmodule Pleroma.Web.ControllerHelper do end end - def try_render(conn, target, params) - when is_binary(target) do + def try_render(conn, target, params) when is_binary(target) do case render(conn, target, params) do nil -> render_error(conn, :not_implemented, "Can't display this activity") res -> res @@ -87,4 +86,8 @@ defmodule Pleroma.Web.ControllerHelper do def try_render(conn, _, _) do render_error(conn, :not_implemented, "Can't display this activity") end + + @spec put_in_if_exist(map(), atom() | String.t(), any) :: map() + def put_in_if_exist(map, _key, nil), do: map + def put_in_if_exist(map, key, value), do: put_in(map, key, value) end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 49735b5c2..72cb3ee27 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Endpoint do @@ -12,7 +12,7 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.HTTPSecurityPlug) plug(Pleroma.Plugs.UploadedMedia) - @static_cache_control "public max-age=86400 must-revalidate" + @static_cache_control "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -59,11 +59,14 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.TrailingFormatPlug) plug(Plug.RequestId) - plug(Plug.Logger) - - plug( - Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], + plug(Plug.Logger, log: :debug) + + plug(Plug.Parsers, + parsers: [ + :urlencoded, + {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}}, + :json + ], pass: ["*/*"], json_decoder: Jason, length: Pleroma.Config.get([:instance, :upload_limit]), diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex index 5fbf3695f..c13518030 100644 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ b/lib/pleroma/web/fallback_redirect_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Fallback.RedirectController do diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index e8a56ebd7..fd904ef0a 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Federator do @@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do require Logger - @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" + @doc """ + Returns `true` if the distance to target object does not exceed max configured value. + Serves to prevent fetching of very long threads, especially useful on smaller instances. + Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161). + Applies to fetching of both ancestor (reply-to) and child (reply) objects. + """ # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength - def allowed_incoming_reply_depth?(depth) do - max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) + def allowed_thread_distance?(distance) do + max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) - if max_replies_depth do - (depth || 1) <= max_replies_depth + if max_distance && max_distance >= 0 do + # Default depth is 0 (an object has zero distance from itself in its thread) + (distance || 0) <= max_distance else true end @@ -58,7 +64,7 @@ defmodule Pleroma.Web.Federator do end def perform(:incoming_ap_doc, params) do - Logger.info("Handling incoming AP activity") + Logger.debug("Handling incoming AP activity") params = Utils.normalize_params(params) @@ -71,13 +77,13 @@ defmodule Pleroma.Web.Federator do {:ok, activity} else %Activity{} -> - Logger.info("Already had #{params["id"]}") + Logger.debug("Already had #{params["id"]}") :error _e -> # Just drop those for now - Logger.info("Unhandled activity") - Logger.info(Jason.encode!(params, pretty: true)) + Logger.debug("Unhandled activity") + Logger.debug(Jason.encode!(params, pretty: true)) :error end end diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index fb9b26649..ad0201361 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Federator.Publisher do @@ -47,7 +47,7 @@ defmodule Pleroma.Web.Federator.Publisher do Config.get([:instance, :federation_publisher_modules]) |> Enum.each(fn module -> if module.is_representable?(activity) do - Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}") + Logger.debug("Publishing #{activity.data["id"]} using #{inspect(module)}") module.publish(user, activity) end end) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index bb1332fd3..e18adaea8 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Feed.FeedView do @@ -13,21 +13,53 @@ defmodule Pleroma.Web.Feed.FeedView do require Pleroma.Constants - def prepare_activity(activity) do + @spec pub_date(String.t() | DateTime.t()) :: String.t() + def pub_date(date) when is_binary(date) do + date + |> Timex.parse!("{ISO:Extended}") + |> pub_date + end + + def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}") + + def prepare_activity(activity, opts \\ []) do object = activity_object(activity) + actor = + if opts[:actor] do + Pleroma.User.get_cached_by_ap_id(activity.actor) + end + %{ activity: activity, data: Map.get(object, :data), - object: object + object: object, + actor: actor } end + def most_recent_update(activities) do + with %{updated_at: updated_at} <- List.first(activities) do + NaiveDateTime.to_iso8601(updated_at) + end + end + def most_recent_update(activities, user) do (List.first(activities) || user).updated_at |> NaiveDateTime.to_iso8601() end + def feed_logo do + case Pleroma.Config.get([:feed, :logo]) do + nil -> + "#{Pleroma.Web.base_url()}/static/logo.png" + + logo -> + "#{Pleroma.Web.base_url()}#{logo}" + end + |> MediaProxy.url() + end + def logo(user) do user |> User.avatar_url() @@ -40,6 +72,8 @@ defmodule Pleroma.Web.Feed.FeedView do def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do content + |> Pleroma.Web.Metadata.Utils.scrub_html() + |> Pleroma.Emoji.Formatter.demojify() |> Formatter.truncate(opts[:max_length], opts[:omission]) |> escape() end @@ -50,6 +84,8 @@ defmodule Pleroma.Web.Feed.FeedView do |> escape() end + def activity_content(_), do: "" + def activity_context(activity), do: activity.data["context"] def attachment_href(attachment) do diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex new file mode 100644 index 000000000..75c9ea17e --- /dev/null +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.TagController do + use Pleroma.Web, :controller + + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Feed.FeedView + + import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] + + def feed(conn, %{"tag" => raw_tag} = params) do + {format, tag} = parse_tag(raw_tag) + + activities = + %{"type" => ["Create"], "tag" => tag} + |> put_in_if_exist("max_id", params["max_id"]) + |> ActivityPub.fetch_public_activities() + + conn + |> put_resp_content_type("application/atom+xml") + |> put_view(FeedView) + |> render("tag.#{format}", + activities: activities, + tag: tag, + feed_config: Config.get([:feed]) + ) + end + + @spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()} + defp parse_tag(raw_tag) when is_binary(raw_tag) do + case Enum.reverse(String.split(raw_tag, ".")) do + [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")} + _ -> {"rss", raw_tag} + end + end + + defp parse_tag(raw_tag), do: {"rss", raw_tag} +end diff --git a/lib/pleroma/web/feed/feed_controller.ex b/lib/pleroma/web/feed/user_controller.ex index d0e23007d..9ba602d9f 100644 --- a/lib/pleroma/web/feed/feed_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -1,14 +1,17 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.Feed.FeedController do +defmodule Pleroma.Web.Feed.UserController do use Pleroma.Web, :controller alias Fallback.RedirectController alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPubController + alias Pleroma.Web.Feed.FeedView + + import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) @@ -22,12 +25,17 @@ defmodule Pleroma.Web.Feed.FeedController do def feed_redirect(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do - ActivityPubController.call(conn, :user) + with %{halted: false} = conn <- + Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) do + ActivityPubController.call(conn, :user) + end end def feed_redirect(conn, %{"nickname" => nickname}) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom") + redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom") end end @@ -36,15 +44,15 @@ defmodule Pleroma.Web.Feed.FeedController do activities = %{ "type" => ["Create"], - "whole_db" => true, "actor_id" => user.ap_id } - |> Map.merge(Map.take(params, ["max_id"])) + |> put_in_if_exist("max_id", params["max_id"]) |> ActivityPub.fetch_public_activities() conn |> put_resp_content_type("application/atom+xml") - |> render("feed.xml", + |> put_view(FeedView) + |> render("user.xml", user: user, activities: activities, feed_config: Pleroma.Config.get([:feed]) diff --git a/lib/pleroma/web/gettext.ex b/lib/pleroma/web/gettext.ex index 1328b46cc..0adf428ec 100644 --- a/lib/pleroma/web/gettext.ex +++ b/lib/pleroma/web/gettext.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Gettext do diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index ca261ad6e..43649ad26 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastoFEController do @@ -20,18 +20,21 @@ defmodule Pleroma.Web.MastoFEController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index) @doc "GET /web/*path" - def index(%{assigns: %{user: user}} = conn, _params) do - token = get_session(conn, :oauth_token) + def index(%{assigns: %{user: user, token: token}} = conn, _params) + when not is_nil(user) and not is_nil(token) do + conn + |> put_layout(false) + |> render("index.html", + token: token.token, + user: user, + custom_emojis: Pleroma.Emoji.get_all() + ) + end - if user && token do - conn - |> put_layout(false) - |> render("index.html", token: token, user: user, custom_emojis: Pleroma.Emoji.get_all()) - else - conn - |> put_session(:return_to, conn.request_path) - |> redirect(to: "/web/login") - end + def index(conn, _params) do + conn + |> put_session(:return_to, conn.request_path) + |> redirect(to: "/web/login") end @doc "GET /web/manifest.json" diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index a69423f60..88c997b9f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountController do @@ -76,7 +76,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts" def create( %{assigns: %{app: app}} = conn, - %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params + %{"username" => nickname, "password" => _, "agreement" => true} = params ) do params = params @@ -93,7 +93,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do |> Map.put("bio", params["bio"] || "") |> Map.put("confirm", params["password"]) - with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), + with :ok <- validate_email_param(params), + {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do json(conn, %{ token_type: "Bearer", @@ -114,6 +115,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do render_error(conn, :forbidden, "Invalid credentials") end + defp validate_email_param(%{"email" => _}), do: :ok + + defp validate_email_param(_) do + case Pleroma.Config.get([:instance, :account_activation_required]) do + true -> {:error, %{"error" => "Missing parameters"}} + _ -> :ok + end + end + @doc "GET /api/v1/accounts/verify_credentials" def verify_credentials(%{assigns: %{user: user}} = conn, _) do chat_token = Phoenix.Token.sign(conn, "user socket", user.id) @@ -188,6 +198,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, Map.merge(user.pleroma_settings_store, value)} end) |> add_if_present(params, "default_scope", :default_scope) + |> add_if_present(params, "actor_type", :actor_type) emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") @@ -249,7 +260,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/accounts/:id/statuses" def statuses(%{assigns: %{user: reading_user}} = conn, params) do with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do - params = Map.put(params, "tag", params["tagged"]) + params = + params + |> Map.put("tag", params["tagged"]) + |> Map.delete("godmode") + activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn @@ -324,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do notifications? = params |> Map.get("notifications", true) |> truthy_param?() - with {:ok, muter} <- User.mute(muter, muted, notifications?) do + with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -333,7 +348,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/unmute" def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do - with {:ok, muter} <- User.unmute(muter, muted) do + with {:ok, _user_relationships} <- User.unmute(muter, muted) do render(conn, "relationship.json", user: muter, target: muted) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -342,7 +357,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/block" def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do - with {:ok, blocker} <- User.block(blocker, blocked), + with {:ok, _user_block} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else @@ -352,7 +367,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts/:id/unblock" def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do - with {:ok, blocker} <- User.unblock(blocker, blocked), + with {:ok, _user_block} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else @@ -374,12 +389,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "GET /api/v1/mutes" def mutes(%{assigns: %{user: user}} = conn, _) do - render(conn, "index.json", users: User.muted_users(user), for: user, as: :user) + users = User.muted_users(user, _restrict_deactivated = true) + render(conn, "index.json", users: users, for: user, as: :user) end @doc "GET /api/v1/blocks" def blocks(%{assigns: %{user: user}} = conn, _) do - render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user) + users = User.blocked_users(user, _restrict_deactivated = true) + render(conn, "index.json", users: users, for: user, as: :user) end @doc "GET /api/v1/endorsements" diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 13a30a34d..5e2871f18 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AppController do diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index d9e51de7f..37b389382 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AuthController do @@ -86,6 +86,6 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} defp get_or_make_app do %{client_name: @local_mastodon_name, redirect_uris: "."} - |> App.get_or_make(["read", "write", "follow", "push"]) + |> App.get_or_make(["read", "write", "follow", "push", "admin"]) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index 6c0584c54..7c9b11bf1 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ConversationController do diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index 391c0648b..d82de1db5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 456fe7ab2..e4156cbe6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.DomainBlockController do diff --git a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex index 41243d5e7..0a257f604 100644 --- a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.FallbackController do diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index cadef72e1..7b0b937a2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.FilterController do diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 3ccbdf1c6..1ca86f63f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.FollowRequestController do diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index a55f60fec..27b5b1a52 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.InstanceController do diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index e0ffdba21..dac4daa7b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ListController do diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index ce025624d..58e8a30c2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MarkerController do diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 7d839a8cf..14075307d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index ed4c08d99..2b6f00952 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MediaController do diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 16759be6a..0c9218454 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.NotificationController do @@ -23,6 +23,23 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) # GET /api/v1/notifications + def index(conn, %{"account_id" => account_id} = params) do + case Pleroma.User.get_cached_by_id(account_id) do + %{ap_id: account_ap_id} -> + params = + params + |> Map.delete("account_id") + |> Map.put("account_ap_id", account_ap_id) + + index(conn, params) + + _ -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Account is not found"}) + end + end + def index(%{assigns: %{user: user}} = conn, params) do notifications = MastodonAPI.get_notifications(user, params) diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index d129f8672..d9f894118 100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.PollController do diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 263c2180f..f5782be13 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ReportController do diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index ff9276541..e1e6bd89b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 0a929f55b..fcab4ef63 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SearchController do @@ -43,7 +43,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do result = default_values |> Enum.map(fn {resource, default_value} -> - if params["type"] == nil or params["type"] == resource do + if params["type"] in [nil, resource] do {resource, fn -> resource_search(version, resource, query, options) end} else {resource, fn -> default_value end} diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 160e039af..83cd2c768 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.StatusController do @@ -124,15 +124,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do ) do params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) - if ScheduledActivity.far_enough?(scheduled_at) do - with {:ok, scheduled_activity} <- - ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do - conn - |> put_view(ScheduledActivityView) - |> render("show.json", scheduled_activity: scheduled_activity) - end + with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)}, + attrs <- %{"params" => params, "scheduled_at" => scheduled_at}, + {:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do + conn + |> put_view(ScheduledActivityView) + |> render("show.json", scheduled_activity: scheduled_activity) else - create(conn, Map.drop(params, ["scheduled_at"])) + {:far_enough, _} -> + create(conn, Map.drop(params, ["scheduled_at"])) + + error -> + error end end @@ -172,6 +175,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do for: user, with_direct_conversation_id: true ) + else + _ -> {:error, :not_found} end end @@ -180,6 +185,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do json(conn, %{}) else + {:error, :not_found} = e -> e _e -> render_error(conn, :forbidden, "Can't delete this post") end end @@ -346,15 +352,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/favourites" def favourites(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("favorited_by", user.ap_id) - |> Map.put("blocking_user", user) - activities = - ActivityPub.fetch_activities([], params) - |> Enum.reverse() + ActivityPub.fetch_favourites( + user, + Map.take(params, Pleroma.Pagination.page_keys()) + ) conn |> add_link_headers(activities) diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index fc7d52824..11df6fc4a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -1,14 +1,14 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SubscriptionController do @moduledoc "The module represents functions to manage user subscriptions." use Pleroma.Web, :controller + alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View alias Pleroma.Web.Push alias Pleroma.Web.Push.Subscription - alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View action_fallback(:errors) diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index fe71c36af..0cdc7bd8d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SuggestionController do @@ -7,62 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do require Logger - alias Pleroma.Config - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.User - alias Pleroma.Web.MediaProxy - - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - - plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index) - - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/suggestions" - def index(%{assigns: %{user: user}} = conn, _) do - if Config.get([:suggestions, :enabled], false) do - with {:ok, data} <- fetch_suggestions(user) do - limit = Config.get([:suggestions, :limit], 23) - - data = - data - |> Enum.slice(0, limit) - |> Enum.map(fn x -> - x - |> Map.put("id", fetch_suggestion_id(x)) - |> Map.put("avatar", MediaProxy.url(x["avatar"])) - |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"])) - end) - - json(conn, data) - end - else - json(conn, []) - end - end - - defp fetch_suggestions(user) do - api = Config.get([:suggestions, :third_party_engine], "") - timeout = Config.get([:suggestions, :timeout], 5000) - host = Config.get([Pleroma.Web.Endpoint, :url, :host]) - - url = - api - |> String.replace("{{host}}", host) - |> String.replace("{{user}}", user.nickname) - - with {:ok, %{status: 200, body: body}} <- - Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do - Jason.decode(body) - else - e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") - end - end - - defp fetch_suggestion_id(attrs) do - case User.get_or_fetch(attrs["acct"]) do - {:ok, %User{id: id}} -> id - _ -> 0 - end + def index(conn, _) do + json(conn, []) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 384159336..09e08271b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.TimelineController do @@ -10,9 +10,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.Pagination alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + # TODO: Replace with a macro when there is a Phoenix release with + # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e + # in it + + plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) + plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) + plug(RateLimiter, [name: :timeline, bucket_name: :home_timeline] when action == :home) + plug(RateLimiter, [name: :timeline, bucket_name: :hashtag_timeline] when action == :hashtag) + plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) @@ -77,10 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> render("index.json", activities: activities, for: user, as: :activity) end - # GET /api/v1/timelines/tag/:tag - def hashtag(%{assigns: %{user: user}} = conn, params) do - local_only = truthy_param?(params["local"]) - + def hashtag_fetching(params, user, local_only) do tags = [params["tag"], params["any"]] |> List.flatten() @@ -98,7 +106,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.get("none", []) |> Enum.map(&String.downcase(&1)) - activities = + _activities = params |> Map.put("type", "Create") |> Map.put("local_only", local_only) @@ -109,6 +117,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.put("tag_all", tag_all) |> Map.put("tag_reject", tag_reject) |> ActivityPub.fetch_public_activities() + end + + # GET /api/v1/timelines/tag/:tag + def hashtag(%{assigns: %{user: user}} = conn, params) do + local_only = truthy_param?(params["local"]) + + activities = hashtag_fetching(params, user, local_only) conn |> add_link_headers(activities, %{"local" => local_only}) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index d875a5788..3fe2be521 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MastodonAPI do @@ -24,19 +24,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do with {:ok, follower, _followed, _} <- result do options = cast_params(params) - - case reblogs_visibility(options[:reblogs], result) do - {:ok, follower} -> {:ok, follower} - _ -> {:ok, follower} - end + set_reblogs_visibility(options[:reblogs], result) + {:ok, follower} end end - defp reblogs_visibility(false, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do CommonAPI.hide_reblogs(follower, followed) end - defp reblogs_visibility(_, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do CommonAPI.show_reblogs(follower, followed) end @@ -59,6 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do user |> Notification.for_user_query(options) |> restrict(:exclude_types, options) + |> restrict(:account_ap_id, options) |> Pagination.fetch_paginated(params) end @@ -73,7 +71,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do exclude_types: {:array, :string}, exclude_visibilities: {:array, :string}, reblogs: :boolean, - with_muted: :boolean + with_muted: :boolean, + with_move: :boolean, + account_ap_id: :string } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) @@ -90,5 +90,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) end + defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do + where(query, [n, a], a.actor == ^account_ap_id) + end + defp restrict(query, _, _), do: query end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index ec720e472..341dc2c91 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -1,11 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountView do use Pleroma.Web, :view - alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView @@ -50,8 +49,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do id: to_string(target.id), following: User.following?(user, target), followed_by: User.following?(target, user), - blocking: User.blocks_ap_id?(user, target), - blocked_by: User.blocks_ap_id?(target, user), + blocking: User.blocks_user?(user, target), + blocked_by: User.blocks_user?(target, user), muting: User.mutes?(user, target), muting_notifications: User.muted_notifications?(user, target), subscribing: User.subscribed_to?(user, target), @@ -67,7 +66,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do end defp do_render("show.json", %{user: user} = opts) do - display_name = HTML.strip_tags(user.name || user.nickname) + user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) + display_name = user.name || user.nickname image = User.avatar_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url() @@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do 0 end - bot = (user.source_data["type"] || "Person") in ["Application", "Service"] + bot = user.actor_type in ["Application", "Service"] emojis = (user.source_data["tag"] || []) @@ -100,17 +100,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do } end) - fields = - user - |> User.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => Pleroma.HTML.strip_tags(name), - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) - - bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) relationship = render("relationship.json", %{user: opts[:for], target: user}) %{ @@ -123,21 +112,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do followers_count: followers_count, following_count: following_count, statuses_count: user.note_count, - note: bio || "", + note: user.bio || "", url: User.profile_url(user), avatar: image, avatar_static: image, header: header, header_static: header, emojis: emojis, - fields: fields, + fields: user.fields, bot: bot, source: %{ - note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), + note: Pleroma.HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), sensitive: false, fields: user.raw_fields, pleroma: %{ - discoverable: user.discoverable + discoverable: user.discoverable, + actor_type: user.actor_type } }, diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex index f52b693a6..d934e2107 100644 --- a/lib/pleroma/web/mastodon_api/views/app_view.ex +++ b/lib/pleroma/web/mastodon_api/views/app_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AppView do @@ -7,10 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.AppView do alias Pleroma.Web.OAuth.App - @vapid_key :web_push_encryption - |> Application.get_env(:vapid_details, []) - |> Keyword.get(:public_key) - def render("show.json", %{app: %App{} = app}) do %{ id: app.id |> to_string, @@ -32,8 +28,10 @@ defmodule Pleroma.Web.MastodonAPI.AppView do end defp with_vapid_key(data) do - if @vapid_key do - Map.put(data, "vapid_key", @vapid_key) + vapid_key = Application.get_env(:web_push_encryption, :vapid_details, [])[:public_key] + + if vapid_key do + Map.put(data, "vapid_key", vapid_key) else data end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 2220fbcb1..2b6f84c72 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ConversationView do diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex index cb8688941..47a242b8e 100644 --- a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex +++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do diff --git a/lib/pleroma/web/mastodon_api/views/filter_view.ex b/lib/pleroma/web/mastodon_api/views/filter_view.ex index a685bc7b6..97fd1e83f 100644 --- a/lib/pleroma/web/mastodon_api/views/filter_view.ex +++ b/lib/pleroma/web/mastodon_api/views/filter_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.FilterView do diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index c4866e510..67214dbea 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.InstanceView do diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex index bfda6f5b3..580596b64 100644 --- a/lib/pleroma/web/mastodon_api/views/list_view.ex +++ b/lib/pleroma/web/mastodon_api/views/list_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ListView do diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 38fbeed5f..985368fe5 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MarkerView do diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ddd7f5318..33145c484 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.NotificationView do @@ -37,18 +37,37 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do } case mastodon_type do - "mention" -> put_status(response, activity, user) - "favourite" -> put_status(response, parent_activity, user) - "reblog" -> put_status(response, parent_activity, user) - "move" -> put_target(response, activity, user) - "follow" -> response - _ -> nil + "mention" -> + put_status(response, activity, user) + + "favourite" -> + put_status(response, parent_activity, user) + + "reblog" -> + put_status(response, parent_activity, user) + + "move" -> + put_target(response, activity, user) + + "follow" -> + response + + "pleroma:emoji_reaction" -> + put_status(response, parent_activity, user) |> put_emoji(activity) + + _ -> + nil end else _ -> nil end end + defp put_emoji(response, activity) do + response + |> Map.put(:emoji, activity.data["content"]) + end + defp put_status(response, activity, user) do Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user})) end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 753039da3..40edbb213 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -1,11 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.PollView do use Pleroma.Web, :view - alias Pleroma.HTML alias Pleroma.Web.CommonAPI.Utils def render("show.json", %{object: object, multiple: multiple, options: options} = params) do @@ -57,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do current_count = option["replies"]["totalItems"] || 0 {%{ - title: HTML.strip_tags(name), + title: name, votes_count: current_count }, current_count + count} end) diff --git a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex index 021489711..d32cef6e2 100644 --- a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex +++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do diff --git a/lib/pleroma/web/mastodon_api/views/report_view.ex b/lib/pleroma/web/mastodon_api/views/report_view.ex index 9da2dd740..98cb581ef 100644 --- a/lib/pleroma/web/mastodon_api/views/report_view.ex +++ b/lib/pleroma/web/mastodon_api/views/report_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ReportView do diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex index fc042a276..458f6bc78 100644 --- a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex +++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a0257dfa6..f7469cdff 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.StatusView do @@ -175,9 +175,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do expires_at = with true <- client_posted_this_activity, - expiration when not is_nil(expiration) <- + %ActivityExpiration{scheduled_at: scheduled_at} <- ActivityExpiration.get_by_activity_id(activity.id) do - expiration.scheduled_at + scheduled_at + else + _ -> nil end thread_muted? = @@ -216,21 +218,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do summary = object.data["summary"] || "" - summary_html = - summary - |> HTML.get_cached_scrubbed_html_for_activity( - User.html_filter_policy(opts[:for]), - activity, - "mastoapi:summary" - ) - - summary_plaintext = - summary - |> HTML.get_cached_stripped_html_for_activity( - activity, - "mastoapi:summary" - ) - card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) url = @@ -253,6 +240,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end + emoji_reactions = + with %{data: %{"reactions" => emoji_reactions}} <- object do + Enum.map(emoji_reactions, fn [emoji, users] -> + %{ + name: emoji, + count: length(users), + me: !!(opts[:for] && opts[:for].ap_id in users) + } + end) + else + _ -> [] + end + %{ id: to_string(activity.id), uri: object.data["id"], @@ -273,7 +273,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do muted: thread_muted? || User.mutes?(opts[:for], user), pinned: pinned?(activity, user), sensitive: sensitive, - spoiler_text: summary_html, + spoiler_text: summary, visibility: get_visibility(object), media_attachments: attachments, poll: render(PollView, "show.json", object: object, for: opts[:for]), @@ -290,10 +290,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do conversation_id: get_context_id(activity), in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, - spoiler_text: %{"text/plain" => summary_plaintext}, + spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, direct_conversation_id: direct_conversation_id, - thread_muted: thread_muted? + thread_muted: thread_muted?, + emoji_reactions: emoji_reactions } } end @@ -322,11 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end - site_name = rich_media[:site_name] || page_url_data.host - %{ type: "link", - provider_name: site_name, + provider_name: page_url_data.host, provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, image: image_url |> MediaProxy.url(), @@ -421,7 +420,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - def render_content(%{data: %{"type" => "Video"}} = object) do + def render_content(%{data: %{"type" => object_type}} = object) + when object_type in ["Video", "Event"] do with name when not is_nil(name) and name != "" <- object.data["name"] do "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}" else diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index a400d1c8d..5652a37c1 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 1725ab071..b2b524524 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxy do diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 8403850ff..1a09ac62a 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxy.MediaProxyController do diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 8761260f2..c9aac27dc 100644 --- a/lib/pleroma/web/metadata.ex +++ b/lib/pleroma/web/metadata.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata do diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex index 8043e6c54..bd1459a17 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/feed.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.Feed do @@ -16,7 +16,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do [ rel: "alternate", type: "application/atom+xml", - href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom" + href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" ], []} ] end diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex index e7fa7f408..21446ac77 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/opengraph.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.OpenGraph do diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex index 4289ebdbd..5a918532a 100644 --- a/lib/pleroma/web/metadata/player_view.ex +++ b/lib/pleroma/web/metadata/player_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.PlayerView do diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/provider.ex index 197fb2a77..767288f9c 100644 --- a/lib/pleroma/web/metadata/provider.ex +++ b/lib/pleroma/web/metadata/provider.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.Provider do diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex index f87fc1973..8905c9c72 100644 --- a/lib/pleroma/web/metadata/rel_me.ex +++ b/lib/pleroma/web/metadata/rel_me.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.RelMe do @@ -8,8 +8,10 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do @impl Provider def build_tags(%{user: user}) do - (Floki.attribute(user.bio, "link[rel~=me]", "href") ++ - Floki.attribute(user.bio, "a[rel~=me]", "href")) + bio_tree = Floki.parse_fragment!(user.bio) + + (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ + Floki.attribute(bio_tree, "a[rel~=me]", "href")) |> Enum.map(fn link -> {:link, [rel: "me", href: link], []} end) diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex index d6a6049b3..5d08ce422 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/twitter_card.ex @@ -1,6 +1,6 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.TwitterCard do @@ -31,7 +31,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do if attachments == [] or Metadata.activity_nsfw?(object) do [ image_tag(user), - {:meta, [property: "twitter:card", content: "summary_large_image"], []} + {:meta, [property: "twitter:card", content: "summary"], []} ] else attachments diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 382ecf426..2f0dfb474 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Utils do @@ -15,19 +15,28 @@ defmodule Pleroma.Web.Metadata.Utils do |> String.replace(~r/<br\s?\/?>/, " ") |> HTML.get_cached_stripped_html_for_activity(object, "metadata") |> Emoji.Formatter.demojify() + |> HtmlEntities.decode() |> Formatter.truncate() end def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do content + |> scrub_html + |> Emoji.Formatter.demojify() + |> HtmlEntities.decode() + |> Formatter.truncate(max_length) + end + + def scrub_html(content) when is_binary(content) do + content # html content comes from DB already encoded, decode first and scrub after |> HtmlEntities.decode() |> String.replace(~r/<br\s?\/?>/, " ") |> HTML.strip_tags() - |> Emoji.Formatter.demojify() - |> Formatter.truncate(max_length) end + def scrub_html(content), do: content + def attachment_url(url) do MediaProxy.url(url) end diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex index 358600e7d..04d823b36 100644 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MongooseIM.MongooseIMController do diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index abcf46034..18eb41333 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Nodeinfo.NodeinfoController do @@ -46,10 +46,10 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do data |> Map.merge(%{quarantined_instances: quarantined}) - |> Map.put(:enabled, Config.get([:instance, :federating])) else %{} end + |> Map.put(:enabled, Config.get([:instance, :federating])) features = [ @@ -69,9 +69,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do if Config.get([:chat, :enabled]) do "chat" end, - if Config.get([:suggestions, :enabled]) do - "suggestions" - end, if Config.get([:instance, :allow_relay]) do "relay" end, @@ -95,20 +92,16 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do openRegistrations: Config.get([:instance, :registrations_open]), usage: %{ users: %{ - total: stats.user_count || 0 + total: Map.get(stats, :user_count, 0) }, - localPosts: stats.status_count || 0 + localPosts: Map.get(stats, :status_count, 0) }, metadata: %{ nodeName: Config.get([:instance, :name]), nodeDescription: Config.get([:instance, :description]), private: !Config.get([:instance, :public], true), suggestions: %{ - enabled: Config.get([:suggestions, :enabled], false), - thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""), - timeout: Config.get([:suggestions, :timeout], 5000), - limit: Config.get([:suggestions, :limit], 23), - web: Config.get([:suggestions, :web], "") + enabled: false }, staffAccounts: staff_accounts, federation: federation_response, diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/oauth.ex index 280cf28c0..2f1b8708d 100644 --- a/lib/pleroma/web/oauth.ex +++ b/lib/pleroma/web/oauth.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth do diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex index cc3fb1ce5..01ed326f4 100644 --- a/lib/pleroma/web/oauth/app.ex +++ b/lib/pleroma/web/oauth/app.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.App do diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index ed42a34f3..268ee5b63 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Authorization do diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex index dd7f08bf1..a89ced886 100644 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ b/lib/pleroma/web/oauth/fallback_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.FallbackController do diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 2aee8cab2..46688db7e 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.OAuthController do @@ -14,10 +14,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do alias Pleroma.Web.ControllerHelper alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken - alias Pleroma.Web.OAuth.Scopes require Logger @@ -167,17 +167,37 @@ defmodule Pleroma.Web.OAuth.OAuthController do defp handle_create_authorization_error( %Plug.Conn{} = conn, - {:auth_active, false}, + {:account_status, :confirmation_pending}, %{"authorization" => _} = params ) do - # Per https://github.com/tootsuite/mastodon/blob/ - # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) |> put_status(:forbidden) |> authorize(params) end + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Password reset is required")) + |> put_status(:forbidden) + |> authorize(params) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :deactivated}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Your account is currently disabled")) + |> put_status(:forbidden) + |> authorize(params) + end + defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do Authenticator.handle_error(conn, error) end @@ -218,46 +238,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do ) do with {:ok, %User{} = user} <- Authenticator.get_user(conn), {:ok, app} <- Token.Utils.fetch_app(conn), - {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, - {:user_active, true} <- {:user_active, !user.deactivated}, - {:password_reset_pending, false} <- - {:password_reset_pending, user.password_reset_pending}, + {:account_status, :active} <- {:account_status, User.account_status(user)}, {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build(user, token)) else - {:auth_active, false} -> - # Per https://github.com/tootsuite/mastodon/blob/ - # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 - render_error( - conn, - :forbidden, - "Your login is missing a confirmed e-mail address", - %{}, - "missing_confirmed_email" - ) - - {:user_active, false} -> - render_error( - conn, - :forbidden, - "Your account is currently disabled", - %{}, - "account_is_disabled" - ) - - {:password_reset_pending, true} -> - render_error( - conn, - :forbidden, - "Password reset is required", - %{}, - "password_reset_required" - ) - - _error -> - render_invalid_credentials_error(conn) + error -> + handle_token_exchange_error(conn, error) end end @@ -286,6 +274,43 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Bad request def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do + render_error( + conn, + :forbidden, + "Your account is currently disabled", + %{}, + "account_is_disabled" + ) + end + + defp handle_token_exchange_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending} + ) do + render_error( + conn, + :forbidden, + "Password reset is required", + %{}, + "password_reset_required" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do + render_error( + conn, + :forbidden, + "Your login is missing a confirmed e-mail address", + %{}, + "missing_confirmed_email" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do + render_invalid_credentials_error(conn) + end + def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, _token} <- RevokeToken.revoke(app, params) do @@ -472,7 +497,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), {:ok, scopes} <- validate_scopes(app, auth_attrs), - {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do + {:account_status, :active} <- {:account_status, User.account_status(user)} do Authorization.create_authorization(app, user, scopes) end end @@ -489,7 +514,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do @spec validate_scopes(App.t(), map()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(app, params) do + defp validate_scopes(%App{} = app, params) do params |> Scopes.fetch_scopes(app.scopes) |> Scopes.validate(app.scopes) diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex index 9b37a91c5..94ddaf913 100644 --- a/lib/pleroma/web/oauth/oauth_view.ex +++ b/lib/pleroma/web/oauth/oauth_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.OAuthView do diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 48bd14407..8ecf901f3 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Scopes do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.OAuth.Scopes do Functions for dealing with scopes. """ + alias Pleroma.Plugs.OAuthScopesPlug + @doc """ Fetch scopes from request params. @@ -55,13 +57,19 @@ defmodule Pleroma.Web.OAuth.Scopes do """ @spec validate(list() | nil, list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - def validate([], _app_scopes), do: {:error, :missing_scopes} - def validate(nil, _app_scopes), do: {:error, :missing_scopes} + def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []], + do: {:error, :missing_scopes} def validate(scopes, app_scopes) do - case Pleroma.Plugs.OAuthScopesPlug.filter_descendants(scopes, app_scopes) do + case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do ^scopes -> {:ok, scopes} _ -> {:error, :unsupported_scopes} end end + + def contains_admin_scopes?(scopes) do + scopes + |> OAuthScopesPlug.filter_descendants(["admin"]) + |> Enum.any?() + end end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 8ea373805..08bb7326d 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex deleted file mode 100644 index f639f9c6f..000000000 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ /dev/null @@ -1,38 +0,0 @@ -# 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.CleanWorker do - @moduledoc """ - The module represents functions to clean an expired oauth tokens. - """ - use GenServer - - @ten_seconds 10_000 - @one_day 86_400_000 - - @interval Pleroma.Config.get( - [:oauth2, :clean_expired_tokens_interval], - @one_day - ) - - alias Pleroma.Web.OAuth.Token - alias Pleroma.Workers.BackgroundWorker - - def start_link(_), do: GenServer.start_link(__MODULE__, %{}) - - def init(_) do - Process.send_after(self(), :perform, @ten_seconds) - {:ok, nil} - end - - @doc false - def handle_info(:perform, state) do - BackgroundWorker.enqueue("clean_expired_tokens", %{}) - - Process.send_after(self(), :perform, @interval) - {:noreply, state} - end - - def perform(:clean), do: Token.delete_expired_tokens() -end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex index 9642103e6..93d6e26ed 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/oauth/token/query.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Query do diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex index 266110814..6f4713dee 100644 --- a/lib/pleroma/web/oauth/token/response.ex +++ b/lib/pleroma/web/oauth/token/response.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Response do diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex index c620050c8..debc29b0b 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex index 983f095b4..069c1ee21 100644 --- a/lib/pleroma/web/oauth/token/strategy/revoke.ex +++ b/lib/pleroma/web/oauth/token/strategy/revoke.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex index 1e8765e93..43aeab6b0 100644 --- a/lib/pleroma/web/oauth/token/utils.ex +++ b/lib/pleroma/web/oauth/token/utils.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Utils do diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 01ec7941e..6fd3cfce5 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus.OStatusController do @@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Router + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) + plug( RateLimiter, [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] @@ -135,13 +139,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end end - def errors(conn, {:error, :not_found}) do + defp errors(conn, {:error, :not_found}) do render_error(conn, :not_found, "Not found") end - def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) + defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) - def errors(conn, _) do + defp errors(conn, _) do render_error(conn, :internal_server_error, "Something went wrong") end end diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index bc2f1017c..dcba67d03 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.AccountController do @@ -144,7 +144,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do @doc "POST /api/v1/pleroma/accounts/:id/subscribe" def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do + with {:ok, _subscription} <- User.subscribe(user, subscription_target) do render(conn, "relationship.json", user: user, target: subscription_target) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) @@ -153,7 +153,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do + with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do render(conn, "relationship.json", user: user, target: subscription_target) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index a474d41d4..03e95e020 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do plug( OAuthScopesPlug, - %{scopes: ["write"]} + %{scopes: ["write"], admin: true} when action in [ :create, :delete, @@ -52,7 +52,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do @doc """ Lists the packs available on the instance as JSON. - The information is public and does not require authentification. The format is + The information is public and does not require authentication. The format is a map of "pack directory name" to pack.json contents. """ def list_packs(conn, _params) do @@ -323,7 +323,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:ok, _} -> conn |> json("ok") - {:error, _} -> + {:error, _, _} -> conn |> put_status(:internal_server_error) |> json(%{error: "Couldn't delete the pack #{name}"}) @@ -573,11 +573,14 @@ keeping it in cache for #{div(cache_ms, 1000)}s") assumed to be emojis and stored in the new `pack.json` file. """ def import_from_fs(conn, _params) do - with {:ok, results} <- File.ls(emoji_dir_path()) do + emoji_path = emoji_dir_path() + + with {:ok, %{access: :read_write}} <- File.stat(emoji_path), + {:ok, results} <- File.ls(emoji_path) do imported_pack_names = results |> Enum.filter(fn file -> - dir_path = Path.join(emoji_dir_path(), file) + dir_path = Path.join(emoji_path, file) # Find the directories that do NOT have pack.json File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) end) @@ -585,6 +588,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") json(conn, imported_pack_names) else + {:ok, %{access: _}} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error: emoji pack directory must be writable"}) + {:error, _} -> conn |> put_status(:internal_server_error) diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index 8cf552b7e..d9c1c8636 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.MascotController do diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 8fed3f5bb..dae7f0f2f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do @@ -22,7 +22,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do plug( OAuthScopesPlug, - %{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses] + %{scopes: ["read:statuses"]} + when action in [:conversation, :conversation_statuses] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:statuses"]} + when action in [:react_with_emoji, :unreact_with_emoji] ) plug( @@ -34,23 +41,36 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), - %Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do + %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <- + Object.normalize(activity) do reactions = emoji_reactions - |> Enum.map(fn {emoji, users} -> - users = Enum.map(users, &User.get_cached_by_ap_id/1) - {emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})} + |> Enum.map(fn [emoji, user_ap_ids] -> + if params["emoji"] && params["emoji"] != emoji do + nil + else + users = + Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) + |> Enum.filter(& &1) + + %{ + name: emoji, + count: length(users), + accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}), + me: !!(user && user.ap_id in user_ap_ids) + } + end end) - |> Enum.into(%{}) + |> Enum.filter(& &1) conn |> json(reactions) else _e -> conn - |> json(%{}) + |> json([]) end end @@ -81,6 +101,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do conn |> put_view(ConversationView) |> render("participation.json", %{participation: participation, for: user}) + else + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end @@ -88,9 +113,9 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do %{assigns: %{user: user}} = conn, %{"id" => participation_id} = params ) do - participation = Participation.get(participation_id, preload: [:conversation]) - - if user.id == participation.user_id do + with %Participation{} = participation <- + Participation.get(participation_id, preload: [:conversation]), + true <- user.id == participation.user_id do params = params |> Map.put("blocking_user", user) @@ -106,6 +131,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) + else + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end @@ -113,15 +143,22 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do %{assigns: %{user: user}} = conn, %{"id" => participation_id, "recipients" => recipients} ) do - participation = - participation_id - |> Participation.get() - - with true <- user.id == participation.user_id, + with %Participation{} = participation <- Participation.get(participation_id), + true <- user.id == participation.user_id, {:ok, participation} <- Participation.set_recipients(participation, recipients) do conn |> put_view(ConversationView) |> render("participation.json", %{participation: participation, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + + _error -> + conn + |> put_status(404) + |> json(%{"error" => "Unknown conversation id"}) end end diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index b74b3debc..4463ec477 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.ScrobbleController do diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index a6a924d02..afa510f08 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push.Impl do @@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do @spec perform(Notification.t()) :: list(any) | :error def perform( %{ - activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, - user_id: user_id + activity: %{data: %{"type" => activity_type}} = activity, + user: %User{id: user_id} } = notif ) when activity_type in @types do @@ -39,18 +39,17 @@ defmodule Pleroma.Web.Push.Impl do for subscription <- fetch_subsriptions(user_id), get_in(subscription.data, ["alerts", type]) do %{ - title: format_title(notif), access_token: subscription.token.token, - body: format_body(notif, actor, object), notification_id: notif.id, notification_type: type, icon: avatar_url, preferred_locale: "en", pleroma: %{ - activity_id: activity_id, + activity_id: notif.activity.id, direct_conversation_id: direct_conversation_id } } + |> Map.merge(build_content(notif, actor, object)) |> Jason.encode!() |> push_message(build_sub(subscription), gcm_api_key, subscription) end @@ -100,6 +99,24 @@ defmodule Pleroma.Web.Push.Impl do } end + def build_content( + %{ + activity: %{data: %{"directMessage" => true}}, + user: %{notification_settings: %{privacy_option: true}} + }, + actor, + _ + ) do + %{title: "New Direct Message", body: "@#{actor.nickname}"} + end + + def build_content(notif, actor, object) do + %{ + title: format_title(notif), + body: format_body(notif, actor, object) + } + end + def format_body( %{activity: %{data: %{"type" => "Create"}}}, actor, diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 7ef1532ac..b80a6438d 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push do diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index 988fabaeb..5c448d6c9 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push.Subscription do diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 16b1a53d2..e97c398dc 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RelMe do @@ -27,9 +27,10 @@ defmodule Pleroma.Web.RelMe do defp parse_url(url) do with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <- Pleroma.HTTP.get(url, [], adapter: @hackney_options), + {:ok, html_tree} <- Floki.parse_document(html), data <- - Floki.attribute(html, "link[rel~=me]", "href") ++ - Floki.attribute(html, "a[rel~=me]", "href") do + Floki.attribute(html_tree, "link[rel~=me]", "href") ++ + Floki.attribute(html_tree, "a[rel~=me]", "href") do {:ok, data} end rescue diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 6506de46c..0314535d2 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright _ 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright _ 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Helpers do diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index c06b0a0f2..0779065ee 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parser do @@ -81,18 +81,18 @@ defmodule Pleroma.Web.RichMedia.Parser do {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) html - |> parse_html + |> parse_html() |> maybe_parse() |> Map.put(:url, url) |> clean_parsed_data() |> check_parsed_data() rescue e -> - {:error, "Parsing error: #{inspect(e)}"} + {:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"} end end - defp parse_html(html), do: Floki.parse(html) + defp parse_html(html), do: Floki.parse_document!(html) defp maybe_parse(html) do Enum.reduce_while(parsers(), %{}, fn parser, acc -> 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 913975616..ae0f36702 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do @@ -48,6 +48,6 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do defp maybe_put_title(meta, _), do: meta defp get_page_title(html) do - Floki.find(html, "title") |> Floki.text() + Floki.find(html, "html head title") |> List.first() |> Floki.text() end end diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex index 875637c4d..8f32bf91b 100644 --- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do diff --git a/lib/pleroma/web/rich_media/parsers/ogp.ex b/lib/pleroma/web/rich_media/parsers/ogp.ex index d40fa009f..3e9012588 100644 --- a/lib/pleroma/web/rich_media/parsers/ogp.ex +++ b/lib/pleroma/web/rich_media/parsers/ogp.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parsers.OGP do diff --git a/lib/pleroma/web/rich_media/parsers/twitter_card.ex b/lib/pleroma/web/rich_media/parsers/twitter_card.ex index afaa98f3d..09d4b526e 100644 --- a/lib/pleroma/web/rich_media/parsers/twitter_card.ex +++ b/lib/pleroma/web/rich_media/parsers/twitter_card.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e6c4f6f14..e4e3ee704 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Router do @@ -187,19 +187,22 @@ defmodule Pleroma.Web.Router do get("/grouped_reports", AdminAPIController, :list_grouped_reports) get("/reports/:id", AdminAPIController, :report_show) patch("/reports", AdminAPIController, :reports_update) - post("/reports/:id/respond", AdminAPIController, :report_respond) + post("/reports/:id/notes", AdminAPIController, :report_notes_create) + delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) put("/statuses/:id", AdminAPIController, :status_update) delete("/statuses/:id", AdminAPIController, :status_delete) + get("/statuses", AdminAPIController, :list_statuses) get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) - get("/config/migrate_to_db", AdminAPIController, :migrate_to_db) - get("/config/migrate_from_db", AdminAPIController, :migrate_from_db) + get("/config/descriptions", AdminAPIController, :config_descriptions) + get("/restart", AdminAPIController, :restart) get("/moderation_log", AdminAPIController, :list_log) post("/reload_emoji", AdminAPIController, :reload_emoji) + get("/stats", AdminAPIController, :stats) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do @@ -228,9 +231,9 @@ defmodule Pleroma.Web.Router do pipe_through(:pleroma_html) post("/main/ostatus", UtilController, :remote_subscribe) - get("/ostatus_subscribe", UtilController, :remote_follow) + get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", UtilController, :do_remote_follow) + post("/ostatus_subscribe", RemoteFollowController, :do_follow) end scope "/api/pleroma", Pleroma.Web.TwitterAPI do @@ -270,7 +273,8 @@ defmodule Pleroma.Web.Router do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:api) - get("/statuses/:id/emoji_reactions_by", PleromaAPIController, :emoji_reactions_by) + get("/statuses/:id/reactions/:emoji", PleromaAPIController, :emoji_reactions_by) + get("/statuses/:id/reactions", PleromaAPIController, :emoji_reactions_by) end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do @@ -286,8 +290,8 @@ defmodule Pleroma.Web.Router do pipe_through(:authenticated_api) patch("/conversations/:id", PleromaAPIController, :update_conversation) - post("/statuses/:id/react_with_emoji", PleromaAPIController, :react_with_emoji) - post("/statuses/:id/unreact_with_emoji", PleromaAPIController, :unreact_with_emoji) + put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji) + delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji) post("/notifications/read", PleromaAPIController, :read_notification) patch("/accounts/update_avatar", AccountController, :update_avatar) @@ -526,12 +530,18 @@ defmodule Pleroma.Web.Router do get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) - get("/users/:nickname/feed", Feed.FeedController, :feed) - get("/users/:nickname", Feed.FeedController, :feed_redirect) + get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) + get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) + get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed) + end + + scope "/", Pleroma.Web do + pipe_through(:browser) get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe) end + # Server to Server (S2S) AP interactions pipeline :activitypub do plug(:accepts, ["activity+json", "json"]) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) @@ -545,6 +555,7 @@ defmodule Pleroma.Web.Router do get("/users/:nickname/outbox", ActivityPubController, :outbox) end + # Client to Server (C2S) AP interactions pipeline :activitypub_client do plug(:accepts, ["activity+json", "json"]) plug(:fetch_session) @@ -588,8 +599,8 @@ defmodule Pleroma.Web.Router do post("/inbox", ActivityPubController, :inbox) end - get("/following", ActivityPubController, :following, assigns: %{relay: true}) - get("/followers", ActivityPubController, :followers, assigns: %{relay: true}) + get("/following", ActivityPubController, :relay_following) + get("/followers", ActivityPubController, :relay_followers) end scope "/internal/fetch", Pleroma.Web.ActivityPub do diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 8ccf15f4b..7f9464268 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StaticFE.StaticFEController do @@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) plug(:assign_id) + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/0 + ) + @page_keys ["max_id", "min_id", "limit", "since_id", "order"] defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), @@ -33,7 +37,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do |> render("error.html", %{message: message, meta: ""}) end - def get_counts(%Activity{} = activity) do + defp get_counts(%Activity{} = activity) do %Object{data: data} = Object.normalize(activity) %{ @@ -43,9 +47,9 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do } end - def represent(%Activity{} = activity), do: represent(activity, false) + defp represent(%Activity{} = activity), do: represent(activity, false) - def represent(%Activity{object: %Object{data: data}} = activity, selected) do + defp represent(%Activity{object: %Object{data: data}} = activity, selected) do {:ok, user} = User.get_or_fetch(activity.object.data["actor"]) link = @@ -54,10 +58,17 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do _ -> data["url"] || data["external_url"] || data["id"] end + content = + if data["content"] do + Pleroma.HTML.filter_tags(data["content"]) + else + nil + end + %{ - user: user, + user: User.sanitize_html(user), title: get_title(activity.object), - content: data["content"] || nil, + content: content, attachment: data["attachment"], link: link, published: data["published"], @@ -109,7 +120,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do next_page_id = List.last(timeline) && List.last(timeline).id render(conn, "profile.html", %{ - user: user, + user: User.sanitize_html(user), timeline: timeline, prev_page_id: prev_page_id, next_page_id: next_page_id, @@ -147,17 +158,17 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do end end - def assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), + defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), do: assign(conn, :notice_id, notice_id) - def assign_id(%{path_info: ["users", user_id]} = conn, _opts), + defp assign_id(%{path_info: ["users", user_id]} = conn, _opts), do: assign(conn, :username_or_id, user_id) - def assign_id(%{path_info: ["objects", object_id]} = conn, _opts), + defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts), do: assign(conn, :object_id, object_id) - def assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), + defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), do: assign(conn, :activity_id, activity_id) - def assign_id(conn, _opts), do: conn + defp assign_id(conn, _opts), do: conn end diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex index 821ece9a9..66d87620c 100644 --- a/lib/pleroma/web/static_fe/static_fe_view.ex +++ b/lib/pleroma/web/static_fe/static_fe_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StaticFE.StaticFEView do diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex index db3e68abe..7a08202a9 100644 --- a/lib/pleroma/web/streamer/ping.ex +++ b/lib/pleroma/web/streamer/ping.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer.Ping do diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex index 5ce3ebb8a..999550b88 100644 --- a/lib/pleroma/web/streamer/state.ex +++ b/lib/pleroma/web/streamer/state.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer.State do diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex index 2fc7ac8cf..814d5a729 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer/streamer.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer do diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex index cf0fa3077..7d5dcd34e 100644 --- a/lib/pleroma/web/streamer/streamer_socket.ex +++ b/lib/pleroma/web/streamer/streamer_socket.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer.StreamerSocket do diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex index ec5985085..bd9029bc0 100644 --- a/lib/pleroma/web/streamer/supervisor.ex +++ b/lib/pleroma/web/streamer/supervisor.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer.Supervisor do diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index 33b24840d..29f992a67 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Streamer.Worker do @@ -129,16 +129,18 @@ defmodule Pleroma.Web.Streamer.Worker do end defp should_send?(%User{} = user, %Activity{} = item) do - blocks = user.blocks || [] - mutes = user.mutes || [] - reblog_mutes = user.muted_reblogs || [] - recipient_blocks = MapSet.new(blocks ++ mutes) + %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} = + User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute]) + + recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids) recipients = MapSet.new(item.recipients) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) with parent <- Object.normalize(item) || item, - true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), - true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), + true <- + Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), + true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, + true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)), true <- MapSet.disjoint?(recipients, recipient_blocks), %{host: item_host} <- URI.parse(item.actor), %{host: parent_host} <- URI.parse(parent.data["actor"]), diff --git a/lib/pleroma/web/templates/email/new_users_digest.html.eex b/lib/pleroma/web/templates/email/new_users_digest.html.eex new file mode 100644 index 000000000..40d9b8381 --- /dev/null +++ b/lib/pleroma/web/templates/email/new_users_digest.html.eex @@ -0,0 +1,158 @@ +<%= for {user, total_statuses, latest_status} <- @users_and_statuses do %> + <%# user card START %> + <div style="background-color:transparent;"> + <div class="block-grid mixed-two-up no-stack" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num3" + style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;"> + <!--<![endif]--> + <div align="left" class="img-container left " + style="padding-right: 0px;padding-left: 0px;"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img + alt="<%= user.name %>" border="0" class="left " src="<%= avatar_url(user) %>" + style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;" + title="<%= user.name %>" width="76" /> + <!--[if mso]></td></tr></table><![endif]--> + </div> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num9" + style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px; color: <%= @styling.text_color %>;"><%= user.name %></span></p> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px;"><%= link "@" <> user.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: admin_user_url(user) %></span></p> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px;">Total: <%= total_statuses %></span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <%# user card END %> + + <%= if latest_status do %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <span style="font-size: 16px; line-height: 19px;"><%= raw latest_status.object.data["content"] %></span></div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;"> + <p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date latest_status.object.data["published"] %></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <% end %> + <%# divider start %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td class="divider_inner" + style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;" + valign="top"> + <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" + height="0" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td height="0" + style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top"><span></span></td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + + <%# divider end %> + <%# user card END %> +<% end %> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_activity.xml.eex index 514eacaed..ac8a75009 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.xml.eex @@ -9,7 +9,7 @@ <ostatus:conversation ref="<%= activity_context(@activity) %>"> <%= activity_context(@activity) %> </ostatus:conversation> - <link ref="<%= activity_context(@activity) %>" rel="ostatus:conversation"/> + <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/> <%= if @data["summary"] do %> <summary><%= @data["summary"] %></summary> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex new file mode 100644 index 000000000..da4fa6d6c --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -0,0 +1,51 @@ +<entry> + <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> + <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> + + <%= render @view_module, "_tag_author.atom", assigns %> + + <id><%= @data["id"] %></id> + <title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title> + <content type="html"><%= activity_content(@object) %></content> + + <%= if @activity.local do %> + <link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/> + <link type="text/html" href='<%= @data["id"] %>' rel="alternate"/> + <% else %> + <link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/> + <% end %> + + <published><%= @data["published"] %></published> + <updated><%= @data["published"] %></updated> + + <ostatus:conversation ref="<%= activity_context(@activity) %>"> + <%= activity_context(@activity) %> + </ostatus:conversation> + <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/> + + <%= if @data["summary"] do %> + <summary><%= @data["summary"] %></summary> + <% end %> + + <%= for id <- @activity.recipients do %> + <%= if id == Pleroma.Constants.as_public() do %> + <link rel="mentioned" + ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" + href="http://activityschema.org/collection/public"/> + <% else %> + <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> + <link rel="mentioned" + ostatus:object-type="http://activitystrea.ms/schema/1.0/person" + href="<%= id %>" /> + <% end %> + <% end %> + <% end %> + + <%= for tag <- @data["tag"] || [] do %> + <category term="<%= tag %>"></category> + <% end %> + + <%= for {emoji, file} <- @data["emoji"] || %{} do %> + <link name="<%= emoji %>" rel="emoji" href="<%= file %>"/> + <% end %> +</entry> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex new file mode 100644 index 000000000..295574df1 --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -0,0 +1,15 @@ +<item> + <title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title> + + + <guid isPermalink="true"><%= activity_context(@activity) %></guid> + <link><%= activity_context(@activity) %></link> + <pubDate><%= pub_date(@data["published"]) %></pubDate> + + <description><%= activity_content(@object) %></description> + <%= for attachment <- @data["attachment"] || [] do %> + <enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/> + <% end %> + +</item> + diff --git a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex new file mode 100644 index 000000000..997c4936e --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex @@ -0,0 +1,18 @@ +<author> + <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> + <id><%= @actor.ap_id %></id> + <uri><%= @actor.ap_id %></uri> + <name><%= @actor.nickname %></name> + <summary><%= escape(@actor.bio) %></summary> + <link rel="avatar" href="<%= User.avatar_url(@actor) %>"/> + <%= if User.banner_url(@actor) do %> + <link rel="header" href="<%= User.banner_url(@actor) %>"/> + <% end %> + <%= if @actor.local do %> + <ap_enabled>true</ap_enabled> + <% end %> + + <poco:preferredUsername><%= @actor.nickname %></poco:preferredUsername> + <poco:displayName><%= @actor.name %></poco:displayName> + <poco:note><%= escape(@actor.bio) %></poco:note> +</author> diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex new file mode 100644 index 000000000..a288539ed --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" + xmlns:thr="http://purl.org/syndication/thread/1.0" + xmlns:georss="http://www.georss.org/georss" + xmlns:activity="http://activitystrea.ms/spec/1.0/" + xmlns:media="http://purl.org/syndication/atommedia" + xmlns:poco="http://portablecontacts.net/spec/1.0" + xmlns:ostatus="http://ostatus.org/schema/1.0" + xmlns:statusnet="http://status.net/schema/api/1/"> + + <id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id> + <title>#<%= @tag %></title> + + <subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle> + <logo><%= feed_logo() %></logo> + <updated><%= most_recent_update(@activities) %></updated> + <link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/> + <%= for activity <- @activities do %> + <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> + <% end %> +</feed> diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex new file mode 100644 index 000000000..eeda01a04 --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0"> + <channel> + + + <title>#<%= @tag %></title> + <description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description> + <link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link> + <webfeeds:logo><%= feed_logo() %></webfeeds:logo> + <webfeeds:accentColor>2b90d9</webfeeds:accentColor> + <%= for activity <- @activities do %> + <%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %> + <% end %> + </channel> +</rss> diff --git a/lib/pleroma/web/templates/feed/feed/feed.xml.eex b/lib/pleroma/web/templates/feed/feed/user.xml.eex index 5ae36d345..d274c08ae 100644 --- a/lib/pleroma/web/templates/feed/feed/feed.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/user.xml.eex @@ -6,16 +6,16 @@ xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"> - <id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id> + <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id> <title><%= @user.nickname <> "'s timeline" %></title> <updated><%= most_recent_update(@activities, @user) %></updated> <logo><%= logo(@user) %></logo> - <link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/> + <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/> <%= render @view_module, "_author.xml", assigns %> <%= if last_activity(@activities) do %> - <link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/> + <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/> <% end %> <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex new file mode 100644 index 000000000..ca2caaf4d --- /dev/null +++ b/lib/pleroma/web/templates/layout/email_styled.html.eex @@ -0,0 +1,193 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:v="urn:schemas-microsoft-com:vml"> + +<head> + <!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]--> + <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> + <meta content="width=device-width" name="viewport" /> + <!--[if !mso]><!--> + <meta content="IE=edge" http-equiv="X-UA-Compatible" /> + <!--<![endif]--> + <title><%= @email.subject %></title> + <!--[if !mso]><!--> + <!--<![endif]--> + <style type="text/css"> + body { + margin: 0; + padding: 0; + } + + a { + + color: <%= @styling.link_color %>; + text-decoration: none; + } + + table, + td, + tr { + vertical-align: top; + border-collapse: collapse; + } + + * { + line-height: inherit; + } + + a[x-apple-data-detectors=true] { + color: inherit !important; + text-decoration: none !important; + } + </style> + <style id="media-query" type="text/css"> + @media (max-width: 610px) { + + .block-grid, + .col { + min-width: 320px !important; + max-width: 100% !important; + display: block !important; + } + + .block-grid { + width: 100% !important; + } + + .col { + width: 100% !important; + } + + .col>div { + margin: 0 auto; + } + + .no-stack .col { + min-width: 0 !important; + display: table-cell !important; + } + + .no-stack.two-up .col { + width: 50% !important; + } + + .no-stack .col.num4 { + width: 33% !important; + } + + .no-stack .col.num8 { + width: 66% !important; + } + + .no-stack .col.num4 { + width: 33% !important; + } + + .no-stack .col.num3 { + width: 25% !important; + } + + .no-stack .col.num6 { + width: 50% !important; + } + + .no-stack .col.num9 { + width: 75% !important; + } + + } + </style> +</head> + +<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;"> + <!--[if IE]><div class="ie-browser"><![endif]--> + <table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" + style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td style="word-break: break-word; vertical-align: top;" valign="top"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]--> + + <%# header %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <div align="center" class="img-container center" + style="padding-right: 0px;padding-left: 0px;"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img + align="center" alt="Image" border="0" class="center" src="<%= @logo_url %>" + style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;" + title="Image" height="80" /> + <!--[if mso]></td></tr></table><![endif]--> + </div> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + + + <%# title %> + <%= if @title do %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;"> + <p style="line-height: 36px; text-align: center; margin: 0;"><span + style="font-size: 30px; color: <%= @styling.header_color %>;"><%= @title %></span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <% end %> + <%= render @view_module, @view_template, assigns %> + + </td> + </tr> + </tbody> + </table> + <!--[if (IE)]></div><![endif]--> +</body> + +</html> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex new file mode 100644 index 000000000..5ba192cd7 --- /dev/null +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex @@ -0,0 +1,11 @@ +<%= if @error == :error do %> + <h2>Error fetching user</h2> +<% else %> + <h2>Remote follow</h2> + <img height="128" width="128" src="<%= avatar_url(@followee) %>"> + <p><%= @followee.nickname %></p> + <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> + <%= hidden_input f, :id, value: @followee.id %> + <%= submit "Authorize" %> + <% end %> +<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex new file mode 100644 index 000000000..df44988ee --- /dev/null +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex @@ -0,0 +1,14 @@ +<%= if @error do %> +<h2><%= @error %></h2> +<% end %> +<h2>Log in to follow</h2> +<p><%= @followee.nickname %></p> +<img height="128" width="128" src="<%= avatar_url(@followee) %>"> +<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> +<%= text_input f, :name, placeholder: "Username", required: true %> +<br> +<%= password_input f, :password, placeholder: "Password", required: true %> +<br> +<%= hidden_input f, :id, value: @followee.id %> +<%= submit "Authorize" %> +<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex index da473d502..da473d502 100644 --- a/lib/pleroma/web/templates/twitter_api/util/followed.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow.html.eex deleted file mode 100644 index 06359fa6c..000000000 --- a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex +++ /dev/null @@ -1,11 +0,0 @@ -<%= if @error == :error do %> - <h2>Error fetching user</h2> -<% else %> - <h2>Remote follow</h2> - <img width="128" height="128" src="<%= @avatar %>"> - <p><%= @name %></p> - <%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "user"], fn f -> %> - <%= hidden_input f, :id, value: @id %> - <%= submit "Authorize" %> - <% end %> -<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex deleted file mode 100644 index 4e3a2be67..000000000 --- a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex +++ /dev/null @@ -1,14 +0,0 @@ -<%= if @error do %> - <h2><%= @error %></h2> -<% end %> -<h2>Log in to follow</h2> -<p><%= @name %></p> -<img height="128" width="128" src="<%= @avatar %>"> -<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "authorization"], fn f -> %> -<%= text_input f, :name, placeholder: "Username" %> -<br> -<%= password_input f, :password, placeholder: "Password" %> -<br> -<%= hidden_input f, :id, value: @id %> -<%= submit "Authorize" %> -<% end %> diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex index a104ea6b8..7f78ce1b9 100644 --- a/lib/pleroma/web/translation_helpers.ex +++ b/lib/pleroma/web/translation_helpers.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TranslationHelpers do diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex index 1941e6143..800ab8954 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.PasswordController do diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex new file mode 100644 index 000000000..89da760da --- /dev/null +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -0,0 +1,114 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.Activity + alias Pleroma.Object.Fetcher + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + alias Pleroma.Web.Auth.Authenticator + alias Pleroma.Web.CommonAPI + + @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] + + plug(Pleroma.Web.FederatingPlug) + + # Note: follower can submit the form (with password auth) not being signed in (having no token) + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} + when action in [:do_follow] + ) + + # GET /ostatus_subscribe + # + def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do + case is_status?(acct) do + true -> follow_status(conn, user, acct) + _ -> follow_account(conn, user, acct) + end + end + + defp follow_status(conn, _user, acct) do + with {:ok, object} <- Fetcher.fetch_object_from_id(acct), + %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do + redirect(conn, to: o_status_path(conn, :notice, activity_id)) + else + error -> + handle_follow_error(conn, error) + end + end + + defp follow_account(conn, user, acct) do + with {:ok, followee} <- User.get_or_fetch(acct) do + render(conn, follow_template(user), %{error: false, followee: followee, acct: acct}) + else + {:error, _reason} -> + render(conn, follow_template(user), %{error: :error}) + end + end + + defp follow_template(%User{} = _user), do: "follow.html" + defp follow_template(_), do: "follow_login.html" + + defp is_status?(acct) do + case Fetcher.fetch_and_contain_remote_object_from_id(acct) do + {:ok, %{"type" => type}} when type in @status_types -> + true + + _ -> + false + end + end + + # POST /ostatus_subscribe + # + def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + redirect(conn, to: "/users/#{followee.id}") + else + error -> + handle_follow_error(conn, error) + end + end + + def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + redirect(conn, to: "/users/#{followee.id}") + else + error -> + handle_follow_error(conn, error) + end + end + + def do_follow(%{assigns: %{user: nil}} = conn, _) do + Logger.debug("Insufficient permissions: follow | write:follows.") + render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."}) + end + + defp handle_follow_error(conn, {:auth, _, followee} = _) do + render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee}) + end + + defp handle_follow_error(conn, {:fetch_user, error} = _) do + Logger.debug("Remote follow failed with error #{inspect(error)}") + render(conn, "followed.html", %{error: "Could not find user"}) + end + + defp handle_follow_error(conn, {:error, "Could not follow user:" <> _} = _) do + render(conn, "followed.html", %{error: "Error following account"}) + end + + defp handle_follow_error(conn, error) do + Logger.debug("Remote follow failed with error #{inspect(error)}") + render(conn, "followed.html", %{error: "Something went wrong."}) + 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 2305bb413..537f9f778 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.UtilController do @@ -7,22 +7,29 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do require Logger - alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck alias Pleroma.Notification - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.CommonAPI alias Pleroma.Web.WebFinger + plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) + plug( OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} - when action in [:do_remote_follow, :follow_import] + when action == :follow_import + ) + + # Note: follower can submit the form (with password auth) not being signed in (having no token) + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} + when action == :do_remote_follow ) plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) @@ -77,94 +84,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end - def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do - if is_status?(acct) do - {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct) - %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"]) - redirect(conn, to: "/notice/#{activity_id}") - else - with {:ok, followee} <- User.get_or_fetch(acct) do - conn - |> render(follow_template(user), %{ - error: false, - acct: acct, - avatar: User.avatar_url(followee), - name: followee.nickname, - id: followee.id - }) - else - {:error, _reason} -> - render(conn, follow_template(user), %{error: :error}) - end - end - end - - defp follow_template(%User{} = _user), do: "follow.html" - defp follow_template(_), do: "follow_login.html" - - defp is_status?(acct) do - case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do - {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] -> - true - - _ -> - false - end - end - - def do_remote_follow(conn, %{ - "authorization" => %{"name" => username, "password" => password, "id" => id} - }) do - with %User{} = followee <- User.get_cached_by_id(id), - {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee}, - {_, true, _} <- { - :auth, - AuthenticationPlug.checkpw(password, user.password_hash), - followee - }, - {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do - conn - |> render("followed.html", %{error: false}) - else - # Was already following user - {:error, "Could not follow user:" <> _rest} -> - render(conn, "followed.html", %{error: "Error following account"}) - - {:auth, _, followee} -> - conn - |> render("follow_login.html", %{ - error: "Wrong username or password", - id: id, - name: followee.nickname, - avatar: User.avatar_url(followee) - }) - - e -> - Logger.debug("Remote follow failed with error #{inspect(e)}") - render(conn, "followed.html", %{error: "Something went wrong."}) - end - end - - def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, - {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do - conn - |> render("followed.html", %{error: false}) - else - # Was already following user - {:error, "Could not follow user:" <> _rest} -> - render(conn, "followed.html", %{error: "Error following account"}) - - {:fetch_user, error} -> - Logger.debug("Remote follow failed with error #{inspect(error)}") - render(conn, "followed.html", %{error: "Could not find user"}) - - e -> - Logger.debug("Remote follow failed with error #{inspect(e)}") - render(conn, "followed.html", %{error: "Something went wrong."}) - end - end - def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do with {:ok, _} <- Notification.read_one(user, notification_id) do json(conn, %{status: "success"}) @@ -345,7 +264,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def delete_account(%{assigns: %{user: user}} = conn, params) do - case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + password = params["password"] || "" + + case CommonAPI.Utils.confirm_current_password(user, password) do {:ok, user} -> User.delete(user) json(conn, %{status: "success"}) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index bfd838902..f9c0994da 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do @@ -99,7 +99,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), - %User{local: true} = user <- User.get_by_nickname_or_email(nickname_or_email), + %User{local: true, email: email} = user when not is_nil(email) <- + User.get_by_nickname_or_email(nickname_or_email), {:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do user |> UserEmail.password_reset_email(token_record.token) @@ -110,6 +111,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do false -> {:error, "bad user identifier"} + %User{local: true, email: nil} -> + {:ok, :noop} + %User{local: false} -> {:error, "remote user"} diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 39f10c49f..0229aea97 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.Controller do diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex index b166b925d..41462e4af 100644 --- a/lib/pleroma/web/twitter_api/views/password_view.ex +++ b/lib/pleroma/web/twitter_api/views/password_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.PasswordView do diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex new file mode 100644 index 000000000..c05c7821c --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do + use Pleroma.Web, :view + import Phoenix.HTML.Form + + defdelegate avatar_url(user), to: Pleroma.User +end diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/twitter_api/views/token_view.ex index 3ff314913..c36303625 100644 --- a/lib/pleroma/web/twitter_api/views/token_view.ex +++ b/lib/pleroma/web/twitter_api/views/token_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TokenView do diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index f4050650e..52054e020 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.UtilView do diff --git a/lib/pleroma/web/uploader_controller.ex b/lib/pleroma/web/uploader_controller.ex index 0cc172698..6533f1c0e 100644 --- a/lib/pleroma/web/uploader_controller.ex +++ b/lib/pleroma/web/uploader_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.UploaderController do diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index b506a234b..6b0fbe61e 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -12,4 +12,8 @@ defmodule Pleroma.Web.EmailView do |> Timex.parse!("{ISO:Extended:Z}") |> Timex.format!("{Mshort} {D}, {YYYY} {h24}:{m}") end + + def admin_user_url(%{id: id}) do + Pleroma.Web.Endpoint.url() <> "/pleroma/admin/#/users/" <> id + end end diff --git a/lib/pleroma/web/views/error_helpers.ex b/lib/pleroma/web/views/error_helpers.ex index bc08e60e4..df657a343 100644 --- a/lib/pleroma/web/views/error_helpers.ex +++ b/lib/pleroma/web/views/error_helpers.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ErrorHelpers do diff --git a/lib/pleroma/web/views/error_view.ex b/lib/pleroma/web/views/error_view.ex index 5cb8669fe..e68d55e08 100644 --- a/lib/pleroma/web/views/error_view.ex +++ b/lib/pleroma/web/views/error_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ErrorView do diff --git a/lib/pleroma/web/views/layout_view.ex b/lib/pleroma/web/views/layout_view.ex index e5183701d..3e49c6549 100644 --- a/lib/pleroma/web/views/layout_view.ex +++ b/lib/pleroma/web/views/layout_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.LayoutView do diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex index c39b7f095..c3096006e 100644 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ b/lib/pleroma/web/views/masto_fe_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastoFEView do diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index a9f14d09a..443868878 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StreamerView do diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 687346554..cf3ac1287 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web do diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index b4cc80179..43a81c75d 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger do diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 896eb15f9..7077b20d2 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger.WebFingerController do diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml/xml.ex index df50aac9c..c69a86a1e 100644 --- a/lib/pleroma/web/xml/xml.ex +++ b/lib/pleroma/web/xml/xml.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.XML do |