Merge remote-tracking branch 'origin/develop' into feature/local-only-scope
authorEgor Kislitsyn <egor@kislitsyn.com>
Thu, 29 Oct 2020 10:51:23 +0000 (14:51 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Thu, 29 Oct 2020 10:51:23 +0000 (14:51 +0400)
25 files changed:
CHANGELOG.md
config/config.exs
config/description.exs
docs/configuration/cheatsheet.md
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/web/admin_api/views/account_view.ex
lib/pleroma/web/feed/tag_controller.ex
lib/pleroma/web/feed/user_controller.ex
lib/pleroma/web/o_status/o_status_controller.ex
lib/pleroma/web/plugs/frontend_static.ex
lib/pleroma/web/router.ex
lib/pleroma/web/static_fe/static_fe_controller.ex
test/fixtures/mewmew_no_name.json [new file with mode: 0644]
test/pleroma/user_test.exs
test/pleroma/web/activity_pub/activity_pub_controller_test.exs
test/pleroma/web/activity_pub/activity_pub_test.exs
test/pleroma/web/endpoint/metrics_exporter_test.exs
test/pleroma/web/feed/tag_controller_test.exs
test/pleroma/web/feed/user_controller_test.exs
test/pleroma/web/o_status/o_status_controller_test.exs
test/pleroma/web/plugs/frontend_static_plug_test.exs
test/pleroma/web/static_fe/static_fe_controller_test.exs
test/support/conn_case.ex

index 361ad503887be36bd8a99f290d824078cc66d064..d2f536da8379d86d1400e74555e649a6d75efb87 100644 (file)
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Experimental websocket-based federation between Pleroma instances.
 - Support for local-only statuses
 - App metrics: ability to restrict access to specified IP whitelist.
+- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
 
 
 ### Changed
@@ -51,7 +52,9 @@ switched to a new configuration mechanism, however it was not officially removed
 
 - Add documented-but-missing chat pagination.
 - Allow sending out emails again.
-- Allow sending chat messages to yourself
+- Allow sending chat messages to yourself.
+- Fix remote users with a whitespace name.
+- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
 
 ## Unreleased (Patch)
 
index bd611fd426c45c1325e29ac48d030e7fa43014bd..c52ee8f82407f23b8ba1eaecde5f185b74dd7f6b 100644 (file)
@@ -234,6 +234,7 @@ config :pleroma, :instance,
     "text/bbcode"
   ],
   autofollowed_nicknames: [],
+  autofollowing_nicknames: [],
   max_pinned_statuses: 1,
   attachment_links: false,
   max_report_comment_size: 1000,
index 55363c45ae655135772325f889a92deb3c707ab4..0bfa9979f91ab37f11226d60eec2b8f05b497116 100644 (file)
@@ -831,6 +831,12 @@ config :pleroma, :config_description, [
         description:
           "Set to nicknames of (local) users that every new user should automatically follow"
       },
+      %{
+        key: :autofollowing_nicknames,
+        type: {:list, :string},
+        description:
+          "Set to nicknames of (local) users that automatically follows every newly registered user"
+      },
       %{
         key: :attachment_links,
         type: :boolean,
index 0b13d7e88197890c8f630dc1cfdefdeb6db1833b..f4b4b6c3cd504a827fda2faa63231af01264083b 100644 (file)
@@ -45,6 +45,7 @@ To add configuration to your config file, you can copy it from the base config.
     older software for theses nicknames.
 * `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
 * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
+* `autofollowing_nicknames`: Set to nicknames of (local) users that automatically follows every newly registered user.
 * `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
 * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
 * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
index dc41d00014b6fba5c8874a01a696bf898dea8358..a1e546b2d165573eb79ea9d38c27e2d8ca6615a5 100644 (file)
@@ -426,7 +426,6 @@ defmodule Pleroma.User do
       params,
       [
         :bio,
-        :name,
         :emoji,
         :ap_id,
         :inbox,
@@ -455,7 +454,9 @@ defmodule Pleroma.User do
         :accepts_chat_messages
       ]
     )
-    |> validate_required([:name, :ap_id])
+    |> cast(params, [:name], empty_values: [])
+    |> validate_required([:ap_id])
+    |> validate_required([:name], trim: false)
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, @email_regex)
     |> validate_length(:bio, max: bio_limit)
@@ -765,6 +766,16 @@ defmodule Pleroma.User do
     follow_all(user, autofollowed_users)
   end
 
+  defp autofollowing_users(user) do
+    candidates = Config.get([:instance, :autofollowing_nicknames])
+
+    User.Query.build(%{nickname: candidates, local: true, deactivated: false})
+    |> Repo.all()
+    |> Enum.each(&follow(&1, user, :follow_accept))
+
+    {:ok, :success}
+  end
+
   @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
   def register(%Ecto.Changeset{} = changeset) do
     with {:ok, user} <- Repo.insert(changeset) do
@@ -774,6 +785,7 @@ defmodule Pleroma.User do
 
   def post_register_action(%User{} = user) do
     with {:ok, user} <- autofollow_users(user),
+         {:ok, _} <- autofollowing_users(user),
          {:ok, user} <- set_cache(user),
          {:ok, _} <- send_welcome_email(user),
          {:ok, _} <- send_welcome_message(user),
index d17c892a7e2b6d6600220e37a2812176354d32fe..13869f89788f4642a15e9cae26d2ca3fb903b15e 100644 (file)
@@ -1378,6 +1378,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
          {:ok, data} <- user_data_from_user_object(data) do
       {:ok, maybe_update_follow_information(data)}
     else
+      # If this has been deleted, only log a debug and not an error
       {:error, "Object has been deleted" = e} ->
         Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
         {:error, e}
index 1a0c9a46c1c62b26c9addd9a2e868b48bc4733b2..b3b23a38baa4df987a67204966a67dc4ff545407 100644 (file)
@@ -48,29 +48,30 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   def is_list?(%{data: %{"listMessage" => _}}), do: true
   def is_list?(_), do: false
 
-  @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
-  def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+  @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean()
+  def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true
 
   def visible_for_user?(nil, _), do: false
 
-  def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+  def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false
 
-  def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+  def visible_for_user?(
+        %Activity{data: %{"listMessage" => list_ap_id}} = activity,
+        %User{} = user
+      ) do
     user.ap_id in activity.data["to"] ||
       list_ap_id
       |> Pleroma.List.get_by_ap_id()
       |> Pleroma.List.member?(user)
   end
 
-  def visible_for_user?(%{local: local} = activity, nil) do
-    cfg_key = if local, do: :local, else: :remote
-
-    if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
+  def visible_for_user?(%Activity{} = activity, nil) do
+    if restrict_unauthenticated_access?(activity),
       do: false,
       else: is_public?(activity)
   end
 
-  def visible_for_user?(activity, user) do
+  def visible_for_user?(%Activity{} = activity, user) do
     x = [user.ap_id | User.following(user)]
     y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
     is_public?(activity) || Enum.any?(x, &(&1 in y))
@@ -86,6 +87,26 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
     result
   end
 
+  def restrict_unauthenticated_access?(%Activity{local: local}) do
+    restrict_unauthenticated_access_to_activity?(local)
+  end
+
+  def restrict_unauthenticated_access?(%Object{} = object) do
+    object
+    |> Object.local?()
+    |> restrict_unauthenticated_access_to_activity?()
+  end
+
+  def restrict_unauthenticated_access?(%User{} = user) do
+    User.visible_for(user, _reading_user = nil)
+  end
+
+  defp restrict_unauthenticated_access_to_activity?(local?) when is_boolean(local?) do
+    cfg_key = if local?, do: :local, else: :remote
+
+    Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key)
+  end
+
   def get_visibility(object) do
     to = object.data["to"] || []
     cc = object.data["cc"] || []
index bda7ea19ce710b2d4e666e59b35779b1cba63513..8bac24d3efabdb991e61999147c6eb5b57395abc 100644 (file)
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
       :skip_thread_containment,
       :pleroma_settings_store,
       :raw_fields,
-      :discoverable,
+      :is_discoverable,
       :actor_type
     ])
     |> Map.merge(%{
index 93a8294b7b49ba88968e693e8121597eb1db6742..218cdbdf3aa3c65f6341ee24150779f746250b96 100644 (file)
@@ -10,14 +10,14 @@ defmodule Pleroma.Web.Feed.TagController do
   alias Pleroma.Web.Feed.FeedView
 
   def feed(conn, params) do
-    unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
+    if Config.get!([:instance, :public]) do
       render_feed(conn, params)
     else
       render_error(conn, :not_found, "Not found")
     end
   end
 
-  def render_feed(conn, %{"tag" => raw_tag} = params) do
+  defp render_feed(conn, %{"tag" => raw_tag} = params) do
     {format, tag} = parse_tag(raw_tag)
 
     activities =
@@ -36,12 +36,13 @@ defmodule Pleroma.Web.Feed.TagController do
   end
 
   @spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
-  defp parse_tag(raw_tag) when is_binary(raw_tag) do
-    case Enum.reverse(String.split(raw_tag, ".")) do
-      [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
-      _ -> {"rss", raw_tag}
+  defp parse_tag(raw_tag) do
+    case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do
+      [format | tag] when format in ["rss", "atom"] ->
+        {format, Enum.join(tag, ".")}
+
+      _ ->
+        {"atom", raw_tag}
     end
   end
-
-  defp parse_tag(raw_tag), do: {"rss", raw_tag}
 end
index 752983c3b0527a35a12570d8035fc25a98fef3ce..a5013d2c004a03315a52fc62eb7eac684482b98c 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.Feed.UserController do
   use Pleroma.Web, :controller
 
+  alias Pleroma.Config
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.ActivityPubController
@@ -22,12 +23,7 @@ defmodule Pleroma.Web.Feed.UserController do
 
   def feed_redirect(%{assigns: %{format: format}} = conn, _params)
       when format in ["json", "activity+json"] do
-    with %{halted: false} = conn <-
-           Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn,
-             unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
-           ) do
-      ActivityPubController.call(conn, :user)
-    end
+    ActivityPubController.call(conn, :user)
   end
 
   def feed_redirect(conn, %{"nickname" => nickname}) do
@@ -36,25 +32,18 @@ defmodule Pleroma.Web.Feed.UserController do
     end
   end
 
-  def feed(conn, params) do
-    unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
-      render_feed(conn, params)
-    else
-      errors(conn, {:error, :not_found})
-    end
-  end
-
-  def render_feed(conn, %{"nickname" => nickname} = params) do
+  def feed(conn, %{"nickname" => nickname} = params) do
     format = get_format(conn)
 
     format =
-      if format in ["rss", "atom"] do
+      if format in ["atom", "rss"] do
         format
       else
         "atom"
       end
 
-    with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+    with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
+         {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
       activities =
         %{
           type: ["Create"],
@@ -69,7 +58,7 @@ defmodule Pleroma.Web.Feed.UserController do
       |> render("user.#{format}",
         user: user,
         activities: activities,
-        feed_config: Pleroma.Config.get([:feed])
+        feed_config: Config.get([:feed])
       )
     end
   end
@@ -81,6 +70,8 @@ defmodule Pleroma.Web.Feed.UserController do
   def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
   def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
 
+  def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found})
+
   def errors(conn, _) do
     render_error(conn, :internal_server_error, "Something went wrong")
   end
index b044260b3602e6c1e0a2dd24d80fe97d129d1a88..668ae0ea4fde363ba01e4eb24f92380d4e61ae76 100644 (file)
@@ -16,10 +16,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
   alias Pleroma.Web.Plugs.RateLimiter
   alias Pleroma.Web.Router
 
-  plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
-    unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
-  )
-
   plug(
     RateLimiter,
     [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@@ -37,14 +33,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
     ActivityPubController.call(conn, :object)
   end
 
-  def object(%{assigns: %{format: format}} = conn, _params) do
+  def object(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
-      case format do
-        _ -> redirect(conn, to: "/notice/#{activity.id}")
-      end
+      redirect(conn, to: "/notice/#{activity.id}")
     else
       reason when reason in [{:public?, false}, {:activity, nil}] ->
         {:error, :not_found}
@@ -59,13 +53,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
     ActivityPubController.call(conn, :activity)
   end
 
-  def activity(%{assigns: %{format: format}} = conn, _params) do
+  def activity(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
-        _ -> redirect(conn, to: "/notice/#{activity.id}")
-      end
+      redirect(conn, to: "/notice/#{activity.id}")
     else
       reason when reason in [{:public?, false}, {:activity, nil}] ->
         {:error, :not_found}
@@ -119,6 +111,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
   def notice_player(conn, %{"id" => id}) do
     with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
          true <- Visibility.is_public?(activity),
+         {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
          %Object{} = object <- Object.normalize(activity),
          %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
          true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
index ceb10dcf87a253431f77fdf5a38d38257cd949f9..1b0b368138dbb6d14ba1fec9f403bddaabb915c6 100644 (file)
@@ -34,22 +34,26 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
   end
 
   def call(conn, opts) do
-    frontend_type = Map.get(opts, :frontend_type, :primary)
-    path = file_path("", frontend_type)
-
-    if path do
-      conn
-      |> call_static(opts, path)
+    with false <- invalid_path?(conn.path_info),
+         frontend_type <- Map.get(opts, :frontend_type, :primary),
+         path when not is_nil(path) <- file_path("", frontend_type) do
+      call_static(conn, opts, path)
     else
-      conn
+      _ ->
+        conn
     end
   end
 
-  defp call_static(conn, opts, from) do
-    opts =
-      opts
-      |> Map.put(:from, from)
+  defp invalid_path?(list) do
+    invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
+  end
 
+  defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
+  defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
+  defp invalid_path?([], _match), do: false
+
+  defp call_static(conn, opts, from) do
+    opts = Map.put(opts, :from, from)
     Plug.Static.call(conn, opts)
   end
 end
index d2d93998965e8592cd72429abacd96b46a7e88d0..07a574f35f46553ab1eb9290503d3ab2f251fd26 100644 (file)
@@ -5,6 +5,26 @@
 defmodule Pleroma.Web.Router do
   use Pleroma.Web, :router
 
+  pipeline :accepts_html do
+    plug(:accepts, ["html"])
+  end
+
+  pipeline :accepts_html_xml do
+    plug(:accepts, ["html", "xml", "rss", "atom"])
+  end
+
+  pipeline :accepts_html_json do
+    plug(:accepts, ["html", "activity+json", "json"])
+  end
+
+  pipeline :accepts_html_xml_json do
+    plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
+  end
+
+  pipeline :accepts_xml_rss_atom do
+    plug(:accepts, ["xml", "rss", "atom"])
+  end
+
   pipeline :browser do
     plug(:accepts, ["html"])
     plug(:fetch_session)
@@ -566,30 +586,43 @@ defmodule Pleroma.Web.Router do
     )
   end
 
-  pipeline :ostatus do
-    plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
-    plug(Pleroma.Web.Plugs.StaticFEPlug)
-  end
-
-  pipeline :oembed do
-    plug(:accepts, ["json", "xml"])
-  end
-
   scope "/", Pleroma.Web do
-    pipe_through([:ostatus, :http_signature])
+    # Note: html format is supported only if static FE is enabled
+    # Note: http signature is only considered for json requests (no auth for non-json requests)
+    pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
 
     get("/objects/:uuid", OStatus.OStatusController, :object)
     get("/activities/:uuid", OStatus.OStatusController, :activity)
     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)
+  end
 
-    get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+  scope "/", Pleroma.Web do
+    # Note: html format is supported only if static FE is enabled
+    # Note: http signature is only considered for json requests (no auth for non-json requests)
+    pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+
+    # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
     get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+  end
+
+  scope "/", Pleroma.Web do
+    # Note: html format is supported only if static FE is enabled
+    pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
 
+    get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+  end
+
+  scope "/", Pleroma.Web do
+    pipe_through(:accepts_html)
+    get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
+  end
+
+  scope "/", Pleroma.Web do
+    pipe_through(:accepts_xml_rss_atom)
     get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
   end
 
index 687b17df6c18e67fa5441c031e6f4df71d1e0edf..bdec0897a6dac228a3916fae3a8630ae770628ea 100644 (file)
@@ -17,74 +17,14 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
   plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
   plug(:assign_id)
 
-  plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
-    unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
-  )
-
   @page_keys ["max_id", "min_id", "limit", "since_id", "order"]
 
-  defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
-    do: name
-
-  defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
-    do: summary
-
-  defp get_title(_), do: nil
-
-  defp not_found(conn, message) do
-    conn
-    |> put_status(404)
-    |> render("error.html", %{message: message, meta: ""})
-  end
-
-  defp get_counts(%Activity{} = activity) do
-    %Object{data: data} = Object.normalize(activity)
-
-    %{
-      likes: data["like_count"] || 0,
-      replies: data["repliesCount"] || 0,
-      announces: data["announcement_count"] || 0
-    }
-  end
-
-  defp represent(%Activity{} = activity), do: represent(activity, false)
-
-  defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
-    {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
-
-    link =
-      case user.local do
-        true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
-        _ -> data["url"] || data["external_url"] || data["id"]
-      end
-
-    content =
-      if data["content"] do
-        data["content"]
-        |> Pleroma.HTML.filter_tags()
-        |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
-      else
-        nil
-      end
-
-    %{
-      user: User.sanitize_html(user),
-      title: get_title(activity.object),
-      content: content,
-      attachment: data["attachment"],
-      link: link,
-      published: data["published"],
-      sensitive: data["sensitive"],
-      selected: selected,
-      counts: get_counts(activity),
-      id: activity.id
-    }
-  end
-
+  @doc "Renders requested local public activity or public activities of requested user"
   def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
     with %Activity{local: true} = activity <-
            Activity.get_by_id_with_object(notice_id),
          true <- Visibility.is_public?(activity.object),
+         {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
          %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
       meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
 
@@ -107,34 +47,35 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
   end
 
   def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
-    case User.get_cached_by_nickname_or_id(username_or_id) do
-      %User{} = user ->
-        meta = Metadata.build_tags(%{user: user})
-
-        params =
-          params
-          |> Map.take(@page_keys)
-          |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
-
-        timeline =
-          user
-          |> ActivityPub.fetch_user_activities(nil, params)
-          |> Enum.map(&represent/1)
-
-        prev_page_id =
-          (params["min_id"] || params["max_id"]) &&
-            List.first(timeline) && List.first(timeline).id
-
-        next_page_id = List.last(timeline) && List.last(timeline).id
-
-        render(conn, "profile.html", %{
-          user: User.sanitize_html(user),
-          timeline: timeline,
-          prev_page_id: prev_page_id,
-          next_page_id: next_page_id,
-          meta: meta
-        })
+    with {_, %User{local: true} = user} <-
+           {:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
+         {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
+      meta = Metadata.build_tags(%{user: user})
+
+      params =
+        params
+        |> Map.take(@page_keys)
+        |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
 
+      timeline =
+        user
+        |> ActivityPub.fetch_user_activities(_reading_user = nil, params)
+        |> Enum.map(&represent/1)
+
+      prev_page_id =
+        (params["min_id"] || params["max_id"]) &&
+          List.first(timeline) && List.first(timeline).id
+
+      next_page_id = List.last(timeline) && List.last(timeline).id
+
+      render(conn, "profile.html", %{
+        user: User.sanitize_html(user),
+        timeline: timeline,
+        prev_page_id: prev_page_id,
+        next_page_id: next_page_id,
+        meta: meta
+      })
+    else
       _ ->
         not_found(conn, "User not found.")
     end
@@ -166,6 +107,64 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
     end
   end
 
+  defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
+    do: name
+
+  defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
+    do: summary
+
+  defp get_title(_), do: nil
+
+  defp not_found(conn, message) do
+    conn
+    |> put_status(404)
+    |> render("error.html", %{message: message, meta: ""})
+  end
+
+  defp get_counts(%Activity{} = activity) do
+    %Object{data: data} = Object.normalize(activity)
+
+    %{
+      likes: data["like_count"] || 0,
+      replies: data["repliesCount"] || 0,
+      announces: data["announcement_count"] || 0
+    }
+  end
+
+  defp represent(%Activity{} = activity), do: represent(activity, false)
+
+  defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+    {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
+
+    link =
+      case user.local do
+        true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+        _ -> data["url"] || data["external_url"] || data["id"]
+      end
+
+    content =
+      if data["content"] do
+        data["content"]
+        |> Pleroma.HTML.filter_tags()
+        |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
+      else
+        nil
+      end
+
+    %{
+      user: User.sanitize_html(user),
+      title: get_title(activity.object),
+      content: content,
+      attachment: data["attachment"],
+      link: link,
+      published: data["published"],
+      sensitive: data["sensitive"],
+      selected: selected,
+      counts: get_counts(activity),
+      id: activity.id
+    }
+  end
+
   defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
     do: assign(conn, :notice_id, notice_id)
 
diff --git a/test/fixtures/mewmew_no_name.json b/test/fixtures/mewmew_no_name.json
new file mode 100644 (file)
index 0000000..532d4cf
--- /dev/null
@@ -0,0 +1,46 @@
+{
+   "@context" : [
+      "https://www.w3.org/ns/activitystreams",
+      "https://princess.cat/schemas/litepub-0.1.jsonld",
+      {
+         "@language" : "und"
+      }
+   ],
+   "attachment" : [],
+   "capabilities" : {
+      "acceptsChatMessages" : true
+   },
+   "discoverable" : false,
+   "endpoints" : {
+      "oauthAuthorizationEndpoint" : "https://princess.cat/oauth/authorize",
+      "oauthRegistrationEndpoint" : "https://princess.cat/api/v1/apps",
+      "oauthTokenEndpoint" : "https://princess.cat/oauth/token",
+      "sharedInbox" : "https://princess.cat/inbox",
+      "uploadMedia" : "https://princess.cat/api/ap/upload_media"
+   },
+   "followers" : "https://princess.cat/users/mewmew/followers",
+   "following" : "https://princess.cat/users/mewmew/following",
+   "icon" : {
+      "type" : "Image",
+      "url" : "https://princess.cat/media/12794fb50e86911e65be97f69196814049dcb398a2f8b58b99bb6591576e648c.png?name=blobcatpresentpink.png"
+   },
+   "id" : "https://princess.cat/users/mewmew",
+   "image" : {
+      "type" : "Image",
+      "url" : "https://princess.cat/media/05d8bf3953ab6028fc920494ffc643fbee9dcef40d7bdd06f107e19acbfbd7f9.png"
+   },
+   "inbox" : "https://princess.cat/users/mewmew/inbox",
+   "manuallyApprovesFollowers" : true,
+   "name" : " ",
+   "outbox" : "https://princess.cat/users/mewmew/outbox",
+   "preferredUsername" : "mewmew",
+   "publicKey" : {
+      "id" : "https://princess.cat/users/mewmew#main-key",
+      "owner" : "https://princess.cat/users/mewmew",
+      "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAru7VpygVef4zrFwnj0Mh\nrbO/2z2EdKN3rERtNrT8zWsLXNLQ50lfpRPnGDrd+xq7Rva4EIu0d5KJJ9n4vtY0\nuxK3On9vA2oyjLlR9O0lI3XTrHJborG3P7IPXrmNUMFpHiFHNqHp5tugUrs1gUFq\n7tmOmM92IP4Wjk8qNHFcsfnUbaPTX7sNIhteQKdi5HrTb/6lrEIe4G/FlMKRqxo3\nRNHuv6SNFQuiUKvFzjzazvjkjvBSm+aFROgdHa2tKl88StpLr7xmuY8qNFCRT6W0\nLacRp6c8ah5f03Kd+xCBVhCKvKaF1K0ERnQTBiitUh85md+Mtx/CoDoLnmpnngR3\nvQIDAQAB\n-----END PUBLIC KEY-----\n\n"
+   },
+   "summary" : "please reply to my posts as direct messages if you have many followers",
+   "tag" : [],
+   "type" : "Person",
+   "url" : "https://princess.cat/users/mewmew"
+}
index 7220ce84636914abdadfe129184a0ae14b8854a5..9ae52d594e0d961a7afc39d0c1834730a2b861b9 100644 (file)
@@ -388,6 +388,7 @@ defmodule Pleroma.UserTest do
     }
 
     setup do: clear_config([:instance, :autofollowed_nicknames])
+    setup do: clear_config([:instance, :autofollowing_nicknames])
     setup do: clear_config([:welcome])
     setup do: clear_config([:instance, :account_activation_required])
 
@@ -408,6 +409,23 @@ defmodule Pleroma.UserTest do
       refute User.following?(registered_user, remote_user)
     end
 
+    test "it adds automatic followers for new registered accounts" do
+      user1 = insert(:user)
+      user2 = insert(:user)
+
+      Pleroma.Config.put([:instance, :autofollowing_nicknames], [
+        user1.nickname,
+        user2.nickname
+      ])
+
+      cng = User.register_changeset(%User{}, @full_user_data)
+
+      {:ok, registered_user} = User.register(cng)
+
+      assert User.following?(user1, registered_user)
+      assert User.following?(user2, registered_user)
+    end
+
     test "it sends a welcome message if it is set" do
       welcome_user = insert(:user)
       Pleroma.Config.put([:welcome, :direct_message, :enabled], true)
index b11e2f9619defd0fe2fd42f4af3aaa000a3f9613..b696a24f499a61d3ca7501fe9d4cc10e54ff2ac7 100644 (file)
@@ -156,21 +156,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert response == "Not found"
     end
-
-    test "it requires authentication if instance is NOT federating", %{
-      conn: conn
-    } do
-      user = insert(:user)
-
-      conn =
-        put_req_header(
-          conn,
-          "accept",
-          "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
-        )
-
-      ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
-    end
   end
 
   describe "mastodon compatibility routes" do
@@ -338,18 +323,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert "Not found" == json_response(conn2, :not_found)
     end
-
-    test "it requires authentication if instance is NOT federating", %{
-      conn: conn
-    } do
-      user = insert(:user)
-      note = insert(:note)
-      uuid = String.split(note.data["id"], "/") |> List.last()
-
-      conn = put_req_header(conn, "accept", "application/activity+json")
-
-      ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
-    end
   end
 
   describe "/activities/:uuid" do
@@ -421,18 +394,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert "Not found" == json_response(conn2, :not_found)
     end
-
-    test "it requires authentication if instance is NOT federating", %{
-      conn: conn
-    } do
-      user = insert(:user)
-      activity = insert(:note_activity)
-      uuid = String.split(activity.data["id"], "/") |> List.last()
-
-      conn = put_req_header(conn, "accept", "application/activity+json")
-
-      ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
-    end
   end
 
   describe "/inbox" do
@@ -893,15 +854,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert response(conn, 200) =~ announce_activity.data["object"]
     end
-
-    test "it requires authentication if instance is NOT federating", %{
-      conn: conn
-    } do
-      user = insert(:user)
-      conn = put_req_header(conn, "accept", "application/activity+json")
-
-      ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
-    end
   end
 
   describe "POST /users/:nickname/outbox (C2S)" do
index 6ac883b2375d35e0ab311e4074faac1461afdecb..43bd14ee6f6de018089b9d8bd04ad37581340743 100644 (file)
@@ -2273,4 +2273,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert length(activities) == 2
     end
   end
+
+  test "allow fetching of accounts with an empty string name field" do
+    Tesla.Mock.mock(fn
+      %{method: :get, url: "https://princess.cat/users/mewmew"} ->
+        file = File.read!("test/fixtures/mewmew_no_name.json")
+        %Tesla.Env{status: 200, body: file}
+    end)
+
+    {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
+    assert user.name == " "
+  end
 end
index f954cc1e758fee0c14217d1dabf1acd41a37636b..875addc96521b737dcec41598989818f9eee0771 100644 (file)
@@ -38,7 +38,6 @@ defmodule Pleroma.Web.Endpoint.MetricsExporterTest do
       for metric <- [
             "http_requests_total",
             "http_request_duration_microseconds",
-            "phoenix_controller_render_duration",
             "phoenix_controller_call_duration",
             "telemetry_scrape_duration",
             "erlang_vm_memory_atom_bytes_total"
index 868e4096511320a93668f0fbdbf66064e0d1914b..e4084b0e554424b94956c80ff0cbd7c0abaa1cd1 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
   import Pleroma.Factory
   import SweetXml
 
+  alias Pleroma.Config
   alias Pleroma.Object
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Feed.FeedView
@@ -15,7 +16,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
   setup do: clear_config([:feed])
 
   test "gets a feed (ATOM)", %{conn: conn} do
-    Pleroma.Config.put(
+    Config.put(
       [:feed, :post_title],
       %{max_length: 25, omission: "..."}
     )
@@ -82,7 +83,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
   end
 
   test "gets a feed (RSS)", %{conn: conn} do
-    Pleroma.Config.put(
+    Config.put(
       [:feed, :post_title],
       %{max_length: 25, omission: "..."}
     )
@@ -157,7 +158,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
     response =
       conn
       |> put_req_header("accept", "application/rss+xml")
-      |> get(tag_feed_path(conn, :feed, "pleromaart"))
+      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
       |> response(200)
 
     xml = parse(response)
@@ -183,14 +184,12 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
   end
 
   describe "private instance" do
-    setup do: clear_config([:instance, :public])
+    setup do: clear_config([:instance, :public], false)
 
     test "returns 404 for tags feed", %{conn: conn} do
-      Config.put([:instance, :public], false)
-
       conn
       |> put_req_header("accept", "application/rss+xml")
-      |> get(tag_feed_path(conn, :feed, "pleromaart"))
+      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
       |> response(404)
     end
   end
index a5dc0894beab1534cd289bf24139a043157ae883..eabfe3a6383449a008b19796950f0f9eca193b4e 100644 (file)
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
 
-  setup do: clear_config([:instance, :federating], true)
+  setup do: clear_config([:static_fe, :enabled], false)
 
   describe "feed" do
     setup do: clear_config([:feed])
@@ -192,6 +192,16 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
              |> get(user_feed_path(conn, :feed, user.nickname))
              |> response(404)
     end
+
+    test "does not require authentication on non-federating instances", %{conn: conn} do
+      clear_config([:instance, :federating], false)
+      user = insert(:user)
+
+      conn
+      |> put_req_header("accept", "application/rss+xml")
+      |> get("/users/#{user.nickname}/feed.rss")
+      |> response(200)
+    end
   end
 
   # Note: see ActivityPubControllerTest for JSON format tests
index ee498f4b555ea10022ac07dbe91f2f6713f88900..65b2c22dbe45bb16ac9a5335f5a3f5ae8675603d 100644 (file)
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
 
   import Pleroma.Factory
 
-  alias Pleroma.Config
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -21,7 +20,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
     :ok
   end
 
-  setup do: clear_config([:instance, :federating], true)
+  setup do: clear_config([:static_fe, :enabled], false)
 
   describe "Mastodon compatibility routes" do
     setup %{conn: conn} do
@@ -215,15 +214,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
       assert response(conn, 404)
     end
 
-    test "it requires authentication if instance is NOT federating", %{
+    test "does not require authentication on non-federating instances", %{
       conn: conn
     } do
-      user = insert(:user)
+      clear_config([:instance, :federating], false)
       note_activity = insert(:note_activity)
 
-      conn = put_req_header(conn, "accept", "text/html")
-
-      ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user)
+      conn
+      |> put_req_header("accept", "text/html")
+      |> get("/notice/#{note_activity.id}")
+      |> response(200)
     end
   end
 
@@ -325,14 +325,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
       |> response(404)
     end
 
-    test "it requires authentication if instance is NOT federating", %{
+    test "does not require authentication on non-federating instances", %{
       conn: conn,
       note_activity: note_activity
     } do
-      user = insert(:user)
-      conn = put_req_header(conn, "accept", "text/html")
+      clear_config([:instance, :federating], false)
 
-      ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user)
+      conn
+      |> put_req_header("accept", "text/html")
+      |> get("/notice/#{note_activity.id}/embed_player")
+      |> response(200)
     end
   end
 end
index f6f7d7bdbec45e4382e9ae56a0d4e3d901e3847b..8b7b022fc871a8f5b364068159afb2424ca6a8a0 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
   use Pleroma.Web.ConnCase
+  import Mock
 
   @dir "test/tmp/instance_static"
 
@@ -53,4 +54,24 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
     index = get(conn, "/pleroma/admin/")
     assert html_response(index, 200) == "from frontend plug"
   end
+
+  test "exclude invalid path", %{conn: conn} do
+    name = "pleroma-fe"
+    ref = "dist"
+    clear_config([:media_proxy, :enabled], true)
+    clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+    clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+    path = "#{@dir}/frontends/#{name}/#{ref}"
+
+    File.mkdir_p!("#{path}/proxy/rr/ss")
+    File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image")
+
+    url =
+      Pleroma.Web.MediaProxy.encode_url("https://pbs.twimg.com/media/Ek7w8WPVcAApOvN.jpg:large")
+
+    with_mock Pleroma.ReverseProxy,
+      call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
+      assert %Plug.Conn{status: :success} = get(conn, url)
+    end
+  end
 end
index f819a1e52d8d74082d8d327e0a887527cc081d06..19506f1d8622996fba7c26ede5def15051253bc6 100644 (file)
@@ -6,14 +6,12 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.Activity
-  alias Pleroma.Config
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
 
   setup_all do: clear_config([:static_fe, :enabled], true)
-  setup do: clear_config([:instance, :federating], true)
 
   setup %{conn: conn} do
     conn = put_req_header(conn, "accept", "text/html")
@@ -74,8 +72,27 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
       refute html =~ ">test29<"
     end
 
-    test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
-      ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user)
+    test "does not require authentication on non-federating instances", %{
+      conn: conn,
+      user: user
+    } do
+      clear_config([:instance, :federating], false)
+
+      conn = get(conn, "/users/#{user.nickname}")
+
+      assert html_response(conn, 200) =~ user.nickname
+    end
+
+    test "returns 404 for local user with `restrict_unauthenticated/profiles/local` setting", %{
+      conn: conn
+    } do
+      clear_config([:restrict_unauthenticated, :profiles, :local], true)
+
+      local_user = insert(:user, local: true)
+
+      conn
+      |> get("/users/#{local_user.nickname}")
+      |> html_response(404)
     end
   end
 
@@ -187,10 +204,28 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
       assert html_response(conn, 302) =~ "redirected"
     end
 
-    test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
+    test "does not require authentication on non-federating instances", %{
+      conn: conn,
+      user: user
+    } do
+      clear_config([:instance, :federating], false)
+
+      {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
+
+      conn = get(conn, "/notice/#{activity.id}")
+
+      assert html_response(conn, 200) =~ "testing a thing!"
+    end
+
+    test "returns 404 for local public activity with `restrict_unauthenticated/activities/local` setting",
+         %{conn: conn, user: user} do
+      clear_config([:restrict_unauthenticated, :activities, :local], true)
+
       {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
 
-      ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user)
+      conn
+      |> get("/notice/#{activity.id}")
+      |> html_response(404)
     end
   end
 end
index 9316a82e49a1624eb8fd81b7c3a5ab02b2f55983..47cb65a80ba102febe685a2226c0f4153031be24 100644 (file)
@@ -112,28 +112,6 @@ defmodule Pleroma.Web.ConnCase do
       defp json_response_and_validate_schema(conn, _status) do
         flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
       end
-
-      defp ensure_federating_or_authenticated(conn, url, user) do
-        initial_setting = Config.get([:instance, :federating])
-        on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
-
-        Config.put([:instance, :federating], false)
-
-        conn
-        |> get(url)
-        |> response(403)
-
-        conn
-        |> assign(:user, user)
-        |> get(url)
-        |> response(200)
-
-        Config.put([:instance, :federating], true)
-
-        conn
-        |> get(url)
-        |> response(200)
-      end
     end
   end