diff options
Diffstat (limited to 'lib')
33 files changed, 589 insertions, 216 deletions
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 093b1f405..c2020d30a 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -124,6 +124,7 @@ defmodule Pleroma.FollowingRelationship do |> join(:inner, [r], f in assoc(r, :follower)) |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) + |> where([r, f], f.deactivated != true) |> select([r, f], f) |> Repo.all() end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index d78c5f202..dc1b9b840 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -109,7 +109,7 @@ defmodule Pleroma.HTML do result = content |> Floki.parse_fragment!() - |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]") + |> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]") |> Floki.attribute("a", "href") |> Enum.at(0) diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex new file mode 100644 index 000000000..e53e64077 --- /dev/null +++ b/lib/pleroma/http/ex_aws.ex @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.ExAws do + @moduledoc false + + @behaviour ExAws.Request.HttpClient + + alias Pleroma.HTTP + + @impl true + def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do + case HTTP.request(method, url, body, headers, http_opts) do + {:ok, env} -> + {:ok, %{status_code: env.status, headers: env.headers, body: env.body}} + + {:error, reason} -> + {:error, %{reason: reason}} + end + end +end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 9ee9606be..2ef1a80c5 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -367,6 +367,7 @@ defmodule Pleroma.Notification do do_send = do_send && user in enabled_receivers create_notification(activity, user, do_send) end) + |> Enum.reject(&is_nil/1) {:ok, notifications} end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 263ded5dd..3e2949ee2 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -83,8 +83,8 @@ defmodule Pleroma.Object.Fetcher do {:transmogrifier, {:error, {:reject, nil}}} -> {:reject, nil} - {:transmogrifier, _} -> - {:error, "Transmogrifier failure."} + {:transmogrifier, _} = e -> + {:error, e} {:object, data, nil} -> reinject_object(%Object{}, data) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1d70a37ef..8a54546d6 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -115,7 +115,7 @@ defmodule Pleroma.User do field(:is_moderator, :boolean, default: false) field(:is_admin, :boolean, default: false) field(:show_role, :boolean, default: true) - field(:settings, :map, default: nil) + field(:mastofe_settings, :map, default: nil) field(:uri, ObjectValidators.Uri, default: nil) field(:hide_followers_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false) @@ -1309,7 +1309,8 @@ defmodule Pleroma.User do unsubscribe(blocked, blocker) - if following?(blocked, blocker), do: unfollow(blocked, blocker) + unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true) + if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker) {:ok, blocker} = update_follower_count(blocker) {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked) @@ -1527,8 +1528,7 @@ defmodule Pleroma.User do blocked_identifiers, fn blocked_identifier -> with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, _user_block} <- block(blocker, blocked), - {:ok, _} <- ActivityPub.block(blocker, blocked) do + {:ok, _block} <- CommonAPI.block(blocker, blocked) do blocked else err -> @@ -2118,8 +2118,8 @@ defmodule Pleroma.User do def mastodon_settings_update(user, settings) do user - |> cast(%{settings: settings}, [:settings]) - |> validate_required([:settings]) + |> cast(%{mastofe_settings: settings}, [:mastofe_settings]) + |> validate_required([:mastofe_settings]) |> update_and_set_cache() end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7cd3eab39..94117202c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -366,33 +366,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - @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 - unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) - - if unfollow_blocked and fetch_latest_follow(blocker, blocked) do - unfollow(blocker, blocked, nil, local) - end - - block_data = make_block_data(blocker, blocked, activity_id) - - with {:ok, activity} <- insert(block_data, local), - _ <- notify_and_stream(activity), - :ok <- maybe_federate(activity) do - {:ok, activity} - else - {:error, error} -> Repo.rollback(error) - end - end - @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()} def flag( %{ @@ -1398,6 +1371,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def maybe_handle_clashing_nickname(nickname) do + with %User{} = old_user <- User.get_by_nickname(nickname) do + Logger.info("Found an old user for #{nickname}, ap id is #{old_user.ap_id}, renaming.") + + old_user + |> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"}) + |> User.update_and_set_cache() + end + end + def make_user_from_ap_id(ap_id) do user = User.get_cached_by_ap_id(ap_id) @@ -1410,6 +1393,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> User.remote_user_changeset(data) |> User.update_and_set_cache() else + maybe_handle_clashing_nickname(data[:nickname]) + data |> User.remote_user_changeset() |> Repo.insert() diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 135a5c431..cabc28de9 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -138,6 +138,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do }, []} end + @spec block(User.t(), User.t()) :: {:ok, map(), keyword()} + def block(blocker, blocked) do + {:ok, + %{ + "id" => Utils.generate_activity_id(), + "type" => "Block", + "actor" => blocker.ap_id, + "object" => blocked.ap_id, + "to" => [blocked.ap_id] + }, []} + end + @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} def announce(actor, object, options \\ []) do public? = Keyword.get(options, :public, false) 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 9e7800997..a7e187b5e 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 @@ -27,11 +27,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do @impl true def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do - with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor), + with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor), {:contains_links, true} <- {:contains_links, contains_links?(object)}, {:old_user, true} <- {:old_user, old_user?(u)} do {:ok, message} else + {:ok, %User{local: true}} -> + {:ok, message} + {:contains_links, false} -> {:ok, message} diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 2c657b467..bb6324460 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator @@ -24,6 +25,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) + def validate(%{"type" => "Block"} = block_activity, meta) do + with {:ok, block_activity} <- + block_activity + |> BlockValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + block_activity = stringify_keys(block_activity) + outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks]) + + meta = + if !outgoing_blocks do + Keyword.put(meta, :do_not_federate, true) + else + meta + end + + {:ok, block_activity, meta} + end + end + def validate(%{"type" => "Update"} = update_activity, meta) do with {:ok, update_activity} <- update_activity diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex new file mode 100644 index 000000000..1dde77198 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.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.ActivityPub.ObjectValidators.BlockValidator do + use Ecto.Schema + + alias Pleroma.EctoType.ActivityPub.ObjectValidators + + import Ecto.Changeset + import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + + @primary_key false + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:type, :string) + field(:actor, ObjectValidators.ObjectID) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:object, ObjectValidators.ObjectID) + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, __schema__(:fields)) + end + + def validate_data(cng) do + cng + |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_inclusion(:type, ["Block"]) + |> validate_actor_presence() + |> validate_actor_presence(field_name: :object) + end + + def cast_and_validate(data) do + data + |> cast_data + |> validate_data + end +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index de143b8f0..5cc2eb378 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -21,6 +21,21 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do def handle(object, meta \\ []) # Tasks this handles: + # - Unfollow and block + def handle( + %{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} = + object, + meta + ) do + with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user), + %User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do + User.block(blocker, blocked) + end + + {:ok, object, meta} + end + + # Tasks this handles: # - Update the user # # For a local user, we also get a changeset with the full information, so we diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4e318e89c..278fbbeab 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -673,7 +673,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming(%{"type" => type} = data, _options) - when type in ["Like", "EmojiReact", "Announce"] do + when type in ~w{Like EmojiReact Announce} do with :ok <- ObjectValidator.fetch_actor_and_object(data), {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do @@ -684,9 +684,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Update"} = data, + %{"type" => type} = data, _options - ) do + ) + when type in ~w{Update Block} do with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} @@ -766,21 +767,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data, - _options - ) do - with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked), - {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker), - {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do - User.unfollow(blocker, blocked) - User.block(blocker, blocked) - {:ok, activity} - else - _e -> :error - end - end - - def handle_incoming( %{ "type" => "Move", "actor" => origin_actor, diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 453a6842e..343f41caa 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -47,6 +47,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean() def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true + def visible_for_user?(nil, _), do: false + + def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false + def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do user.ap_id in activity.data["to"] || list_ap_id @@ -54,8 +58,6 @@ defmodule Pleroma.Web.ActivityPub.Visibility do |> Pleroma.List.member?(user) end - def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false - def visible_for_user?(%{local: local} = activity, nil) do cfg_key = if local, diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index bd9026237..fbfc27d6f 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -40,7 +40,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do |> List.first() _ -> - nil + "application/json" end private_data = Map.put(private_data, :operation_id, operation_id) diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index d54e2158d..84f18f1b6 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do pleroma: %Schema{ type: :object, properties: %{ - allow_following_move: %Schema{type: :boolean}, - background_image: %Schema{type: :string, nullable: true}, + allow_following_move: %Schema{ + type: :boolean, + description: "whether the user allows automatically follow moved following accounts" + }, + background_image: %Schema{type: :string, nullable: true, format: :uri}, chat_token: %Schema{type: :string}, - confirmation_pending: %Schema{type: :boolean}, + confirmation_pending: %Schema{ + type: :boolean, + description: + "whether the user account is waiting on email confirmation to be activated" + }, hide_favorites: %Schema{type: :boolean}, - hide_followers_count: %Schema{type: :boolean}, - hide_followers: %Schema{type: :boolean}, - hide_follows_count: %Schema{type: :boolean}, - hide_follows: %Schema{type: :boolean}, - is_admin: %Schema{type: :boolean}, - is_moderator: %Schema{type: :boolean}, + hide_followers_count: %Schema{ + type: :boolean, + description: "whether the user has follower stat hiding enabled" + }, + hide_followers: %Schema{ + type: :boolean, + description: "whether the user has follower hiding enabled" + }, + hide_follows_count: %Schema{ + type: :boolean, + description: "whether the user has follow stat hiding enabled" + }, + hide_follows: %Schema{ + type: :boolean, + description: "whether the user has follow hiding enabled" + }, + is_admin: %Schema{ + type: :boolean, + description: "whether the user is an admin of the local instance" + }, + is_moderator: %Schema{ + type: :boolean, + description: "whether the user is a moderator of the local instance" + }, skip_thread_containment: %Schema{type: :boolean}, - tags: %Schema{type: :array, items: %Schema{type: :string}}, - unread_conversation_count: %Schema{type: :integer}, + tags: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: + "List of tags being used for things like extra roles or moderation(ie. marking all media as nsfw all)." + }, + unread_conversation_count: %Schema{ + type: :integer, + description: "The count of unread conversations. Only returned to the account owner." + }, notification_settings: %Schema{ type: :object, properties: %{ @@ -66,7 +99,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do }, relationship: AccountRelationship, settings_store: %Schema{ - type: :object + type: :object, + description: + "A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`" } } }, @@ -74,16 +109,32 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do type: :object, properties: %{ fields: %Schema{type: :array, items: AccountField}, - note: %Schema{type: :string}, + note: %Schema{ + type: :string, + description: + "Plaintext version of the bio without formatting applied by the backend, used for editing the bio." + }, privacy: VisibilityScope, sensitive: %Schema{type: :boolean}, pleroma: %Schema{ type: :object, properties: %{ actor_type: ActorType, - discoverable: %Schema{type: :boolean}, - no_rich_text: %Schema{type: :boolean}, - show_role: %Schema{type: :boolean} + discoverable: %Schema{ + type: :boolean, + description: + "whether the user allows discovery of the account in search results and other services." + }, + no_rich_text: %Schema{ + type: :boolean, + description: + "whether the HTML tags for rich-text formatting are stripped from all statuses requested from the API." + }, + show_role: %Schema{ + type: :boolean, + description: + "whether the user wants their role (e.g admin, moderator) to be shown" + } } } } diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 8b87cb25b..28cde963e 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -184,6 +184,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do thread_muted: %Schema{ type: :boolean, description: "`true` if the thread the post belongs to is muted" + }, + parent_visible: %Schema{ + type: :boolean, + description: "`true` if the parent post is visible to the user" } } }, diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 04e081a8e..fd7149079 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do require Pleroma.Constants require Logger + def block(blocker, blocked) do + with {:ok, block_data, _} <- Builder.block(blocker, blocked), + {:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do + {:ok, block} + end + end + def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), :ok <- validate_chat_content_length(content, !!maybe_attachment), diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex index 0d9d578fc..431ad5485 100644 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ b/lib/pleroma/web/fallback_redirect_controller.ex @@ -9,6 +9,7 @@ defmodule Fallback.RedirectController do alias Pleroma.User alias Pleroma.Web.Metadata + alias Pleroma.Web.Preload def api_not_implemented(conn, _params) do conn @@ -16,16 +17,7 @@ defmodule Fallback.RedirectController do |> json(%{error: "Not implemented"}) end - def redirector(conn, _params, code \\ 200) - - # redirect to admin section - # /pleroma/admin -> /pleroma/admin/ - # - def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do - redirect(conn, to: "/pleroma/admin/") - end - - def redirector(conn, _params, code) do + def redirector(conn, _params, code \\ 200) do conn |> put_resp_content_type("text/html") |> send_file(code, index_file_path()) @@ -43,28 +35,33 @@ defmodule Fallback.RedirectController do def redirector_with_meta(conn, params) do {:ok, index_content} = File.read(index_file_path()) - tags = - try do - Metadata.build_tags(params) - rescue - e -> - Logger.error( - "Metadata rendering for #{conn.request_path} failed.\n" <> - Exception.format(:error, e, __STACKTRACE__) - ) - - "" - end + tags = build_tags(conn, params) + preloads = preload_data(conn, params) - response = String.replace(index_content, "<!--server-generated-meta-->", tags) + response = + index_content + |> String.replace("<!--server-generated-meta-->", tags <> preloads) conn |> put_resp_content_type("text/html") |> send_resp(200, response) end - def index_file_path do - Pleroma.Plugs.InstanceStatic.file_path("index.html") + def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do + redirect(conn, to: "/pleroma/admin/") + end + + def redirector_with_preload(conn, params) do + {:ok, index_content} = File.read(index_file_path()) + preloads = preload_data(conn, params) + + response = + index_content + |> String.replace("<!--server-generated-meta-->", preloads) + + conn + |> put_resp_content_type("text/html") + |> send_resp(200, response) end def registration_page(conn, params) do @@ -76,4 +73,36 @@ defmodule Fallback.RedirectController do |> put_status(204) |> text("") end + + defp index_file_path do + Pleroma.Plugs.InstanceStatic.file_path("index.html") + end + + defp build_tags(conn, params) do + try do + Metadata.build_tags(params) + rescue + e -> + Logger.error( + "Metadata rendering for #{conn.request_path} failed.\n" <> + Exception.format(:error, e, __STACKTRACE__) + ) + + "" + end + end + + defp preload_data(conn, params) do + try do + Preload.build_tags(conn, params) + rescue + e -> + Logger.error( + "Preloading for #{conn.request_path} failed.\n" <> + Exception.format(:error, e, __STACKTRACE__) + ) + + "" + end + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7a88a847c..b5008d69b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -385,8 +385,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, _user_block} <- User.block(blocker, blocked), - {:ok, _activity} <- ActivityPub.block(blocker, blocked) do + with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else {:error, message} -> json_response(conn, :forbidden, %{error: message}) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 2c49bedb3..6ee17f4dd 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy - import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1] + import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] # TODO: Add cached version. defp get_replied_to_activities([]), do: %{} @@ -364,7 +364,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do expires_at: expires_at, direct_conversation_id: direct_conversation_id, thread_muted: thread_muted?, - emoji_reactions: emoji_reactions + emoji_reactions: emoji_reactions, + parent_visible: visible_for_user?(reply_to, opts[:for]) } } end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex new file mode 100644 index 000000000..47fa46376 --- /dev/null +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Nodeinfo.Nodeinfo do + alias Pleroma.Config + alias Pleroma.Stats + alias Pleroma.User + alias Pleroma.Web.Federator.Publisher + alias Pleroma.Web.MastodonAPI.InstanceView + + # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field + # under software. + def get_nodeinfo("2.0") do + stats = Stats.get_stats() + + staff_accounts = + User.all_superusers() + |> Enum.map(fn u -> u.ap_id end) + + federation = InstanceView.federation() + features = InstanceView.features() + + %{ + version: "2.0", + software: %{ + name: Pleroma.Application.name() |> String.downcase(), + version: Pleroma.Application.version() + }, + protocols: Publisher.gather_nodeinfo_protocol_names(), + services: %{ + inbound: [], + outbound: [] + }, + openRegistrations: Config.get([:instance, :registrations_open]), + usage: %{ + users: %{ + total: Map.get(stats, :user_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: false + }, + staffAccounts: staff_accounts, + federation: federation, + pollLimits: Config.get([:instance, :poll_limits]), + postFormats: Config.get([:instance, :allowed_post_formats]), + uploadLimits: %{ + general: Config.get([:instance, :upload_limit]), + avatar: Config.get([:instance, :avatar_upload_limit]), + banner: Config.get([:instance, :banner_upload_limit]), + background: Config.get([:instance, :background_upload_limit]) + }, + fieldsLimits: %{ + maxFields: Config.get([:instance, :max_account_fields]), + maxRemoteFields: Config.get([:instance, :max_remote_account_fields]), + nameLength: Config.get([:instance, :account_field_name_length]), + valueLength: Config.get([:instance, :account_field_value_length]) + }, + accountActivationRequired: Config.get([:instance, :account_activation_required], false), + invitesEnabled: Config.get([:instance, :invites_enabled], false), + mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), + features: features, + restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), + skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) + } + } + end + + def get_nodeinfo("2.1") do + raw_response = get_nodeinfo("2.0") + + updated_software = + raw_response + |> Map.get(:software) + |> Map.put(:repository, Pleroma.Application.repository()) + + raw_response + |> Map.put(:software, updated_software) + |> Map.put(:version, "2.1") + end + + def get_nodeinfo(_version) do + {:error, :missing} + end +end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 721b599d4..8c7a9e565 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -5,12 +5,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do use Pleroma.Web, :controller - alias Pleroma.Config - alias Pleroma.Stats - alias Pleroma.User alias Pleroma.Web - alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.MastodonAPI.InstanceView + alias Pleroma.Web.Nodeinfo.Nodeinfo def schemas(conn, _params) do response = %{ @@ -29,102 +25,20 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do json(conn, response) end - # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field - # under software. - def raw_nodeinfo do - stats = Stats.get_stats() - - staff_accounts = - User.all_superusers() - |> Enum.map(fn u -> u.ap_id end) - - features = InstanceView.features() - federation = InstanceView.federation() - - %{ - version: "2.0", - software: %{ - name: Pleroma.Application.name() |> String.downcase(), - version: Pleroma.Application.version() - }, - protocols: Publisher.gather_nodeinfo_protocol_names(), - services: %{ - inbound: [], - outbound: [] - }, - openRegistrations: Config.get([:instance, :registrations_open]), - usage: %{ - users: %{ - total: Map.get(stats, :user_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: false - }, - staffAccounts: staff_accounts, - federation: federation, - pollLimits: Config.get([:instance, :poll_limits]), - postFormats: Config.get([:instance, :allowed_post_formats]), - uploadLimits: %{ - general: Config.get([:instance, :upload_limit]), - avatar: Config.get([:instance, :avatar_upload_limit]), - banner: Config.get([:instance, :banner_upload_limit]), - background: Config.get([:instance, :background_upload_limit]) - }, - fieldsLimits: %{ - maxFields: Config.get([:instance, :max_account_fields]), - maxRemoteFields: Config.get([:instance, :max_remote_account_fields]), - nameLength: Config.get([:instance, :account_field_name_length]), - valueLength: Config.get([:instance, :account_field_value_length]) - }, - accountActivationRequired: Config.get([:instance, :account_activation_required], false), - invitesEnabled: Config.get([:instance, :invites_enabled], false), - mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), - features: features, - restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), - skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) - } - } - end - # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json # and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json - def nodeinfo(conn, %{"version" => "2.0"}) do - conn - |> put_resp_header( - "content-type", - "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" - ) - |> json(raw_nodeinfo()) - end - - def nodeinfo(conn, %{"version" => "2.1"}) do - raw_response = raw_nodeinfo() - - updated_software = - raw_response - |> Map.get(:software) - |> Map.put(:repository, Pleroma.Application.repository()) - - response = - raw_response - |> Map.put(:software, updated_software) - |> Map.put(:version, "2.1") - - conn - |> put_resp_header( - "content-type", - "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8" - ) - |> json(response) - end - - def nodeinfo(conn, _) do - render_error(conn, :not_found, "Nodeinfo schema version not handled") + def nodeinfo(conn, %{"version" => version}) do + case Nodeinfo.get_nodeinfo(version) do + {:error, :missing} -> + render_error(conn, :not_found, "Nodeinfo schema version not handled") + + node_info -> + conn + |> put_resp_header( + "content-type", + "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" + ) + |> json(node_info) + end end end diff --git a/lib/pleroma/web/preload.ex b/lib/pleroma/web/preload.ex new file mode 100644 index 000000000..90e454468 --- /dev/null +++ b/lib/pleroma/web/preload.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload do + alias Phoenix.HTML + require Logger + + def build_tags(_conn, params) do + preload_data = + Enum.reduce(Pleroma.Config.get([__MODULE__, :providers], []), %{}, fn parser, acc -> + terms = + params + |> parser.generate_terms() + |> Enum.map(fn {k, v} -> {k, Base.encode64(Jason.encode!(v))} end) + |> Enum.into(%{}) + + Map.merge(acc, terms) + end) + + rendered_html = + preload_data + |> Jason.encode!() + |> build_script_tag() + |> HTML.safe_to_string() + + rendered_html + end + + def build_script_tag(content) do + HTML.Tag.content_tag(:script, HTML.raw(content), + id: "initial-results", + type: "application/json" + ) + end +end diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/instance.ex new file mode 100644 index 000000000..50d1f3382 --- /dev/null +++ b/lib/pleroma/web/preload/instance.ex @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload.Providers.Instance do + alias Pleroma.Plugs.InstanceStatic + alias Pleroma.Web.MastodonAPI.InstanceView + alias Pleroma.Web.Nodeinfo.Nodeinfo + alias Pleroma.Web.Preload.Providers.Provider + + @behaviour Provider + @instance_url "/api/v1/instance" + @panel_url "/instance/panel.html" + @nodeinfo_url "/nodeinfo/2.0.json" + + @impl Provider + def generate_terms(_params) do + %{} + |> build_info_tag() + |> build_panel_tag() + |> build_nodeinfo_tag() + end + + defp build_info_tag(acc) do + info_data = InstanceView.render("show.json", %{}) + + Map.put(acc, @instance_url, info_data) + end + + defp build_panel_tag(acc) do + instance_path = InstanceStatic.file_path(@panel_url |> to_string()) + + if File.exists?(instance_path) do + panel_data = File.read!(instance_path) + Map.put(acc, @panel_url, panel_data) + else + acc + end + end + + defp build_nodeinfo_tag(acc) do + case Nodeinfo.get_nodeinfo("2.0") do + {:error, _} -> + acc + + nodeinfo_data -> + Map.put(acc, @nodeinfo_url, nodeinfo_data) + end + end +end diff --git a/lib/pleroma/web/preload/provider.ex b/lib/pleroma/web/preload/provider.ex new file mode 100644 index 000000000..7ef595a34 --- /dev/null +++ b/lib/pleroma/web/preload/provider.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload.Providers.Provider do + @callback generate_terms(map()) :: map() +end diff --git a/lib/pleroma/web/preload/status_net.ex b/lib/pleroma/web/preload/status_net.ex new file mode 100644 index 000000000..9b62f87a2 --- /dev/null +++ b/lib/pleroma/web/preload/status_net.ex @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload.Providers.StatusNet do + alias Pleroma.Web.Preload.Providers.Provider + alias Pleroma.Web.TwitterAPI.UtilController + + @behaviour Provider + @config_url "/api/statusnet/config.json" + + @impl Provider + def generate_terms(_params) do + %{} + |> build_config_tag() + end + + defp build_config_tag(acc) do + resp = + Plug.Test.conn(:get, @config_url |> to_string()) + |> UtilController.config(nil) + + Map.put(acc, @config_url, resp.resp_body) + end +end diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/timelines.ex new file mode 100644 index 000000000..57de04051 --- /dev/null +++ b/lib/pleroma/web/preload/timelines.ex @@ -0,0 +1,39 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload.Providers.Timelines do + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Preload.Providers.Provider + + @behaviour Provider + @public_url "/api/v1/timelines/public" + + @impl Provider + def generate_terms(params) do + build_public_tag(%{}, params) + end + + def build_public_tag(acc, params) do + if Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated], true) do + acc + else + Map.put(acc, @public_url, public_timeline(params)) + end + end + + defp public_timeline(%{"path" => ["main", "all"]}), do: get_public_timeline(false) + + defp public_timeline(_params), do: get_public_timeline(true) + + defp get_public_timeline(local_only) do + activities = + ActivityPub.fetch_public_activities(%{ + type: ["Create"], + local_only: local_only + }) + + StatusView.render("index.json", activities: activities, for: nil, as: :activity) + end +end diff --git a/lib/pleroma/web/preload/user.ex b/lib/pleroma/web/preload/user.ex new file mode 100644 index 000000000..b3d2e9b8d --- /dev/null +++ b/lib/pleroma/web/preload/user.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Preload.Providers.User do + alias Pleroma.User + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.Preload.Providers.Provider + + @behaviour Provider + @account_url_base "/api/v1/accounts" + + @impl Provider + def generate_terms(%{user: user}) do + build_accounts_tag(%{}, user) + end + + def generate_terms(_params), do: %{} + + def build_accounts_tag(acc, %User{} = user) do + account_data = AccountView.render("show.json", %{user: user, for: user}) + Map.put(acc, "#{@account_url_base}/#{user.id}", account_data) + end + + def build_accounts_tag(acc, _), do: acc +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 419aa55e4..9e457848e 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -726,7 +726,7 @@ defmodule Pleroma.Web.Router do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) get("/api*path", RedirectController, :api_not_implemented) - get("/*path", RedirectController, :redirector) + get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index fd2aee175..aaca182ec 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.CommonAPI + alias Pleroma.Web.TwitterAPI.UtilView alias Pleroma.Web.WebFinger plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) @@ -90,17 +91,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def config(%{assigns: %{format: "xml"}} = conn, _params) do instance = Pleroma.Config.get(:instance) - - response = """ - <config> - <site> - <name>#{Keyword.get(instance, :name)}</name> - <site>#{Web.base_url()}</site> - <textlimit>#{Keyword.get(instance, :limit)}</textlimit> - <closed>#{!Keyword.get(instance, :registrations_open)}</closed> - </site> - </config> - """ + response = UtilView.status_net_config(instance) conn |> put_resp_content_type("application/xml") diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index 52054e020..d3bdb4f62 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -5,4 +5,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do use Pleroma.Web, :view import Phoenix.HTML.Form + alias Pleroma.Web + + def status_net_config(instance) do + """ + <config> + <site> + <name>#{Keyword.get(instance, :name)}</name> + <site>#{Web.base_url()}</site> + <textlimit>#{Keyword.get(instance, :limit)}</textlimit> + <closed>#{!Keyword.get(instance, :registrations_open)}</closed> + </site> + </config> + """ + end end diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex index c3096006e..f739dacb6 100644 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ b/lib/pleroma/web/views/masto_fe_view.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastoFEView do "video\/mp4" ] }, - settings: user.settings || @default_settings, + settings: user.mastofe_settings || @default_settings, push_subscription: nil, accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)}, custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis), |