diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c672275e..dabc2a85a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** removed `with_move` parameter from notifications timeline. ### Added +- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon. - Instance: Add `background_image` to configuration and `/api/v1/instance` - Instance: Extend `/api/v1/instance` with Pleroma-specific information. - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - NodeInfo: `pleroma_emoji_reactions` to the `features` list. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. - Configuration: Add `:database_config_whitelist` setting to whitelist settings which can be configured from AdminFE. +- Configuration: `filename_display_max_length` option to set filename truncate limit, if filename display enabled (0 = no limit). - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required. - Mix task to create trusted OAuth App. - Notifications: Added `follow_request` notification type. @@ -47,7 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Healthcheck reporting the number of memory currently used, rather than allocated in total -- `InsertSkeletonsForDeletedUsers` failing on some instances +- `InsertSkeletonsForDeletedUsers` failing on some instances ## [2.0.3] - 2020-05-02 diff --git a/config/config.exs b/config/config.exs index 1b11b4fa9..7385fb6c3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -71,7 +71,8 @@ config :pleroma, Pleroma.Upload, follow_redirect: true, pool: :upload ] - ] + ], + filename_display_max_length: 30 config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads" diff --git a/config/description.exs b/config/description.exs index 716bcf4ff..807c945e0 100644 --- a/config/description.exs +++ b/config/description.exs @@ -119,6 +119,11 @@ config :pleroma, :config_description, [ ] } ] + }, + %{ + key: :filename_display_max_length, + type: :integer, + description: "Set max length of a filename to display. 0 = no limit. Default: 30" } ] }, @@ -969,6 +974,13 @@ config :pleroma, :config_description, [ ] } ] + }, + %{ + key: :instance_thumbnail, + type: :string, + description: + "The instance thumbnail image. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html)", + suggestions: ["/instance/thumbnail.jpeg"] } ] }, @@ -1112,7 +1124,7 @@ config :pleroma, :config_description, [ logoMask: true, minimalScopesMode: false, noAttachmentLinks: false, - nsfwCensorImage: "", + nsfwCensorImage: "/static/img/nsfw.74818f9.png", postContentType: "text/plain", redirectRootLogin: "/main/friends", redirectRootNoLogin: "/main/all", @@ -1226,7 +1238,7 @@ config :pleroma, :config_description, [ type: :string, description: "URL of the image to use for hiding NSFW media attachments in the timeline.", - suggestions: ["/static/img/nsfw.png"] + suggestions: ["/static/img/nsfw.74818f9.png"] }, %{ key: :postContentType, @@ -1346,6 +1358,12 @@ config :pleroma, :config_description, [ suggestions: [ :pleroma_fox_tan ] + }, + %{ + key: :default_user_avatar, + type: :string, + description: "URL of the default user avatar.", + suggestions: ["/images/avi.png"] } ] }, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index e8def466e..505acb293 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -498,6 +498,7 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host. * `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it. * `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation. +* `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30. !!! warning `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index bc842a59f..86409738a 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -147,6 +147,7 @@ defmodule Mix.Tasks.Pleroma.Instance do "What directory should media uploads go in (when using the local uploader)?", Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads]) ) + |> Path.expand() static_dir = get_option( @@ -155,6 +156,7 @@ defmodule Mix.Tasks.Pleroma.Instance do "What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?", Pleroma.Config.get([:instance, :static_dir]) ) + |> Path.expand() Config.put([:instance, :static_dir], static_dir) @@ -204,7 +206,7 @@ defmodule Mix.Tasks.Pleroma.Instance do shell_info("Writing the postgres script to #{psql_path}.") File.write(psql_path, result_psql) - write_robots_txt(indexable, template_dir) + write_robots_txt(static_dir, indexable, template_dir) shell_info( "\n All files successfully written! Refer to the installation instructions for your platform for next steps." @@ -224,15 +226,13 @@ defmodule Mix.Tasks.Pleroma.Instance do end end - defp write_robots_txt(indexable, template_dir) do + defp write_robots_txt(static_dir, indexable, template_dir) do robots_txt = EEx.eval_file( template_dir <> "/robots_txt.eex", indexable: indexable ) - static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") - unless File.exists?(static_dir) do File.mkdir_p!(static_dir) end diff --git a/lib/pleroma/emails/new_users_digest_email.ex b/lib/pleroma/emails/new_users_digest_email.ex index 7d16b807f..348cbac9c 100644 --- a/lib/pleroma/emails/new_users_digest_email.ex +++ b/lib/pleroma/emails/new_users_digest_email.ex @@ -14,8 +14,10 @@ defmodule Pleroma.Emails.NewUsersDigestEmail do styling = Pleroma.Config.get([Pleroma.Emails.UserEmail, :styling]) logo_url = - Pleroma.Web.Endpoint.url() <> - Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo]) + Pleroma.Helpers.UriHelper.maybe_add_base( + Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo]), + Pleroma.Web.Endpoint.url() + ) new() |> to({to.name, to.email}) diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex index 256252ddb..69d8c8fe0 100644 --- a/lib/pleroma/helpers/uri_helper.ex +++ b/lib/pleroma/helpers/uri_helper.ex @@ -24,4 +24,7 @@ defmodule Pleroma.Helpers.UriHelper do params end end + + def maybe_add_base("/" <> uri, base), do: Path.join([base, uri]) + def maybe_add_base(uri, _base), do: uri end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 224dfd023..d2eeeb479 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -305,8 +305,13 @@ defmodule Pleroma.User do def avatar_url(user, options \\ []) do case user.avatar do - %{"url" => [%{"href" => href} | _]} -> href - _ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png" + %{"url" => [%{"href" => href} | _]} -> + href + + _ -> + unless options[:no_default] do + Config.get([:assets, :default_user_avatar], "#{Web.base_url()}/images/avi.png") + end end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index db2499b88..f325154da 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -363,36 +363,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do 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, - activity_id \\ nil, - 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), - object <- Object.get_by_id(object.id), - announce_data <- make_announce_data(user, object, activity_id, public), - {:ok, activity} <- insert(announce_data, local), - {:ok, object} <- add_announce_to_object(activity, object), - _ <- notify_and_stream(activity), - :ok <- maybe_federate(activity) do - {:ok, activity, object} - else - false -> {:error, false} - {: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 diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 62ad15d85..28727d619 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.Endpoint alias Pleroma.Web.FederatingPlug alias Pleroma.Web.Federator @@ -75,8 +76,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def object(conn, %{"uuid" => uuid}) do - with ap_id <- o_status_url(conn, :object, uuid), + def object(conn, _) do + with ap_id <- Endpoint.url() <> conn.request_path, %Object{} = object <- Object.get_cached_by_ap_id(ap_id), {_, true} <- {:public?, Visibility.is_public?(object)} do conn @@ -101,8 +102,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn end - def activity(conn, %{"uuid" => uuid}) do - with ap_id <- o_status_url(conn, :activity, uuid), + def activity(conn, _params) do + with ap_id <- Endpoint.url() <> conn.request_path, %Activity{} = activity <- Activity.normalize(ap_id), {_, true} <- {:public?, Visibility.is_public?(activity)} do conn diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 0107a8baa..a40054921 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility + require Pleroma.Constants + @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()} def emoji_react(actor, object, emoji) do with {:ok, data, meta} <- object_action(actor, object) do @@ -120,6 +122,29 @@ defmodule Pleroma.Web.ActivityPub.Builder do end end + def announce(actor, object, options \\ []) do + public? = Keyword.get(options, :public, false) + to = [actor.follower_address, object.data["actor"]] + + to = + if public? do + [Pleroma.Constants.as_public() | to] + else + to + end + + {:ok, + %{ + "id" => Utils.generate_activity_id(), + "actor" => actor.ap_id, + "object" => object.data["id"], + "to" => to, + "context" => object.data["context"], + "type" => "Announce", + "published" => Utils.make_date() + }, []} + end + @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()} defp object_action(actor, object) do object_actor = User.get_cached_by_ap_id(object.data["actor"]) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 7f1e0171c..c01c5f780 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator @@ -84,6 +85,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end + def validate(%{"type" => "Announce"} = object, meta) do + with {:ok, object} <- + object + |> AnnounceValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object |> Map.from_struct()) + {:ok, object, meta} + end + end + def cast_and_apply(%{"type" => "ChatMessage"} = object) do ChatMessageValidator.cast_and_apply(object) end @@ -116,7 +127,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def fetch_actor_and_object(object) do fetch_actor(object) - Object.normalize(object["object"]) + Object.normalize(object["object"], true) :ok end end diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex new file mode 100644 index 000000000..40f861f47 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex @@ -0,0 +1,101 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do + use Ecto.Schema + + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility + + import Ecto.Changeset + import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + + require Pleroma.Constants + + @primary_key false + + embedded_schema do + field(:id, Types.ObjectID, primary_key: true) + field(:type, :string) + field(:object, Types.ObjectID) + field(:actor, Types.ObjectID) + field(:context, :string, autogenerate: {Utils, :generate_context_id, []}) + field(:to, Types.Recipients, default: []) + field(:cc, Types.Recipients, default: []) + field(:published, Types.DateTime) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + |> fix_after_cast() + end + + def fix_after_cast(cng) do + cng + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Announce"]) + |> validate_required([:id, :type, :object, :actor, :to, :cc]) + |> validate_actor_presence() + |> validate_object_presence() + |> validate_existing_announce() + |> validate_announcable() + end + + def validate_announcable(cng) do + with actor when is_binary(actor) <- get_field(cng, :actor), + object when is_binary(object) <- get_field(cng, :object), + %User{} = actor <- User.get_cached_by_ap_id(actor), + %Object{} = object <- Object.get_cached_by_ap_id(object), + false <- Visibility.is_public?(object) do + same_actor = object.data["actor"] == actor.ap_id + is_public = Pleroma.Constants.as_public() in (get_field(cng, :to) ++ get_field(cng, :cc)) + + cond do + same_actor && is_public -> + cng + |> add_error(:actor, "can not announce this object publicly") + + !same_actor -> + cng + |> add_error(:actor, "can not announce this object") + + true -> + cng + end + else + _ -> cng + end + end + + def validate_existing_announce(cng) do + actor = get_field(cng, :actor) + object = get_field(cng, :object) + + if actor && object && Utils.get_existing_announce(actor, %{data: %{"id" => object}}) do + cng + |> add_error(:actor, "already announced this object") + |> add_error(:object, "already announced by this actor") + else + cng + end + end +end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 657cdfdb1..0c54c4b23 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Object alias Pleroma.Repo alias Pleroma.Web.ActivityPub.ActivityPub @@ -44,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do defp maybe_federate(%Activity{} = activity, meta) do with {:ok, local} <- Keyword.fetch(meta, :local) do - do_not_federate = meta[:do_not_federate] + do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating]) if !do_not_federate && local do Federator.publish(activity) diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index 729c23af7..484178edd 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -4,9 +4,10 @@ defmodule Pleroma.Web.ActivityPub.Relay do alias Pleroma.Activity - alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI require Logger @relay_nickname "relay" @@ -48,11 +49,11 @@ defmodule Pleroma.Web.ActivityPub.Relay do end end - @spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()} + @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()} def publish(%Activity{data: %{"type" => "Create"}} = activity) do with %User{} = user <- get_actor(), - %Object{} = object <- Object.normalize(activity) do - ActivityPub.announce(user, object, nil, true, false) + true <- Visibility.is_public?(activity) do + CommonAPI.repeat(activity.id, user) else error -> format_error(error) end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 8e64b4615..f0f0659c2 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -42,6 +42,21 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end end + # Tasks this handles: + # - Add announce to object + # - Set up notification + # - Stream out the announce + def handle(%{data: %{"type" => "Announce"}} = object, meta) do + announced_object = Object.get_by_ap_id(object.data["object"]) + + Utils.add_announce_to_object(object, announced_object) + + Notification.create_notifications(object) + ActivityPub.stream_out(object) + + {:ok, object, meta} + end + def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do with undone_object <- Activity.get_by_ap_id(undone_object), :ok <- handle_undoing(undone_object) do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index afc63d6b7..a7f33c332 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -672,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def handle_incoming(%{"type" => type} = data, _options) when type in ["Like", "EmojiReact"] do + def handle_incoming(%{"type" => type} = data, _options) + when type in ["Like", "EmojiReact", "Announce"] do with :ok <- ObjectValidator.fetch_actor_and_object(data), {:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do @@ -683,21 +684,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, - _options - ) do - with actor <- Containment.get_actor(data), - {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_embedded_obj_helper(object_id, actor), - public <- Visibility.is_public?(data), - {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do - {:ok, activity} - else - _e -> :error - end - end - - def handle_incoming( %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} = data, _options diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex similarity index 89% rename from lib/pleroma/web/admin_api/admin_api_controller.ex rename to lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 647ceb3ba..6b1d64a2e 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -98,13 +98,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} - when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:statuses"], admin: true} - when action in [:status_update, :status_delete] + when action in [:list_user_statuses, :list_instance_statuses] ) plug( @@ -136,7 +130,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ] ) - action_fallback(:errors) + action_fallback(AdminAPI.FallbackController) def user_delete(conn, %{"nickname" => nickname}) do user_delete(conn, %{"nicknames" => [nickname]}) @@ -597,16 +591,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do json_response(conn, :no_content, "") else {:registrations_open, _} -> - errors( - conn, - {:error, "To send invites you need to set the `registrations_open` option to false."} - ) + {:error, "To send invites you need to set the `registrations_open` option to false."} {:invites_enabled, _} -> - errors( - conn, - {:error, "To send invites you need to set the `invites_enabled` option to true."} - ) + {:error, "To send invites you need to set the `invites_enabled` option to true."} end end @@ -814,71 +802,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do 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(AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) - end - - def status_show(conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id) do - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - else - _ -> errors(conn, {:error, :not_found}) - end - end - - def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do - params = - params - |> Map.take(["sensitive", "visibility"]) - |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) - - with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do - {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) - - ModerationLog.insert_log(%{ - action: "status_update", - actor: admin, - subject: activity, - sensitive: sensitive, - visibility: params[:visibility] - }) - - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - end - end - - def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do - ModerationLog.insert_log(%{ - action: "status_delete", - actor: user, - subject_id: id - }) - - json(conn, %{}) - end - end - def list_log(conn, params) do {page, page_size} = page_params(params) @@ -904,7 +827,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def config_show(conn, %{"only_db" => true}) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do configs = Pleroma.Repo.all(ConfigDB) conn @@ -914,7 +837,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def config_show(conn, _params) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do configs = ConfigDB.get_all_as_keyword() merged = @@ -953,7 +876,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def config_update(conn, %{"configs" => configs}) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do {_errors, results} = configs |> Enum.filter(&whitelisted_config?/1) @@ -997,7 +920,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def restart(conn, _params) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do Restarter.Pleroma.restart(Config.get(:env), 50) json(conn, %{}) @@ -1008,14 +931,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()}) end - defp configurable_from_database(conn) do + defp configurable_from_database do if Config.get(:configurable_from_database) do :ok else - errors( - conn, - {:error, "To use this endpoint you need to enable configuration from database."} - ) + {:error, "To use this endpoint you need to enable configuration from database."} end end @@ -1159,30 +1079,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> json(%{"status_visibility" => count}) end - defp errors(conn, {:error, :not_found}) do - conn - |> put_status(:not_found) - |> json(dgettext("errors", "Not found")) - end - - defp errors(conn, {:error, reason}) do - conn - |> put_status(:bad_request) - |> json(reason) - end - - defp errors(conn, {:param_cast, _}) do - conn - |> put_status(:bad_request) - |> json(dgettext("errors", "Invalid parameters")) - end - - defp errors(conn, _) do - conn - |> put_status(:internal_server_error) - |> json(dgettext("errors", "Something went wrong")) - end - defp page_params(params) do {get_page(params["page"]), get_page_size(params["page_size"])} end diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex new file mode 100644 index 000000000..82965936d --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.FallbackController do + use Pleroma.Web, :controller + + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> json(%{error: dgettext("errors", "Not found")}) + end + + def call(conn, {:error, reason}) do + conn + |> put_status(:bad_request) + |> json(%{error: reason}) + end + + def call(conn, {:param_cast, _}) do + conn + |> put_status(:bad_request) + |> json(dgettext("errors", "Invalid parameters")) + end + + def call(conn, _) do + conn + |> put_status(:internal_server_error) + |> json(%{error: dgettext("errors", "Something went wrong")}) + end +end diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex new file mode 100644 index 000000000..08cb9c10b --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -0,0 +1,79 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.ModerationLog + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} when action in [:index, :show]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:statuses"], admin: true} when action in [:update, :delete] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.StatusOperation + + def index(%{assigns: %{user: _admin}} = conn, params) do + activities = + ActivityPub.fetch_statuses(nil, %{ + "godmode" => params.godmode, + "local_only" => params.local_only, + "limit" => params.page_size, + "offset" => (params.page - 1) * params.page_size, + "exclude_reblogs" => not params.with_reblogs + }) + + render(conn, "index.json", activities: activities, as: :activity) + end + + def show(conn, %{id: id}) do + with %Activity{} = activity <- Activity.get_by_id(id) do + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + else + nil -> {:error, :not_found} + end + end + + def update(%{assigns: %{user: admin}, body_params: params} = conn, %{id: id}) do + with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do + ModerationLog.insert_log(%{ + action: "status_update", + actor: admin, + subject: activity, + sensitive: params[:sensitive], + visibility: params[:visibility] + }) + + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + end + end + + def delete(%{assigns: %{user: user}} = conn, %{id: id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + ModerationLog.insert_log(%{ + action: "status_delete", + actor: user, + subject_id: id + }) + + json(conn, %{}) + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex new file mode 100644 index 000000000..0b138dc79 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex @@ -0,0 +1,165 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + import Pleroma.Web.ApiSpec.Helpers + import Pleroma.Web.ApiSpec.StatusOperation, only: [id_param: 0] + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Admin", "Statuses"], + operationId: "AdminAPI.StatusController.index", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [ + Operation.parameter( + :godmode, + :query, + %Schema{type: :boolean, default: false}, + "Allows to see private statuses" + ), + Operation.parameter( + :local_only, + :query, + %Schema{type: :boolean, default: false}, + "Excludes remote statuses" + ), + Operation.parameter( + :with_reblogs, + :query, + %Schema{type: :boolean, default: false}, + "Allows to see reblogs" + ), + Operation.parameter( + :page, + :query, + %Schema{type: :integer, default: 1}, + "Page" + ), + Operation.parameter( + :page_size, + :query, + %Schema{type: :integer, default: 50}, + "Number of statuses to return" + ) + ], + responses: %{ + 200 => + Operation.response("Array of statuses", "application/json", %Schema{ + type: :array, + items: status() + }) + } + } + end + + def show_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Show Status", + operationId: "AdminAPI.StatusController.show", + parameters: [id_param()], + security: [%{"oAuth" => ["read:statuses"]}], + responses: %{ + 200 => Operation.response("Status", "application/json", Status), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Change the scope of an individual reported status", + operationId: "AdminAPI.StatusController.update", + parameters: [id_param()], + security: [%{"oAuth" => ["write:statuses"]}], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Status", "application/json", Status), + 400 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Delete an individual reported status", + operationId: "AdminAPI.StatusController.delete", + parameters: [id_param()], + security: [%{"oAuth" => ["write:statuses"]}], + responses: %{ + 200 => empty_object_response(), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp status do + %Schema{ + anyOf: [ + Status, + %Schema{ + type: :object, + properties: %{ + account: %Schema{allOf: [Account, admin_account()]} + } + } + ] + } + end + + defp admin_account do + %Schema{ + type: :object, + properties: %{ + id: FlakeID, + avatar: %Schema{type: :string}, + nickname: %Schema{type: :string}, + display_name: %Schema{type: :string}, + deactivated: %Schema{type: :boolean}, + local: %Schema{type: :boolean}, + roles: %Schema{ + type: :object, + properties: %{ + admin: %Schema{type: :boolean}, + moderator: %Schema{type: :boolean} + } + }, + tags: %Schema{type: :string}, + confirmation_pending: %Schema{type: :string} + } + } + end + + defp update_request do + %Schema{ + type: :object, + properties: %{ + sensitive: %Schema{ + type: :boolean, + description: "Mark status and attached media as sensitive?" + }, + visibility: VisibilityScope + }, + example: %{ + "visibility" => "private", + "sensitive" => "false" + } + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index 7c08fbaa7..1a49fece0 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Status @@ -46,7 +47,8 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do security: [%{"oAuth" => ["write:statuses"]}], operationId: "EmojiReactionController.create", responses: %{ - 200 => Operation.response("Status", "application/json", Status) + 200 => Operation.response("Status", "application/json", Status), + 400 => Operation.response("Bad Request", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex index 636c39a15..b0c8db863 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do alias Pleroma.Web.ApiSpec.NotificationOperation alias Pleroma.Web.ApiSpec.Schemas.ApiError + import Pleroma.Web.ApiSpec.Helpers + def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") apply(__MODULE__, operation, []) @@ -17,10 +19,14 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do %Operation{ tags: ["Notifications"], summary: "Mark notifications as read. Query parameters are mutually exclusive.", - parameters: [ - Operation.parameter(:id, :query, :string, "A single notification ID to read"), - Operation.parameter(:max_id, :query, :string, "Read all notifications up to this id") - ], + requestBody: + request_body("Parameters", %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer, description: "A single notification ID to read"}, + max_id: %Schema{type: :integer, description: "Read all notifications up to this ID"} + } + }), security: [%{"oAuth" => ["write:notifications"]}], operationId: "PleromaAPI.NotificationController.mark_as_read", responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 0682ca6e5..ca9db01e5 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -487,7 +487,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } end - defp id_param do + def id_param do Operation.parameter(:id, :path, FlakeID, "Status ID", example: "9umDrYheeY451cQnEe", required: true diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 7a2c0a96d..c08edbc5f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -167,18 +167,19 @@ defmodule Pleroma.Web.CommonAPI do end def repeat(id, user, params \\ %{}) do - with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id) do - object = Object.normalize(activity) - announce_activity = Utils.get_existing_announce(user.ap_id, object) - public = public_announce?(object, params) - - if announce_activity do - {:ok, announce_activity, object} - else - ActivityPub.announce(user, object, nil, true, public) - end + with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), + object = %Object{} <- Object.normalize(activity, false), + {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)}, + public = public_announce?(object, params), + {:ok, announce, _} <- Builder.announce(user, object, public: public), + {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do + {:ok, activity} else - _ -> {:error, :not_found} + {:existing_announce, %Activity{} = announce} -> + {:ok, announce} + + _ -> + {:error, :not_found} end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index ef7a9d967..15594125f 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -102,7 +102,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do - if inReplyTo do + # If the OP is a DM already, add the implicit actor. + if inReplyTo && Visibility.is_direct?(inReplyTo) do {Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []} else {mentioned_users, []} @@ -395,10 +396,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do def to_masto_date(_), do: "" defp shortname(name) do - if String.length(name) < 30 do - name + with max_length when max_length > 0 <- + Config.get([Pleroma.Upload, :filename_display_max_length], 30), + true <- String.length(name) > max_length do + String.slice(name, 0..max_length) <> "…" else - String.slice(name, 0..30) <> "…" + _ -> name end end @@ -467,6 +470,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.map(& &1.ap_id) recipients ++ subscriber_ids + else + _e -> recipients end end @@ -478,6 +483,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> User.get_followers() |> Enum.map(& &1.ap_id) |> Enum.concat(recipients) + else + _e -> recipients end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 9dbf4f33c..83d997abd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -210,7 +210,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "POST /api/v1/statuses/:id/reblog" def reblog(%{assigns: %{user: user}, body_params: params} = conn, %{id: ap_id_or_id}) do - with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params), + with {:ok, announce} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 98050487a..c498fe632 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do streaming_api: Pleroma.Web.Endpoint.websocket_url() }, stats: Pleroma.Stats.get_stats(), - thumbnail: Pleroma.Web.base_url() <> "/instance/thumbnail.jpeg", + thumbnail: instance_thumbnail(), languages: ["en"], registrations: Keyword.get(instance, :registrations_open), # Extra (not present in Mastodon): @@ -88,4 +88,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do end |> Map.put(:enabled, Config.get([:instance, :federating])) end + + defp instance_thumbnail do + Pleroma.Config.get([:instance, :instance_thumbnail]) || + "#{Pleroma.Web.base_url()}/instance/thumbnail.jpeg" + end end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 6971cd9f8..de1b0b3f0 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -32,13 +32,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do action_fallback(:errors) - def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid}) + def object(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do ActivityPubController.call(conn, :object) end - def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do - with id <- o_status_url(conn, :object, uuid), + def object(%{assigns: %{format: format}} = conn, _params) do + with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)} do @@ -54,13 +54,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end end - def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid}) + def activity(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do ActivityPubController.call(conn, :activity) end - def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do - with id <- o_status_url(conn, :activity, uuid), + def activity(%{assigns: %{format: format}} = conn, _params) do + with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)} do case format do diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index a002912f3..19dcffdf3 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -22,6 +22,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.EmojiReactionOperation + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), %Object{data: %{"reactions" => reactions}} when is_list(reactions) <- diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex index 0b2f678c5..3ed8bd294 100644 --- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation - def mark_as_read(%{assigns: %{user: user}} = conn, %{id: notification_id}) do + def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do with {:ok, notification} <- Notification.read_one(user, notification_id) do render(conn, "show.json", notification: notification, for: user) else @@ -25,7 +25,7 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do end end - def mark_as_read(%{assigns: %{user: user}} = conn, %{max_id: max_id}) do + def mark_as_read(%{assigns: %{user: user}, body_params: %{max_id: max_id}} = conn, _) do notifications = user |> Notification.set_read_up_to(max_id) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index cd742a032..fef277ac6 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,10 +189,10 @@ defmodule Pleroma.Web.Router do post("/reports/:id/notes", AdminAPIController, :report_notes_create) delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) - get("/statuses/:id", AdminAPIController, :status_show) - put("/statuses/:id", AdminAPIController, :status_update) - delete("/statuses/:id", AdminAPIController, :status_delete) - get("/statuses", AdminAPIController, :list_statuses) + get("/statuses/:id", StatusController, :show) + put("/statuses/:id", StatusController, :update) + delete("/statuses/:id", StatusController, :delete) + get("/statuses", StatusController, :index) get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) @@ -564,6 +564,10 @@ defmodule Pleroma.Web.Router do get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) + # Mastodon compatibility routes + get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object) + get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity) + get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) diff --git a/priv/static/READ_THIS_BEFORE_TOUCHING_FILES_HERE b/priv/static/READ_THIS_BEFORE_TOUCHING_FILES_HERE new file mode 100644 index 000000000..eb5294eaf --- /dev/null +++ b/priv/static/READ_THIS_BEFORE_TOUCHING_FILES_HERE @@ -0,0 +1 @@ +If you are an instance admin and you want to modify the instace static files, this is probably not the right place to do it. This directory is checked in version control, so don't be surprised if you get merge conflicts after modifying anything here. Please use instance static directory instead, it has the same directory structure and files placed there will override files placed here. See https://docs.pleroma.social/backend/configuration/static_dir/ for more info diff --git a/test/fixtures/kroeg-announce-with-inline-actor.json b/test/fixtures/kroeg-announce-with-inline-actor.json index 7bd6e8199..f73f93410 100644 --- a/test/fixtures/kroeg-announce-with-inline-actor.json +++ b/test/fixtures/kroeg-announce-with-inline-actor.json @@ -1 +1,88 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://puckipedia.com/-/context"],"actor":{"endpoints":"https://puckipedia.com/#endpoints","followers":"https://puckipedia.com/followers","following":"https://puckipedia.com/following","icon":{"mediaType":"image/png","type":"Image","url":"https://puckipedia.com/images/avatar.png"},"id":"https://puckipedia.com/","inbox":"https://puckipedia.com/inbox","kroeg:blocks":{"id":"https://puckipedia.com/blocks"},"liked":"https://puckipedia.com/liked","manuallyApprovesFollowers":false,"name":"HACKER TEEN PUCKIPEDIA 👩‍💻","outbox":"https://puckipedia.com/outbox","preferredUsername":"puckipedia","publicKey":{"id":"https://puckipedia.com/#key","owner":"https://puckipedia.com/","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvN05xIcFE0Qgany7Rht4\n0ZI5wu++IT7K5iSqRimBYkpoeHbVcT9RFlW+aWH/QJJW/YgZ7+LMr8AMCrKrwSpS\nCndyrpx4O4lZ3FNRLu7tbklh01rGZfE6R1SFfYBpvMvImc9nYT6iezYDbv6NkHku\no3aVhjql216XlA0OhIrqQme9sAdrLbjbMrTUS8douCTkDOX+JFj1ghHCqdYEMZJI\nOY9kovtgnqyxFLm0RsPGsO1+g/OVojqG+VqHz6O2lceaTVQLlnZ4gOhLVG1tVsA2\nRfXQK+R/VgXncYE+BlQVd/tcdGAz7CDL7PP3rP65gmARnafhGR96cCOi/KzlAXSO\nMwIDAQAB\n-----END PUBLIC KEY-----","type":[]},"summary":"

federated hacker teen
\n[she/they]

","type":"Person","updated":"2017-12-19T16:56:29.7576707+00:00"},"cc":"http://mastodon.example.org/users/admin","id":"https://puckipedia.com/cc56a9658e","object":{"as:sensitive":false,"attributedTo":{"endpoints":{"sharedInbox":"https://mastodon.social/inbox","type":[]},"followers":"http://mastodon.example.org/users/admin/followers","following":"http://mastodon.example.org/users/admin/following","icon":{"mediaType":"image/png","type":"Image","url":"https://files.mastodon.social/accounts/avatars/000/015/163/original/70ca6c52b01ca913.png"},"id":"http://mastodon.example.org/users/admin","inbox":"http://mastodon.example.org/users/admin/inbox","manuallyApprovesFollowers":{"@value":"False","type":"xsd:boolean"},"name":"","outbox":"http://mastodon.example.org/users/admin/outbox","preferredUsername":"revenant","publicKey":{"id":"http://mastodon.example.org/users/admin#main-key","owner":"http://mastodon.example.org/users/admin","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gEN3wPW7gkE2gQqnmfB\n1ychjmFIf2LIwY0oCJLiGE/xpZrUKoq+eWH30AP7mATw4LD0gOYABL/ijqPUrPqR\nDXLL+0CqMP8HsZKvRlj9KArMK3YtNiSGGj2U7iReiRrD7nJzjJlsjjJXflLZhZ7/\nenSv1CcaeK8tB0PoAgShy/MyfhPF7WI5/Zm9DmmDQFvUEnDYKXAf/vG/IWw1EyMC\nkbaEYJeIowQU3GsbPxzRGI22bQtfotm431Ch2MbNo+kyzmYVFLAVoSGNMzvJwOPg\nTxLIIBeQXG7MinRyK887yPKhxhcALea4yCcALaa+3jPE7yqwIKYwTHtSlblsHDAo\nmQIDAQAB\n-----END PUBLIC KEY-----\n","type":[]},"summary":"

neatly partitioned meats and cheeses appeal to me on an aesthetic level | any pronouns | revenant1.net

","type":"Person","url":"https://mastodon.social/@revenant"},"cc":"http://mastodon.example.org/users/admin/followers","content":"

the name's jond (jeans bond)

","contentMap":{"en":"

the name's jond (jeans bond)

"},"conversation":"tag:mastodon.social,2018-09-25:objectId=55659382:objectType=Conversation","id":"http://mastodon.example.org/users/admin/statuses/100787282858396771","ostatus:atomUri":"http://mastodon.example.org/users/admin/statuses/100787282858396771","published":"2018-09-25T16:11:29Z","to":"https://www.w3.org/ns/activitystreams#Public","type":"Note","url":"https://mastodon.social/@revenant/100787282858396771"},"to":["https://www.w3.org/ns/activitystreams#Public","https://puckipedia.com/followers"],"type":"Announce"} +{ + "@context" : [ + "https://www.w3.org/ns/activitystreams", + "https://puckipedia.com/-/context" + ], + "actor" : { + "endpoints" : "https://puckipedia.com/#endpoints", + "followers" : "https://puckipedia.com/followers", + "following" : "https://puckipedia.com/following", + "icon" : { + "mediaType" : "image/png", + "type" : "Image", + "url" : "https://puckipedia.com/images/avatar.png" + }, + "id" : "https://puckipedia.com/", + "inbox" : "https://puckipedia.com/inbox", + "kroeg:blocks" : { + "id" : "https://puckipedia.com/blocks" + }, + "liked" : "https://puckipedia.com/liked", + "manuallyApprovesFollowers" : false, + "name" : "HACKER TEEN PUCKIPEDIA 👩‍💻", + "outbox" : "https://puckipedia.com/outbox", + "preferredUsername" : "puckipedia", + "publicKey" : { + "id" : "https://puckipedia.com/#key", + "owner" : "https://puckipedia.com/", + "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvN05xIcFE0Qgany7Rht4\n0ZI5wu++IT7K5iSqRimBYkpoeHbVcT9RFlW+aWH/QJJW/YgZ7+LMr8AMCrKrwSpS\nCndyrpx4O4lZ3FNRLu7tbklh01rGZfE6R1SFfYBpvMvImc9nYT6iezYDbv6NkHku\no3aVhjql216XlA0OhIrqQme9sAdrLbjbMrTUS8douCTkDOX+JFj1ghHCqdYEMZJI\nOY9kovtgnqyxFLm0RsPGsO1+g/OVojqG+VqHz6O2lceaTVQLlnZ4gOhLVG1tVsA2\nRfXQK+R/VgXncYE+BlQVd/tcdGAz7CDL7PP3rP65gmARnafhGR96cCOi/KzlAXSO\nMwIDAQAB\n-----END PUBLIC KEY-----", + "type" : [] + }, + "summary" : "

federated hacker teen
\n[she/they]

", + "type" : "Person", + "updated" : "2017-12-19T16:56:29.7576707+00:00" + }, + "cc" : "http://mastodon.example.org/users/admin", + "id" : "https://puckipedia.com/cc56a9658e", + "object" : { + "as:sensitive" : false, + "attributedTo" : { + "endpoints" : { + "sharedInbox" : "https://mastodon.social/inbox", + "type" : [] + }, + "followers" : "http://mastodon.example.org/users/admin/followers", + "following" : "http://mastodon.example.org/users/admin/following", + "icon" : { + "mediaType" : "image/png", + "type" : "Image", + "url" : "https://files.mastodon.social/accounts/avatars/000/015/163/original/70ca6c52b01ca913.png" + }, + "id" : "http://mastodon.example.org/users/admin", + "inbox" : "http://mastodon.example.org/users/admin/inbox", + "manuallyApprovesFollowers" : { + "@value" : "False", + "type" : "xsd:boolean" + }, + "name" : "", + "outbox" : "http://mastodon.example.org/users/admin/outbox", + "preferredUsername" : "revenant", + "publicKey" : { + "id" : "http://mastodon.example.org/users/admin#main-key", + "owner" : "http://mastodon.example.org/users/admin", + "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gEN3wPW7gkE2gQqnmfB\n1ychjmFIf2LIwY0oCJLiGE/xpZrUKoq+eWH30AP7mATw4LD0gOYABL/ijqPUrPqR\nDXLL+0CqMP8HsZKvRlj9KArMK3YtNiSGGj2U7iReiRrD7nJzjJlsjjJXflLZhZ7/\nenSv1CcaeK8tB0PoAgShy/MyfhPF7WI5/Zm9DmmDQFvUEnDYKXAf/vG/IWw1EyMC\nkbaEYJeIowQU3GsbPxzRGI22bQtfotm431Ch2MbNo+kyzmYVFLAVoSGNMzvJwOPg\nTxLIIBeQXG7MinRyK887yPKhxhcALea4yCcALaa+3jPE7yqwIKYwTHtSlblsHDAo\nmQIDAQAB\n-----END PUBLIC KEY-----\n", + "type" : [] + }, + "summary" : "

neatly partitioned meats and cheeses appeal to me on an aesthetic level | any pronouns | revenant1.net

", + "type" : "Person", + "url" : "https://mastodon.social/@revenant" + }, + "cc" : "http://mastodon.example.org/users/admin/followers", + "content" : "

the name's jond (jeans bond)

", + "contentMap" : { + "en" : "

the name's jond (jeans bond)

" + }, + "conversation" : "tag:mastodon.social,2018-09-25:objectId=55659382:objectType=Conversation", + "id" : "http://mastodon.example.org/users/admin/statuses/100787282858396771", + "ostatus:atomUri" : "http://mastodon.example.org/users/admin/statuses/100787282858396771", + "published" : "2018-09-25T16:11:29Z", + "to" : "https://www.w3.org/ns/activitystreams#Public", + "type" : "Note", + "url" : "https://mastodon.social/@revenant/100787282858396771" + }, + "to" : [ + "https://www.w3.org/ns/activitystreams#Public", + "https://puckipedia.com/followers" + ], + "type" : "Announce" +} diff --git a/test/fixtures/mastodon-note-object.json b/test/fixtures/mastodon-note-object.json index 75bed9625..d28c7fbe9 100644 --- a/test/fixtures/mastodon-note-object.json +++ b/test/fixtures/mastodon-note-object.json @@ -1,9 +1,45 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"http://mastodon.example.org/users/admin/statuses/99541947525187367","type":"Note","summary":null,"content":"\u003cp\u003eyeah.\u003c/p\u003e","inReplyTo":null,"published":"2018-02-17T17:46:20Z","url":"http://mastodon.example.org/@admin/99541947525187367","attributedTo":"http://mastodon.example.org/users/admin","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["http://mastodon.example.org/users/admin/followers"],"sensitive":false,"atomUri":"http://mastodon.example.org/users/admin/statuses/99541947525187367","inReplyToAtomUri":null,"conversation":"tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation","tag":[], - "attachment": [ +{ + "@context" : [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", { - "url": "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg", - "type": "Document", - "name": null, - "mediaType": "image/jpeg" + "Emoji" : "toot:Emoji", + "Hashtag" : "as:Hashtag", + "atomUri" : "ostatus:atomUri", + "conversation" : "ostatus:conversation", + "inReplyToAtomUri" : "ostatus:inReplyToAtomUri", + "manuallyApprovesFollowers" : "as:manuallyApprovesFollowers", + "movedTo" : "as:movedTo", + "ostatus" : "http://ostatus.org#", + "sensitive" : "as:sensitive", + "toot" : "http://joinmastodon.org/ns#" } - ]} + ], + "atomUri" : "http://mastodon.example.org/users/admin/statuses/99541947525187367", + "attachment" : [ + { + "mediaType" : "image/jpeg", + "name" : null, + "type" : "Document", + "url" : "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg" + } + ], + "attributedTo" : "http://mastodon.example.org/users/admin", + "cc" : [ + "http://mastodon.example.org/users/admin/followers" + ], + "content" : "

yeah.

", + "conversation" : "tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation", + "id" : "http://mastodon.example.org/users/admin/statuses/99541947525187367", + "inReplyTo" : null, + "inReplyToAtomUri" : null, + "published" : "2018-02-17T17:46:20Z", + "sensitive" : false, + "summary" : null, + "tag" : [], + "to" : [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type" : "Note", + "url" : "http://mastodon.example.org/@admin/99541947525187367" +} diff --git a/test/notification_test.exs b/test/notification_test.exs index 111ff09f4..3a96721fa 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -648,7 +648,7 @@ defmodule Pleroma.NotificationTest do status: "hey @#{other_user.nickname}!" }) - {:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user) + {:ok, activity_two} = CommonAPI.repeat(activity_one.id, third_user) {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity_two) @@ -778,7 +778,7 @@ defmodule Pleroma.NotificationTest do assert Enum.empty?(Notification.for_user(user)) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, _} = CommonAPI.repeat(activity.id, other_user) assert length(Notification.for_user(user)) == 1 @@ -795,7 +795,7 @@ defmodule Pleroma.NotificationTest do assert Enum.empty?(Notification.for_user(user)) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, _} = CommonAPI.repeat(activity.id, other_user) assert length(Notification.for_user(user)) == 1 diff --git a/test/tasks/instance_test.exs b/test/tasks/instance_test.exs index f6a4ba508..3b4c041d9 100644 --- a/test/tasks/instance_test.exs +++ b/test/tasks/instance_test.exs @@ -63,7 +63,7 @@ defmodule Pleroma.InstanceTest do "--uploads-dir", "test/uploads", "--static-dir", - "instance/static/" + "./test/../test/instance/static/" ]) end @@ -83,6 +83,7 @@ defmodule Pleroma.InstanceTest do assert generated_config =~ "configurable_from_database: true" assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]" assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql() + assert File.exists?(Path.expand("./test/instance/static/robots.txt")) end defp generated_setup_psql do diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index ca8daae44..b55aa1cdb 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -91,6 +91,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do describe "running rm" do test "user is deleted" do + clear_config([:instance, :federating], true) user = insert(:user) with_mock Pleroma.Web.Federator, @@ -108,8 +109,10 @@ defmodule Mix.Tasks.Pleroma.UserTest do test "a remote user's create activity is deleted when the object has been pruned" do user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{status: "uguu"}) + + clear_config([:instance, :federating], true) + object = Object.normalize(post) Object.prune(object) diff --git a/test/user_test.exs b/test/user_test.exs index 863e0106c..45125f704 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -992,7 +992,7 @@ defmodule Pleroma.UserTest do user = insert(:user, local: true) {:ok, activity} = CommonAPI.post(actor, %{status: "hello"}) - {:ok, announce, _} = CommonAPI.repeat(activity.id, user) + {:ok, announce} = CommonAPI.repeat(activity.id, user) recipients = User.get_recipients_from_activity(announce) @@ -1147,7 +1147,7 @@ defmodule Pleroma.UserTest do {:ok, like} = CommonAPI.favorite(user, activity_two.id) {:ok, like_two} = CommonAPI.favorite(follower, activity.id) - {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user) + {:ok, repeat} = CommonAPI.repeat(activity_two.id, user) {:ok, job} = User.delete(user) {:ok, _user} = ObanHelpers.perform(job) @@ -1777,4 +1777,16 @@ defmodule Pleroma.UserTest do assert result.email_notifications["digest"] == false end end + + test "avatar fallback" do + user = insert(:user) + assert User.avatar_url(user) =~ "/images/avi.png" + + Pleroma.Config.put([:assets, :default_user_avatar], "avatar.png") + + user = User.get_cached_by_nickname_or_id(user.nickname) + assert User.avatar_url(user) =~ "avatar.png" + + assert User.avatar_url(user, no_default: true) == nil + end end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index c432c90e3..dd2a48a61 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo - import Pleroma.Factory alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Delivery @@ -14,13 +13,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do alias Pleroma.Object alias Pleroma.Tests.ObanHelpers alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Endpoint alias Pleroma.Workers.ReceiverWorker + import Pleroma.Factory + + require Pleroma.Constants + setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -168,6 +173,60 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end end + describe "mastodon compatibility routes" do + test "it returns a json representation of the object with accept application/json", %{ + conn: conn + } do + {:ok, object} = + %{ + "type" => "Note", + "content" => "hey", + "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999", + "actor" => Endpoint.url() <> "/users/raymoo", + "to" => [Pleroma.Constants.as_public()] + } + |> Object.create() + + conn = + conn + |> put_req_header("accept", "application/json") + |> get("/users/raymoo/statuses/999999999") + + assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object}) + end + + test "it returns a json representation of the activity with accept application/json", %{ + conn: conn + } do + {:ok, object} = + %{ + "type" => "Note", + "content" => "hey", + "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999", + "actor" => Endpoint.url() <> "/users/raymoo", + "to" => [Pleroma.Constants.as_public()] + } + |> Object.create() + + {:ok, activity, _} = + %{ + "id" => object.data["id"] <> "/activity", + "type" => "Create", + "object" => object.data["id"], + "actor" => object.data["actor"], + "to" => object.data["to"] + } + |> ActivityPub.persist(local: true) + + conn = + conn + |> put_req_header("accept", "application/json") + |> get("/users/raymoo/statuses/999999999/activity") + + assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity}) + end + end + describe "/objects/:uuid" do test "it returns a json representation of the object with accept application/json", %{ conn: conn diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 77bd07edf..3dcb62873 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -537,7 +537,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_one) {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]}) - {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) + {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) activity_three = Activity.get_by_id(activity_three.id) @@ -592,7 +592,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) - {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend) + {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend) activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker}) @@ -618,7 +618,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do followed_user = insert(:user) ActivityPub.follow(user, followed_user) - {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user) + {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user) activities = ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) @@ -651,7 +651,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"}) bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}}) bad_activity = insert(:note_activity, %{note: bad_note}) - {:ok, repeat_activity, _} = CommonAPI.repeat(bad_activity.id, domain_user) + {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user) activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true}) @@ -699,7 +699,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do activity_three_actor = User.get_by_ap_id(activity_three.data["actor"]) {:ok, _user_relationships} = User.mute(user, activity_three_actor) - {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) + {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) activity_three = Activity.get_by_id(activity_three.id) @@ -749,7 +749,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, user} = User.follow(user, booster) - {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster) + {:ok, announce} = CommonAPI.repeat(activity_three.id, booster) [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)]) @@ -846,7 +846,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do booster = insert(:user) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster) - {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + {:ok, activity} = CommonAPI.repeat(activity.id, booster) activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) @@ -860,7 +860,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster) {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster) - {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + {:ok, activity} = CommonAPI.repeat(activity.id, booster) activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) @@ -868,75 +868,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end - describe "announcing an object" do - test "adds an announce activity to the db" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - {:ok, announce_activity, object} = ActivityPub.announce(user, object) - assert object.data["announcement_count"] == 1 - assert object.data["announcements"] == [user.ap_id] - - assert announce_activity.data["to"] == [ - User.ap_followers(user), - note_activity.data["actor"] - ] - - assert announce_activity.data["object"] == object.data["id"] - assert announce_activity.data["actor"] == user.ap_id - assert announce_activity.data["context"] == object.data["context"] - end - - test "reverts annouce from object on error" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do - assert {:error, :reverted} = ActivityPub.announce(user, object) - end - - reloaded_object = Object.get_by_ap_id(object.data["id"]) - assert reloaded_object == object - refute reloaded_object.data["announcement_count"] - refute reloaded_object.data["announcements"] - end - end - - describe "announcing a private object" do - test "adds an announce activity to the db if the audience is not widened" do - user = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - object = Object.normalize(note_activity) - - {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) - - assert announce_activity.data["to"] == [User.ap_followers(user)] - - assert announce_activity.data["object"] == object.data["id"] - assert announce_activity.data["actor"] == user.ap_id - assert announce_activity.data["context"] == object.data["context"] - end - - test "does not add an announce activity to the db if the audience is widened" do - user = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - object = Object.normalize(note_activity) - - assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) - end - - test "does not add an announce activity to the db if the announcer is not the author" do - user = insert(:user) - announcer = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - object = Object.normalize(note_activity) - - assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) - end - end - describe "uploading files" do test "copies the file to the configured folder" do file = %Plug.Upload{ diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index f9990bd2c..929fdbc9b 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -516,4 +516,96 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do assert {:object, valid_like["object"]} in validated.changes end end + + describe "announces" do + setup do + user = insert(:user) + announcer = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) + + object = Object.normalize(post_activity, false) + {:ok, valid_announce, []} = Builder.announce(announcer, object) + + %{ + valid_announce: valid_announce, + user: user, + post_activity: post_activity, + announcer: announcer + } + end + + test "returns ok for a valid announce", %{valid_announce: valid_announce} do + assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, []) + end + + test "returns an error if the object can't be found", %{valid_announce: valid_announce} do + without_object = + valid_announce + |> Map.delete("object") + + {:error, cng} = ObjectValidator.validate(without_object, []) + + assert {:object, {"can't be blank", [validation: :required]}} in cng.errors + + nonexisting_object = + valid_announce + |> Map.put("object", "https://gensokyo.2hu/objects/99999999") + + {:error, cng} = ObjectValidator.validate(nonexisting_object, []) + + assert {:object, {"can't find object", []}} in cng.errors + end + + test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do + nonexisting_actor = + valid_announce + |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + + {:error, cng} = ObjectValidator.validate(nonexisting_actor, []) + + assert {:actor, {"can't find user", []}} in cng.errors + end + + test "returns an error if the actor already announced the object", %{ + valid_announce: valid_announce, + announcer: announcer, + post_activity: post_activity + } do + _announce = CommonAPI.repeat(post_activity.id, announcer) + + {:error, cng} = ObjectValidator.validate(valid_announce, []) + + assert {:actor, {"already announced this object", []}} in cng.errors + assert {:object, {"already announced by this actor", []}} in cng.errors + end + + test "returns an error if the actor can't announce the object", %{ + announcer: announcer, + user: user + } do + {:ok, post_activity} = + CommonAPI.post(user, %{status: "a secret post", visibility: "private"}) + + object = Object.normalize(post_activity, false) + + # Another user can't announce it + {:ok, announce, []} = Builder.announce(announcer, object, public: false) + + {:error, cng} = ObjectValidator.validate(announce, []) + + assert {:actor, {"can not announce this object", []}} in cng.errors + + # The actor of the object can announce it + {:ok, announce, []} = Builder.announce(user, object, public: false) + + assert {:ok, _, _} = ObjectValidator.validate(announce, []) + + # The actor of the object can not announce it publicly + {:ok, announce, []} = Builder.announce(user, object, public: true) + + {:error, cng} = ObjectValidator.validate(announce, []) + + assert {:actor, {"can not announce this object publicly", []}} in cng.errors + end + end end diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs index f3c437498..26557720b 100644 --- a/test/web/activity_pub/pipeline_test.exs +++ b/test/web/activity_pub/pipeline_test.exs @@ -9,6 +9,11 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do import Pleroma.Factory describe "common_pipeline/2" do + setup do + clear_config([:instance, :federating], true) + :ok + end + test "it goes through validation, filtering, persisting, side effects and federation for local activities" do activity = insert(:note_activity) meta = [local: true] @@ -83,5 +88,44 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) end end + + test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do + clear_config([:instance, :federating], false) + + activity = insert(:note_activity) + meta = [local: true] + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [handle: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.Federator, + [], + [] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + end + end end end diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs index 9e16e39c4..dbee8a0f4 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do use Pleroma.DataCase alias Pleroma.Activity - alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay @@ -95,21 +94,20 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do end) assert capture_log(fn -> - assert Relay.publish(activity) == {:error, nil} - end) =~ "[error] error: nil" + assert Relay.publish(activity) == {:error, false} + end) =~ "[error] error: false" end test_with_mock "returns announce activity and publish to federate", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) + clear_config([:instance, :federating], true) service_actor = Relay.get_actor() note = insert(:note_activity) - assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) + assert {:ok, %Activity{} = activity} = Relay.publish(note) assert activity.data["type"] == "Announce" assert activity.data["actor"] == service_actor.ap_id - assert activity.data["object"] == obj.data["id"] assert called(Pleroma.Web.Federator.publish(activity)) end @@ -117,13 +115,12 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], false) + clear_config([:instance, :federating], false) service_actor = Relay.get_actor() note = insert(:note_activity) - assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) + assert {:ok, %Activity{} = activity} = Relay.publish(note) assert activity.data["type"] == "Announce" assert activity.data["actor"] == service_actor.ap_id - assert activity.data["object"] == obj.data["id"] refute called(Pleroma.Web.Federator.publish(activity)) end end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index d63264de9..fb4411c07 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -173,7 +173,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) {:ok, like} = CommonAPI.favorite(user, post.id) {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍") - {:ok, announce, _} = CommonAPI.repeat(post.id, user) + {:ok, announce} = CommonAPI.repeat(post.id, user) {:ok, block} = ActivityPub.block(user, poster) User.block(user, poster) @@ -376,4 +376,61 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do assert chat end end + + describe "announce objects" do + setup do + poster = insert(:user) + user = insert(:user) + {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) + {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"}) + + {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true) + + {:ok, private_announce_data, _meta} = + Builder.announce(user, private_post.object, public: false) + + {:ok, relay_announce_data, _meta} = + Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true) + + {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true) + {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true) + {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true) + + %{ + announce: announce, + user: user, + poster: poster, + private_announce: private_announce, + relay_announce: relay_announce + } + end + + test "adds the announce to the original object", %{announce: announce, user: user} do + {:ok, announce, _} = SideEffects.handle(announce) + object = Object.get_by_ap_id(announce.data["object"]) + assert object.data["announcement_count"] == 1 + assert user.ap_id in object.data["announcements"] + end + + test "does not add the announce to the original object if the actor is a service actor", %{ + relay_announce: announce + } do + {:ok, announce, _} = SideEffects.handle(announce) + object = Object.get_by_ap_id(announce.data["object"]) + assert object.data["announcement_count"] == nil + end + + test "creates a notification", %{announce: announce, poster: poster} do + {:ok, announce, _} = SideEffects.handle(announce) + assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id) + end + + test "it streams out the announce", %{announce: announce} do + with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end do + {:ok, announce, _} = SideEffects.handle(announce) + + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(announce)) + end + end + end end diff --git a/test/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/web/activity_pub/transmogrifier/announce_handling_test.exs new file mode 100644 index 000000000..e895636b5 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/announce_handling_test.exs @@ -0,0 +1,172 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnnounceHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming honk announces" do + user = insert(:user, ap_id: "https://honktest/u/test", local: false) + other_user = insert(:user) + {:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"}) + + announce = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "actor" => "https://honktest/u/test", + "id" => "https://honktest/u/test/bonk/1793M7B9MQ48847vdx", + "object" => post.data["object"], + "published" => "2019-06-25T19:33:58Z", + "to" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Announce" + } + + {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(announce) + + object = Object.get_by_ap_id(post.data["object"]) + + assert length(object.data["announcements"]) == 1 + assert user.ap_id in object.data["announcements"] + end + + test "it works for incoming announces with actor being inlined (kroeg)" do + data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!() + + _user = insert(:user, local: false, ap_id: data["actor"]["id"]) + other_user = insert(:user) + + {:ok, post} = CommonAPI.post(other_user, %{status: "kroegeroeg"}) + + data = + data + |> put_in(["object", "id"], post.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "https://puckipedia.com/" + end + + test "it works for incoming announces, fetching the announced object" do + data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", "http://mastodon.example.org/users/admin/statuses/99541947525187367") + + Tesla.Mock.mock(fn + %{method: :get} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/mastodon-note-object.json")} + end) + + _user = insert(:user, local: false, ap_id: data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Announce" + + assert data["id"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + + assert data["object"] == + "http://mastodon.example.org/users/admin/statuses/99541947525187367" + + assert(Activity.get_create_by_object_ap_id(data["object"])) + end + + @tag capture_log: true + test "it works for incoming announces with an existing activity" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + + data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _user = insert(:user, local: false, ap_id: data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Announce" + + assert data["id"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + + assert data["object"] == activity.data["object"] + + assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id + end + + # Ignore inlined activities for now + @tag skip: true + test "it works for incoming announces with an inlined activity" do + data = + File.read!("test/fixtures/mastodon-announce-private.json") + |> Poison.decode!() + + _user = + insert(:user, + local: false, + ap_id: data["actor"], + follower_address: data["actor"] <> "/followers" + ) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Announce" + + assert data["id"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + + object = Object.normalize(data["object"]) + + assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368" + assert object.data["content"] == "this is a private toot" + end + + @tag capture_log: true + test "it rejects incoming announces with an inlined activity from another origin" do + Tesla.Mock.mock(fn + %{method: :get} -> %Tesla.Env{status: 404, body: ""} + end) + + data = + File.read!("test/fixtures/bogus-mastodon-announce.json") + |> Poison.decode!() + + _user = insert(:user, local: false, ap_id: data["actor"]) + + assert {:error, e} = Transmogrifier.handle_incoming(data) + end + + test "it does not clobber the addressing on announce activities" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + + data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", Object.normalize(activity).data["id"]) + |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"]) + |> Map.put("cc", []) + + _user = + insert(:user, + local: false, + ap_id: data["actor"], + follower_address: "http://mastodon.example.org/users/admin/followers" + ) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["to"] == ["http://mastodon.example.org/users/admin/followers"] + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 0a54e3bb9..356004d48 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -28,6 +28,63 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do setup do: clear_config([:instance, :max_remote_account_fields]) describe "handle_incoming" do + test "it works for incoming notices with tag not being an array (kroeg)" do + data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"]) + + assert object.data["emoji"] == %{ + "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png" + } + + data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"]) + + assert "test" in object.data["tag"] + end + + test "it works for incoming notices with url not being a string (prismo)" do + data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"]) + + assert object.data["url"] == "https://prismo.news/posts/83" + end + + test "it cleans up incoming notices which are not really DMs" do + user = insert(:user) + other_user = insert(:user) + + to = [user.ap_id, other_user.ap_id] + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("to", to) + |> Map.put("cc", []) + + object = + data["object"] + |> Map.put("to", to) + |> Map.put("cc", []) + + data = Map.put(data, "object", object) + + {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert data["to"] == [] + assert data["cc"] == to + + object_data = Object.normalize(activity).data + + assert object_data["to"] == [] + assert object_data["cc"] == to + end + test "it ignores an incoming notice if we already have it" do activity = insert(:note_activity) @@ -260,172 +317,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "

henlo from my Psion netBook

message sent from my Psion netBook

" end - test "it works for incoming honk announces" do - _user = insert(:user, ap_id: "https://honktest/u/test", local: false) - other_user = insert(:user) - {:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"}) - - announce = %{ - "@context" => "https://www.w3.org/ns/activitystreams", - "actor" => "https://honktest/u/test", - "id" => "https://honktest/u/test/bonk/1793M7B9MQ48847vdx", - "object" => post.data["object"], - "published" => "2019-06-25T19:33:58Z", - "to" => "https://www.w3.org/ns/activitystreams#Public", - "type" => "Announce" - } - - {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(announce) - end - - test "it works for incoming announces with actor being inlined (kroeg)" do - data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "https://puckipedia.com/" - end - - test "it works for incoming notices with tag not being an array (kroeg)" do - data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - object = Object.normalize(data["object"]) - - assert object.data["emoji"] == %{ - "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png" - } - - data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - object = Object.normalize(data["object"]) - - assert "test" in object.data["tag"] - end - - test "it works for incoming notices with url not being a string (prismo)" do - data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - object = Object.normalize(data["object"]) - - assert object.data["url"] == "https://prismo.news/posts/83" - end - - test "it cleans up incoming notices which are not really DMs" do - user = insert(:user) - other_user = insert(:user) - - to = [user.ap_id, other_user.ap_id] - - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() - |> Map.put("to", to) - |> Map.put("cc", []) - - object = - data["object"] - |> Map.put("to", to) - |> Map.put("cc", []) - - data = Map.put(data, "object", object) - - {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) - - assert data["to"] == [] - assert data["cc"] == to - - object_data = Object.normalize(activity).data - - assert object_data["to"] == [] - assert object_data["cc"] == to - end - - test "it works for incoming announces" do - data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Announce" - - assert data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - - assert data["object"] == - "http://mastodon.example.org/users/admin/statuses/99541947525187367" - - assert Activity.get_create_by_object_ap_id(data["object"]) - end - - test "it works for incoming announces with an existing activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) - - data = - File.read!("test/fixtures/mastodon-announce.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Announce" - - assert data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - - assert data["object"] == activity.data["object"] - - assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id - end - - test "it works for incoming announces with an inlined activity" do - data = - File.read!("test/fixtures/mastodon-announce-private.json") - |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Announce" - - assert data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - - object = Object.normalize(data["object"]) - - assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368" - assert object.data["content"] == "this is a private toot" - end - - @tag capture_log: true - test "it rejects incoming announces with an inlined activity from another origin" do - data = - File.read!("test/fixtures/bogus-mastodon-announce.json") - |> Poison.decode!() - - assert :error = Transmogrifier.handle_incoming(data) - end - - test "it does not clobber the addressing on announce activities" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) - - data = - File.read!("test/fixtures/mastodon-announce.json") - |> Poison.decode!() - |> Map.put("object", Object.normalize(activity).data["id"]) - |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"]) - |> Map.put("cc", []) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["to"] == ["http://mastodon.example.org/users/admin/followers"] - end - test "it ensures that as:Public activities make it to their followers collection" do user = insert(:user) @@ -1188,7 +1079,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"}) - {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) + {:ok, announce_activity} = CommonAPI.repeat(activity.id, user) {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) @@ -1438,7 +1329,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) + {:error, _} = Transmogrifier.handle_incoming(data) end) =~ "Object containment failed" end @@ -1453,7 +1344,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) + {:error, _} = Transmogrifier.handle_incoming(data) end) =~ "Object containment failed" end @@ -1468,7 +1359,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) + {:error, _} = Transmogrifier.handle_incoming(data) end) =~ "Object containment failed" end end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index 9e0a0f1c4..15f03f193 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -334,7 +334,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do assert object = Object.normalize(note_activity) actor = insert(:user) - {:ok, announce, _object} = ActivityPub.announce(actor, object) + {:ok, announce} = CommonAPI.repeat(note_activity.id, actor) assert Utils.get_existing_announce(actor.ap_id, object) == announce end end diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index 43f0617f0..f0389845d 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -73,7 +73,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do object = Object.normalize(note) user = insert(:user) - {:ok, announce_activity, _} = CommonAPI.repeat(note.id, user) + {:ok, announce_activity} = CommonAPI.repeat(note.id, user) result = ObjectView.render("object.json", %{object: announce_activity}) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs similarity index 95% rename from test/web/admin_api/admin_api_controller_test.exs rename to test/web/admin_api/controllers/admin_api_controller_test.exs index 370d876d0..321840a8c 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -148,6 +148,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do describe "DELETE /api/pleroma/admin/users" do test "single user", %{admin: admin, conn: conn} do user = insert(:user) + clear_config([:instance, :federating], true) with_mock Pleroma.Web.Federator, publish: fn _ -> nil end do @@ -350,7 +351,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") - assert "Not found" == json_response(conn, 404) + assert %{"error" => "Not found"} == json_response(conn, 404) end end @@ -683,7 +684,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :bad_request) == - "To send invites you need to set the `invites_enabled` option to true." + %{ + "error" => + "To send invites you need to set the `invites_enabled` option to true." + } end test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do @@ -693,7 +697,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :bad_request) == - "To send invites you need to set the `registrations_open` option to false." + %{ + "error" => + "To send invites you need to set the `registrations_open` option to false." + } end end @@ -1307,7 +1314,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"}) |> json_response(404) - assert response == "Not found" + assert response == %{"error" => "Not found"} end end @@ -1413,7 +1420,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "with invalid token", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) - assert json_response(conn, :not_found) == "Not found" + assert json_response(conn, :not_found) == %{"error" => "Not found"} end end @@ -1440,7 +1447,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "returns 404 when report id is invalid", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/reports/test") - assert json_response(conn, :not_found) == "Not found" + assert json_response(conn, :not_found) == %{"error" => "Not found"} end end @@ -1697,115 +1704,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end end - describe "GET /api/pleroma/admin/statuses/:id" do - test "not found", %{conn: conn} do - assert conn - |> get("/api/pleroma/admin/statuses/not_found") - |> json_response(:not_found) - end - - test "shows activity", %{conn: conn} do - activity = insert(:note_activity) - - response = - conn - |> get("/api/pleroma/admin/statuses/#{activity.id}") - |> json_response(200) - - assert response["id"] == activity.id - end - end - - describe "PUT /api/pleroma/admin/statuses/:id" do - setup do - activity = insert(:note_activity) - - %{id: activity.id} - end - - test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) - |> json_response(:ok) - - assert response["sensitive"] - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) - |> json_response(:ok) - - refute response["sensitive"] - end - - test "change visibility flag", %{conn: conn, id: id, admin: admin} do - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"}) - |> json_response(:ok) - - assert response["visibility"] == "public" - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"}) - |> json_response(:ok) - - assert response["visibility"] == "private" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"}) - |> json_response(:ok) - - assert response["visibility"] == "unlisted" - end - - test "returns 400 when visibility is unknown", %{conn: conn, id: id} do - conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{visibility: "test"}) - - assert json_response(conn, :bad_request) == "Unsupported visibility" - end - end - - describe "DELETE /api/pleroma/admin/statuses/:id" do - setup do - activity = insert(:note_activity) - - %{id: activity.id} - end - - test "deletes status", %{conn: conn, id: id, admin: admin} do - conn - |> delete("/api/pleroma/admin/statuses/#{id}") - |> json_response(:ok) - - refute Activity.get_by_id(id) - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deleted status ##{id}" - end - - test "returns 404 when the status does not exist", %{conn: conn} do - conn = delete(conn, "/api/pleroma/admin/statuses/test") - - assert json_response(conn, :not_found) == "Not found" - end - end - describe "GET /api/pleroma/admin/config" do setup do: clear_config(:configurable_from_database, true) @@ -1814,7 +1712,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = get(conn, "/api/pleroma/admin/config") assert json_response(conn, 400) == - "To use this endpoint you need to enable configuration from database." + %{ + "error" => "To use this endpoint you need to enable configuration from database." + } end test "with settings only in db", %{conn: conn} do @@ -1936,7 +1836,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []}) assert json_response(conn, 400) == - "To use this endpoint you need to enable configuration from database." + %{"error" => "To use this endpoint you need to enable configuration from database."} end describe "POST /api/pleroma/admin/config" do @@ -2944,6 +2844,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert ":proxy_url" in db end + @tag capture_log: true test "doesn't set keys not in the whitelist", %{conn: conn} do clear_config(:database_config_whitelist, [ {:pleroma, :key1}, @@ -2998,54 +2899,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do on_exit(fn -> Restarter.Pleroma.refresh() end) end - describe "GET /api/pleroma/admin/statuses" do - test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do - blocked = insert(:user) - user = insert(:user) - User.block(admin, blocked) - - {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) - - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"}) - - response = - conn - |> get("/api/pleroma/admin/statuses") - |> json_response(200) - - refute "private" in Enum.map(response, & &1["visibility"]) - assert length(response) == 3 - end - - test "returns only local statuses with local_only on", %{conn: conn} do - user = insert(:user) - remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") - insert(:note_activity, user: user, local: true) - insert(:note_activity, user: remote_user, local: false) - - response = - conn - |> get("/api/pleroma/admin/statuses?local_only=true") - |> json_response(200) - - assert length(response) == 1 - end - - test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do - user = insert(:user) - - {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) - - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") - assert json_response(conn, 200) |> length() == 3 - end - end - describe "GET /api/pleroma/admin/users/:nickname/statuses" do setup do user = insert(:user) @@ -3096,7 +2949,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "excludes reblogs by default", %{conn: conn, user: user} do other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "."}) - {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user) conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses") assert json_response(conn_res, 200) |> length() == 0 diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/web/admin_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..124d8dc2e --- /dev/null +++ b/test/web/admin_api/controllers/status_controller_test.exs @@ -0,0 +1,194 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.ModerationLog + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "GET /api/pleroma/admin/statuses/:id" do + test "not found", %{conn: conn} do + assert conn + |> get("/api/pleroma/admin/statuses/not_found") + |> json_response_and_validate_schema(:not_found) + end + + test "shows activity", %{conn: conn} do + activity = insert(:note_activity) + + response = + conn + |> get("/api/pleroma/admin/statuses/#{activity.id}") + |> json_response_and_validate_schema(200) + + assert response["id"] == activity.id + end + end + + describe "PUT /api/pleroma/admin/statuses/:id" do + setup do + activity = insert(:note_activity) + + %{id: activity.id} + end + + test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) + |> json_response_and_validate_schema(:ok) + + assert response["sensitive"] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) + |> json_response_and_validate_schema(:ok) + + refute response["sensitive"] + end + + test "change visibility flag", %{conn: conn, id: id, admin: admin} do + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"}) + |> json_response_and_validate_schema(:ok) + + assert response["visibility"] == "public" + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"}) + |> json_response_and_validate_schema(:ok) + + assert response["visibility"] == "private" + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"}) + |> json_response_and_validate_schema(:ok) + + assert response["visibility"] == "unlisted" + end + + test "returns 400 when visibility is unknown", %{conn: conn, id: id} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "test"}) + + assert %{"error" => "test - Invalid value for enum."} = + json_response_and_validate_schema(conn, :bad_request) + end + end + + describe "DELETE /api/pleroma/admin/statuses/:id" do + setup do + activity = insert(:note_activity) + + %{id: activity.id} + end + + test "deletes status", %{conn: conn, id: id, admin: admin} do + conn + |> delete("/api/pleroma/admin/statuses/#{id}") + |> json_response_and_validate_schema(:ok) + + refute Activity.get_by_id(id) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted status ##{id}" + end + + test "returns 404 when the status does not exist", %{conn: conn} do + conn = delete(conn, "/api/pleroma/admin/statuses/test") + + assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} + end + end + + describe "GET /api/pleroma/admin/statuses" do + test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do + blocked = insert(:user) + user = insert(:user) + User.block(admin, blocked) + + {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"}) + + response = + conn + |> get("/api/pleroma/admin/statuses") + |> json_response_and_validate_schema(200) + + refute "private" in Enum.map(response, & &1["visibility"]) + assert length(response) == 3 + end + + test "returns only local statuses with local_only on", %{conn: conn} do + user = insert(:user) + remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") + insert(:note_activity, user: user, local: true) + insert(:note_activity, user: remote_user, local: false) + + response = + conn + |> get("/api/pleroma/admin/statuses?local_only=true") + |> json_response_and_validate_schema(200) + + assert length(response) == 1 + end + + test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do + user = insert(:user) + + {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") + assert json_response_and_validate_schema(conn, 200) |> length() == 3 + end + end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 9e626fb9e..9e129e5a7 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -116,6 +116,8 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"}) + clear_config([:instance, :federating], true) + Object.normalize(post, false) |> Object.prune() @@ -134,6 +136,8 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"}) + clear_config([:instance, :federating], true) + with_mock Pleroma.Web.Federator, publish: fn _ -> nil end do assert {:ok, delete} = CommonAPI.delete(post.id, user) @@ -410,6 +414,32 @@ defmodule Pleroma.Web.CommonAPITest do end) end + test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"}) + + {:ok, open_answer} = + CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id}) + + # The OP is implicitly added + assert user.ap_id in open_answer.recipients + + {:ok, secret_answer} = + CommonAPI.post(other_user, %{ + status: "lol, that guy really is stupid, right, @#{third_user.nickname}?", + in_reply_to_status_id: post.id, + visibility: "direct" + }) + + assert third_user.ap_id in secret_answer.recipients + + # The OP is not added + refute user.ap_id in secret_answer.recipients + end + test "it allows to address a list" do user = insert(:user) {:ok, list} = Pleroma.List.create("foo", user) @@ -491,7 +521,8 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) - {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user) + {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user) + assert Visibility.is_public?(announce_activity) end test "can't repeat a repeat" do @@ -499,9 +530,9 @@ defmodule Pleroma.Web.CommonAPITest do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) - {:ok, %Activity{} = announce, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user) - refute match?({:ok, %Activity{}, _}, CommonAPI.repeat(announce.id, user)) + refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user)) end test "repeating a status privately" do @@ -510,10 +541,11 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) - {:ok, %Activity{} = announce_activity, _} = + {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user, %{visibility: "private"}) assert Visibility.is_private?(announce_activity) + refute Visibility.visible_for_user?(announce_activity, nil) end test "favoriting a status" do @@ -533,8 +565,8 @@ defmodule Pleroma.Web.CommonAPITest do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"}) - {:ok, %Activity{} = announce, object} = CommonAPI.repeat(activity.id, user) - {:ok, ^announce, ^object} = CommonAPI.repeat(activity.id, user) + {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user) + {:ok, ^announce} = CommonAPI.repeat(activity.id, user) end test "favoriting a status twice returns ok, but without the like activity" do diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index 5708db6a4..e67c10b93 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -14,18 +14,41 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do @public_address "https://www.w3.org/ns/activitystreams#Public" - test "it adds attachment links to a given text and attachment set" do - name = - "Sakura%20Mana%20%E2%80%93%20Turned%20on%20by%20a%20Senior%20OL%20with%20a%20Temptating%20Tight%20Skirt-s%20Full%20Hipline%20and%20Panty%20Shot-%20Beautiful%20Thick%20Thighs-%20and%20Erotic%20Ass-%20-2015-%20--%20Oppaitime%208-28-2017%206-50-33%20PM.png" + describe "add_attachments/2" do + setup do + name = + "Sakura Mana – Turned on by a Senior OL with a Temptating Tight Skirt-s Full Hipline and Panty Shot- Beautiful Thick Thighs- and Erotic Ass- -2015- -- Oppaitime 8-28-2017 6-50-33 PM.png" - attachment = %{ - "url" => [%{"href" => name}] - } + attachment = %{ + "url" => [%{"href" => URI.encode(name)}] + } - res = Utils.add_attachments("", [attachment]) + %{name: name, attachment: attachment} + end + + test "it adds attachment links to a given text and attachment set", %{ + name: name, + attachment: attachment + } do + len = 10 + clear_config([Pleroma.Upload, :filename_display_max_length], len) + + expected = + "
#{String.slice(name, 0..len)}…" + + assert Utils.add_attachments("", [attachment]) == expected + end + + test "doesn't truncate file name if config for truncate is set to 0", %{ + name: name, + attachment: attachment + } do + clear_config([Pleroma.Upload, :filename_display_max_length], 0) + + expected = "
#{name}" - assert res == - "
Sakura Mana – Turned on by a Se…" + assert Utils.add_attachments("", [attachment]) == expected + end end describe "it confirms the password given is the current users password" do @@ -297,11 +320,10 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil) - assert length(to) == 3 + assert length(to) == 2 assert Enum.empty?(cc) assert mentioned_user.ap_id in to - assert third_user.ap_id in to assert user.follower_address in to end @@ -327,6 +349,15 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil) + assert length(to) == 1 + assert Enum.empty?(cc) + + assert mentioned_user.ap_id in to + + {:ok, direct_activity} = CommonAPI.post(third_user, %{status: "uguu", visibility: "direct"}) + + {to, cc} = Utils.get_to_and_cc(user, mentions, direct_activity, "direct", nil) + assert length(to) == 2 assert Enum.empty?(cc) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 280bd6aca..1ce97378d 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -256,7 +256,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do User.block(user_one, user_two) {:ok, activity} = CommonAPI.post(user_two, %{status: "User one sux0rz"}) - {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three) + {:ok, repeat} = CommonAPI.repeat(activity.id, user_three) assert resp = conn @@ -375,7 +375,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "gets a user's statuses without reblogs", %{user: user, conn: conn} do {:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"}) - {:ok, _, _} = CommonAPI.repeat(post_id, user) + {:ok, _} = CommonAPI.repeat(post_id, user) conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true") assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200) @@ -678,7 +678,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) - {:ok, %{id: reblog_id}, _} = CommonAPI.repeat(activity.id, followed) + {:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, followed) assert [] == conn diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 562fc4d8e..e278d61f5 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -280,8 +280,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do {:ok, unlisted_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"}) - {:ok, _, _} = CommonAPI.repeat(public_activity.id, user) - {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user) + {:ok, _} = CommonAPI.repeat(public_activity.id, user) + {:ok, _} = CommonAPI.repeat(unlisted_activity.id, user) activity_ids = conn @@ -301,7 +301,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"}) {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) - {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user) {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) mention_notification_id = get_notification_id_by_activity(mention_activity) @@ -339,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"}) {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) - {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user) {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) mention_notification_id = get_notification_id_by_activity(mention_activity) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 962e64b03..700c82e4f 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -878,8 +878,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do user3 = insert(:user) {:ok, _} = CommonAPI.favorite(user2, activity.id) {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) - {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) - {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) + {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1) + {:ok, _} = CommonAPI.repeat(activity.id, user2) conn_res = build_conn() @@ -917,7 +917,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do activity = insert(:note_activity) - {:ok, _, _} = CommonAPI.repeat(activity.id, user) + {:ok, _} = CommonAPI.repeat(activity.id, user) conn = conn @@ -1427,7 +1427,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "returns users who have reblogged the status", %{conn: conn, activity: activity} do other_user = insert(:user) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, _} = CommonAPI.repeat(activity.id, other_user) response = conn @@ -1458,7 +1458,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do other_user = insert(:user) {:ok, _user_relationship} = User.block(user, other_user) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, _} = CommonAPI.repeat(activity.id, other_user) response = conn @@ -1469,12 +1469,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end test "does not return users who have reblogged the status privately", %{ - conn: conn, - activity: activity + conn: conn } do other_user = insert(:user) + {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"}) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"}) + {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"}) response = conn @@ -1486,7 +1486,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "does not fail on an unauthenticated request", %{activity: activity} do other_user = insert(:user) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + {:ok, _} = CommonAPI.repeat(activity.id, other_user) response = build_conn() diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 8db6a2379..384fe7253 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -104,7 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do user = insert(:user) another_user = insert(:user) {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"}) - {:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user) + {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, another_user) {:ok, [notification]} = Notification.create_notifications(reblog_activity) reblog_activity = Activity.get_by_id(create_activity.id) diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 43e3bdca1..5cbadf0fc 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -442,7 +442,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do user = insert(:user) activity = insert(:note_activity) - {:ok, reblog, _} = CommonAPI.repeat(activity.id, user) + {:ok, reblog} = CommonAPI.repeat(activity.id, user) represented = StatusView.render("show.json", %{for: user, activity: reblog}) @@ -600,7 +600,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status: "˙˙ɐʎns" }) - {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) + {:ok, activity} = CommonAPI.repeat(activity.id, other_user) result = StatusView.render("show.json", %{activity: activity, for: user}) diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index bb349cb19..ee498f4b5 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -10,7 +10,11 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do alias Pleroma.Config alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Endpoint + + require Pleroma.Constants setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -19,6 +23,47 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do setup do: clear_config([:instance, :federating], true) + describe "Mastodon compatibility routes" do + setup %{conn: conn} do + conn = put_req_header(conn, "accept", "text/html") + + {:ok, object} = + %{ + "type" => "Note", + "content" => "hey", + "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999", + "actor" => Endpoint.url() <> "/users/raymoo", + "to" => [Pleroma.Constants.as_public()] + } + |> Object.create() + + {:ok, activity, _} = + %{ + "id" => object.data["id"] <> "/activity", + "type" => "Create", + "object" => object.data["id"], + "actor" => object.data["actor"], + "to" => object.data["to"] + } + |> ActivityPub.persist(local: true) + + %{conn: conn, activity: activity} + end + + test "redirects to /notice/:id for html format", %{conn: conn, activity: activity} do + conn = get(conn, "/users/raymoo/statuses/999999999") + assert redirected_to(conn) == "/notice/#{activity.id}" + end + + test "redirects to /notice/:id for html format for activity", %{ + conn: conn, + activity: activity + } do + conn = get(conn, "/users/raymoo/statuses/999999999/activity") + assert redirected_to(conn) == "/notice/#{activity.id}" + end + end + # Note: see ActivityPubControllerTest for JSON format tests describe "GET /objects/:uuid (text/html)" do setup %{conn: conn} do diff --git a/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index ee66ebf87..e1bb5ebfe 100644 --- a/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -33,6 +33,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do assert result["pleroma"]["emoji_reactions"] == [ %{"name" => "☕", "count" => 1, "me" => true} ] + + # Reacting with a non-emoji + assert conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/x") + |> json_response_and_validate_schema(400) end test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do diff --git a/test/web/pleroma_api/controllers/notification_controller_test.exs b/test/web/pleroma_api/controllers/notification_controller_test.exs index 7c5ace804..bb4fe6c49 100644 --- a/test/web/pleroma_api/controllers/notification_controller_test.exs +++ b/test/web/pleroma_api/controllers/notification_controller_test.exs @@ -23,7 +23,8 @@ defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do response = conn - |> post("/api/v1/pleroma/notifications/read?id=#{notification1.id}") + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/notifications/read", %{id: notification1.id}) |> json_response_and_validate_schema(:ok) assert %{"pleroma" => %{"is_seen" => true}} = response @@ -41,7 +42,8 @@ defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do [response1, response2] = conn - |> post("/api/v1/pleroma/notifications/read?max_id=#{notification2.id}") + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id}) |> json_response_and_validate_schema(:ok) assert %{"pleroma" => %{"is_seen" => true}} = response1 @@ -54,7 +56,10 @@ defmodule Pleroma.Web.PleromaAPI.NotificationControllerTest do test "it returns error when notification not found", %{conn: conn} do response = conn - |> post("/api/v1/pleroma/notifications/read?id=22222222222222") + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/notifications/read", %{ + id: 22_222_222_222_222 + }) |> json_response_and_validate_schema(:bad_request) assert response == %{"error" => "Cannot get notification"} diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 2acd0939f..a826b24c9 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -151,7 +151,7 @@ defmodule Pleroma.Web.Push.ImplTest do "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." }) - {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) + {:ok, announce_activity} = CommonAPI.repeat(activity.id, user) object = Object.normalize(activity) assert Impl.format_body(%{activity: announce_activity}, user, object) == diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 95b7d1420..cb4595bb6 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -106,7 +106,7 @@ defmodule Pleroma.Web.StreamerTest do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) - {:ok, announce, _} = CommonAPI.repeat(activity.id, user) + {:ok, announce} = CommonAPI.repeat(activity.id, user) assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} refute Streamer.filtered_by_user?(user, announce) @@ -427,7 +427,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, create_activity} = CommonAPI.post(user3, %{status: "I'm kawen"}) Streamer.get_topic_and_add_socket("user", user1) - {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) + {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, _, ^announce_activity} assert Streamer.filtered_by_user?(user1, announce_activity) end @@ -440,7 +440,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, create_activity} = CommonAPI.post(user1, %{status: "I'm kawen"}) Streamer.get_topic_and_add_socket("user", user1) - {:ok, _favorite_activity, _} = CommonAPI.repeat(create_activity.id, user2) + {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, "notification.json", notif} assert Streamer.filtered_by_user?(user1, notif) diff --git a/test/workers/cron/new_users_digest_worker_test.exs b/test/workers/cron/new_users_digest_worker_test.exs index 54cf0ca46..ee589bb55 100644 --- a/test/workers/cron/new_users_digest_worker_test.exs +++ b/test/workers/cron/new_users_digest_worker_test.exs @@ -28,6 +28,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorkerTest do assert email.html_body =~ user.nickname assert email.html_body =~ user2.nickname assert email.html_body =~ "cofe" + assert email.html_body =~ "#{Pleroma.Web.Endpoint.url()}/static/logo.png" end test "it doesn't fail when admin has no email" do