diff options
Diffstat (limited to 'lib')
238 files changed, 2409 insertions, 1215 deletions
diff --git a/lib/mix/tasks/pleroma/count_statuses.ex b/lib/mix/tasks/pleroma/count_statuses.ex index e1e8195dd..8761d8f17 100644 --- a/lib/mix/tasks/pleroma/count_statuses.ex +++ b/lib/mix/tasks/pleroma/count_statuses.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Mix.Tasks.Pleroma.CountStatuses do    @shortdoc "Re-counts statuses for all users" diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex index 3595f912d..cac148b88 100644 --- a/lib/mix/tasks/pleroma/digest.ex +++ b/lib/mix/tasks/pleroma/digest.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Mix.Tasks.Pleroma.Digest do    use Mix.Task    import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 6088fc71d..ad5c37fc9 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Mix.Tasks.Pleroma.Docs do    use Mix.Task    import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto.ex index 3363cd45f..3363cd45f 100644 --- a/lib/mix/tasks/pleroma/ecto/ecto.ex +++ b/lib/mix/tasks/pleroma/ecto.ex diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index d3fac6ec8..bc5facc09 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -1,12 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Mix.Tasks.Pleroma.Email do    use Mix.Task    import Mix.Pleroma -  @shortdoc "Simple Email test" +  @shortdoc "Email administrative tasks"    @moduledoc File.read!("docs/administration/CLI_tasks/email.md")    def run(["test" | args]) do -    Mix.Pleroma.start_pleroma() +    start_pleroma()      {options, [], []} =        OptionParser.parse( @@ -21,4 +25,20 @@ defmodule Mix.Tasks.Pleroma.Email do      shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")    end + +  def run(["resend_confirmation_emails"]) do +    start_pleroma() + +    shell_info("Sending emails to all unconfirmed users") + +    Pleroma.User.Query.build(%{ +      local: true, +      deactivated: false, +      confirmation_pending: true, +      invisible: false +    }) +    |> Pleroma.Repo.chunk_stream(500) +    |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1)) +    |> Stream.run() +  end  end diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 91440b453..1915aacd9 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -33,7 +33,12 @@ defmodule Mix.Tasks.Pleroma.Instance do            uploads_dir: :string,            static_dir: :string,            listen_ip: :string, -          listen_port: :string +          listen_port: :string, +          strip_uploads: :string, +          anonymize_uploads: :string, +          dedupe_uploads: :string, +          skip_release_env: :boolean, +          release_env_file: :string          ],          aliases: [            o: :output, @@ -158,6 +163,30 @@ defmodule Mix.Tasks.Pleroma.Instance do          )          |> Path.expand() +      strip_uploads = +        get_option( +          options, +          :strip_uploads, +          "Do you want to strip location (GPS) data from uploaded images? (y/n)", +          "y" +        ) === "y" + +      anonymize_uploads = +        get_option( +          options, +          :anonymize_uploads, +          "Do you want to anonymize the filenames of uploads? (y/n)", +          "n" +        ) === "y" + +      dedupe_uploads = +        get_option( +          options, +          :dedupe_uploads, +          "Do you want to deduplicate uploaded files? (y/n)", +          "n" +        ) === "y" +        Config.put([:instance, :static_dir], static_dir)        secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) @@ -188,7 +217,13 @@ defmodule Mix.Tasks.Pleroma.Instance do            uploads_dir: uploads_dir,            rum_enabled: rum_enabled,            listen_ip: listen_ip, -          listen_port: listen_port +          listen_port: listen_port, +          upload_filters: +            upload_filters(%{ +              strip: strip_uploads, +              anonymize: anonymize_uploads, +              dedupe: dedupe_uploads +            })          )        result_psql = @@ -208,6 +243,24 @@ defmodule Mix.Tasks.Pleroma.Instance do        write_robots_txt(static_dir, indexable, template_dir) +      if Keyword.get(options, :skip_release_env, false) do +        shell_info(""" +        Release environment file is skip. Please generate the release env file before start. +        `MIX_ENV=#{Mix.env()} mix pleroma.release_env gen` +        """) +      else +        shell_info("Generation the environment file:") + +        release_env_args = +          with path when not is_nil(path) <- Keyword.get(options, :release_env_file) do +            ["gen", "--path", path] +          else +            _ -> ["gen"] +          end + +        Mix.Tasks.Pleroma.ReleaseEnv.run(release_env_args) +      end +        shell_info(          "\n All files successfully written! Refer to the installation instructions for your platform for next steps."        ) @@ -247,4 +300,31 @@ defmodule Mix.Tasks.Pleroma.Instance do      File.write(robots_txt_path, robots_txt)      shell_info("Writing #{robots_txt_path}.")    end + +  defp upload_filters(filters) when is_map(filters) do +    enabled_filters = +      if filters.strip do +        [Pleroma.Upload.Filter.ExifTool] +      else +        [] +      end + +    enabled_filters = +      if filters.anonymize do +        enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename] +      else +        enabled_filters +      end + +    enabled_filters = +      if filters.dedupe do +        enabled_filters ++ [Pleroma.Upload.Filter.Dedupe] +      else +        enabled_filters +      end + +    enabled_filters +  end + +  defp upload_filters(_), do: []  end diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex index 00f5ba7bf..f99275de1 100644 --- a/lib/mix/tasks/pleroma/notification_settings.ex +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Mix.Tasks.Pleroma.NotificationSettings do    @shortdoc "Enable&Disable privacy option for push notifications"    @moduledoc """ diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index a6d8d6c1c..bb808ca47 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -21,10 +21,19 @@ defmodule Mix.Tasks.Pleroma.Relay do      end    end -  def run(["unfollow", target]) do +  def run(["unfollow", target | rest]) do      start_pleroma() -    with {:ok, _activity} <- Relay.unfollow(target) do +    {options, [], []} = +      OptionParser.parse( +        rest, +        strict: [force: :boolean], +        aliases: [f: :force] +      ) + +    force = Keyword.get(options, :force, false) + +    with {:ok, _activity} <- Relay.unfollow(target, %{force: force}) do        # put this task to sleep to allow the genserver to push out the messages        :timer.sleep(500)      else diff --git a/lib/mix/tasks/pleroma/release_env.ex b/lib/mix/tasks/pleroma/release_env.ex new file mode 100644 index 000000000..9da74ffcf --- /dev/null +++ b/lib/mix/tasks/pleroma/release_env.ex @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.ReleaseEnv do +  use Mix.Task +  import Mix.Pleroma + +  @shortdoc "Generate Pleroma environment file." +  @moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md") + +  def run(["gen" | rest]) do +    {options, [], []} = +      OptionParser.parse( +        rest, +        strict: [ +          force: :boolean, +          path: :string +        ], +        aliases: [ +          p: :path, +          f: :force +        ] +      ) + +    file_path = +      get_option( +        options, +        :path, +        "Environment file path", +        "./config/pleroma.env" +      ) + +    env_path = Path.expand(file_path) + +    proceed? = +      if File.exists?(env_path) do +        get_option( +          options, +          :force, +          "Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)", +          "n" +        ) === "y" +      else +        true +      end + +    if proceed? do +      case do_generate(env_path) do +        {:error, reason} -> +          shell_error( +            File.Error.message(%{action: "write to file", reason: reason, path: env_path}) +          ) + +        _ -> +          shell_info("\nThe file generated: #{env_path}.\n") + +          shell_info(""" +          WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable. +            Example: +              chmod 0444 #{file_path} +              chattr +i #{file_path} +          """) +      end +    else +      shell_info("\nThe file is exist. #{env_path}.\n") +    end +  end + +  def do_generate(path) do +    content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}" + +    File.mkdir_p!(Path.dirname(path)) +    File.write(path, content) +  end +end diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robots_txt.ex index 24f08180e..24f08180e 100644 --- a/lib/mix/tasks/pleroma/robotstxt.ex +++ b/lib/mix/tasks/pleroma/robots_txt.ex diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index b20c49d89..a8d251411 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -196,17 +196,24 @@ defmodule Mix.Tasks.Pleroma.User do        OptionParser.parse(          rest,          strict: [ -          moderator: :boolean,            admin: :boolean, -          locked: :boolean +          confirmed: :boolean, +          locked: :boolean, +          moderator: :boolean          ]        )      with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do        user = -        case Keyword.get(options, :moderator) do +        case Keyword.get(options, :admin) do            nil -> user -          value -> set_moderator(user, value) +          value -> set_admin(user, value) +        end + +      user = +        case Keyword.get(options, :confirmed) do +          nil -> user +          value -> set_confirmed(user, value)          end        user = @@ -216,9 +223,9 @@ defmodule Mix.Tasks.Pleroma.User do          end        _user = -        case Keyword.get(options, :admin) do +        case Keyword.get(options, :moderator) do            nil -> user -          value -> set_admin(user, value) +          value -> set_moderator(user, value)          end      else        _ -> @@ -353,6 +360,42 @@ defmodule Mix.Tasks.Pleroma.User do      end    end +  def run(["confirm_all"]) do +    start_pleroma() + +    Pleroma.User.Query.build(%{ +      local: true, +      deactivated: false, +      is_moderator: false, +      is_admin: false, +      invisible: false +    }) +    |> Pleroma.Repo.chunk_stream(500, :batches) +    |> Stream.each(fn users -> +      users +      |> Enum.each(fn user -> User.need_confirmation(user, false) end) +    end) +    |> Stream.run() +  end + +  def run(["unconfirm_all"]) do +    start_pleroma() + +    Pleroma.User.Query.build(%{ +      local: true, +      deactivated: false, +      is_moderator: false, +      is_admin: false, +      invisible: false +    }) +    |> Pleroma.Repo.chunk_stream(500, :batches) +    |> Stream.each(fn users -> +      users +      |> Enum.each(fn user -> User.need_confirmation(user, true) end) +    end) +    |> Stream.run() +  end +    def run(["sign_out", nickname]) do      start_pleroma() @@ -376,7 +419,7 @@ defmodule Mix.Tasks.Pleroma.User do        |> Enum.each(fn user ->          shell_info(            "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{ -            user.locked +            user.is_locked            }, deactivated: #{user.deactivated}"          )        end) @@ -404,10 +447,17 @@ defmodule Mix.Tasks.Pleroma.User do    defp set_locked(user, value) do      {:ok, user} =        user -      |> Changeset.change(%{locked: value}) +      |> Changeset.change(%{is_locked: value})        |> User.update_and_set_cache() -    shell_info("Locked status of #{user.nickname}: #{user.locked}") +    shell_info("Locked status of #{user.nickname}: #{user.is_locked}") +    user +  end + +  defp set_confirmed(user, value) do +    {:ok, user} = User.need_confirmation(user, !value) + +    shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")      user    end  end diff --git a/lib/transports.ex b/lib/phoenix/transports/web_socket/raw.ex index aab7fad99..c3665bebe 100644 --- a/lib/transports.ex +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -31,7 +31,12 @@ defmodule Phoenix.Transports.WebSocket.Raw do      case conn do        %{halted: false} = conn -> -        case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do +        case handler.connect(%{ +               endpoint: endpoint, +               transport: transport, +               options: [serializer: nil], +               params: conn.params +             }) do            {:ok, socket} ->              {:ok, conn, {__MODULE__, {socket, opts}}} diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 17af04257..553834da0 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Activity do    alias Pleroma.ReportNote    alias Pleroma.ThreadMute    alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    import Ecto.Changeset    import Ecto.Query @@ -153,6 +154,18 @@ defmodule Pleroma.Activity do    def get_bookmark(_, _), do: nil +  def get_report(activity_id) do +    opts = %{ +      type: "Flag", +      skip_preload: true, +      preload_report_notes: true +    } + +    ActivityPub.fetch_activities_query([], opts) +    |> where(id: ^activity_id) +    |> Repo.one() +  end +    def change(struct, params \\ %{}) do      struct      |> cast(params, [:data, :recipients]) diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex index 9e65bedad..fe2e8cb5c 100644 --- a/lib/pleroma/activity/ir/topics.ex +++ b/lib/pleroma/activity/ir/topics.ex @@ -40,7 +40,8 @@ defmodule Pleroma.Activity.Ir.Topics do    end    defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do -    tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) +    tags ++ +      remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)    end    defp item_creation_tags(tags, _, _) do @@ -55,9 +56,19 @@ defmodule Pleroma.Activity.Ir.Topics do    defp hashtags_to_topics(_), do: [] +  defp remote_topics(%{local: true}), do: [] + +  defp remote_topics(%{actor: actor}) when is_binary(actor), +    do: ["public:remote:" <> URI.parse(actor).host] + +  defp remote_topics(_), do: [] +    defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []    defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"] +  defp attachment_topics(_object, %{actor: actor}) when is_binary(actor), +    do: ["public:media", "public:remote:media:" <> URI.parse(actor).host] +    defp attachment_topics(_object, _act), do: ["public:media"]  end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 00ec79a2a..7c4cd9626 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -52,11 +52,10 @@ defmodule Pleroma.Application do      Pleroma.HTML.compile_scrubbers()      Pleroma.Config.Oban.warn()      Config.DeprecationWarnings.warn() -    Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() +    Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()      Pleroma.ApplicationRequirements.verify!()      setup_instrumenters()      load_custom_modules() -    check_system_commands()      Pleroma.Docs.JSON.compile()      adapter = Application.get_env(:tesla, :adapter) @@ -89,18 +88,19 @@ defmodule Pleroma.Application do          Pleroma.Repo,          Config.TransferTask,          Pleroma.Emoji, -        Pleroma.Plugs.RateLimiter.Supervisor +        Pleroma.Web.Plugs.RateLimiter.Supervisor        ] ++          cachex_children() ++          http_children(adapter, @env) ++          [            Pleroma.Stats,            Pleroma.JobQueueMonitor, +          {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},            {Oban, Config.get(Oban)}          ] ++          task_children(@env) ++          dont_run_in_test(@env) ++ -        chat_child(@env, chat_enabled?()) ++ +        chat_child(chat_enabled?()) ++          [            Pleroma.Web.Endpoint,            Pleroma.Gopher.Server @@ -151,7 +151,10 @@ defmodule Pleroma.Application do      Pleroma.Web.Endpoint.MetricsExporter.setup()      Pleroma.Web.Endpoint.PipelineInstrumenter.setup() -    Pleroma.Web.Endpoint.Instrumenter.setup() + +    # Note: disabled until prometheus-phx is integrated into prometheus-phoenix: +    # Pleroma.Web.Endpoint.Instrumenter.setup() +    PrometheusPhx.setup()    end    defp cachex_children do @@ -165,7 +168,11 @@ defmodule Pleroma.Application do        build_cachex("web_resp", limit: 2500),        build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),        build_cachex("failed_proxy_url", limit: 2500), -      build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000) +      build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000), +      build_cachex("chat_message_id_idempotency_key", +        expiration: chat_message_id_idempotency_key_expiration(), +        limit: 500_000 +      )      ]    end @@ -175,6 +182,9 @@ defmodule Pleroma.Application do    defp idempotency_expiration,      do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60)) +  defp chat_message_id_idempotency_key_expiration, +    do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60)) +    defp seconds_valid_interval,      do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid])) @@ -202,11 +212,14 @@ defmodule Pleroma.Application do      ]    end -  defp chat_child(_env, true) do -    [Pleroma.Web.ChatChannel.ChatChannelState] +  defp chat_child(true) do +    [ +      Pleroma.Web.ChatChannel.ChatChannelState, +      {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]} +    ]    end -  defp chat_child(_, _), do: [] +  defp chat_child(_), do: []    defp task_children(:test) do      [ @@ -260,21 +273,4 @@ defmodule Pleroma.Application do    end    defp http_children(_, _), do: [] - -  defp check_system_commands do -    filters = Config.get([Pleroma.Upload, :filters]) - -    check_filter = fn filter, command_required -> -      with true <- filter in filters, -           false <- Pleroma.Utils.command_available?(command_required) do -        Logger.error( -          "#{filter} is specified in list of Pleroma.Upload filters, but the #{command_required} command is not found" -        ) -      end -    end - -    check_filter.(Pleroma.Upload.Filters.Exiftool, "exiftool") -    check_filter.(Pleroma.Upload.Filters.Mogrify, "mogrify") -    check_filter.(Pleroma.Upload.Filters.Mogrifun, "mogrify") -  end  end diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 16f62b6f5..b977257a3 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -9,6 +9,9 @@ defmodule Pleroma.ApplicationRequirements do    defmodule VerifyError, do: defexception([:message]) +  alias Pleroma.Config +  alias Pleroma.Helpers.MediaHelper +    import Ecto.Query    require Logger @@ -16,7 +19,8 @@ defmodule Pleroma.ApplicationRequirements do    @spec verify!() :: :ok | VerifyError.t()    def verify! do      :ok -    |> check_confirmation_accounts! +    |> check_system_commands!() +    |> check_confirmation_accounts!()      |> check_migrations_applied!()      |> check_welcome_message_config!()      |> check_rum!() @@ -48,7 +52,9 @@ defmodule Pleroma.ApplicationRequirements do      if Pleroma.Config.get([:instance, :account_activation_required]) &&           not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do        Logger.error( -        "Account activation enabled, but no Mailer settings enabled.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer." +        "Account activation enabled, but no Mailer settings enabled.\n" <> +          "Please set config :pleroma, :instance, account_activation_required: false\n" <> +          "Otherwise setup and enable Mailer."        )        {:error, @@ -81,7 +87,9 @@ defmodule Pleroma.ApplicationRequirements do                Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)              Logger.error( -              "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true" +              "The following migrations were not applied:\n#{down_migrations_text}" <> +                "If you want to start Pleroma anyway, set\n" <> +                "config :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"              )              {:error, "Unapplied Migrations detected"} @@ -124,14 +132,22 @@ defmodule Pleroma.ApplicationRequirements do      case {setting, migrate} do        {true, false} ->          Logger.error( -          "Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`" +          "Use `RUM` index is enabled, but were not applied migrations for it.\n" <> +            "If you want to start Pleroma anyway, set\n" <> +            "config :pleroma, :database, rum_enabled: false\n" <> +            "Otherwise apply the following migrations:\n" <> +            "`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"          )          {:error, "Unapplied RUM Migrations detected"}        {false, true} ->          Logger.error( -          "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`" +          "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\n" <> +            "If you want to use `RUM`, set\n" <> +            "config :pleroma, :database, rum_enabled: true\n" <> +            "Otherwise roll `RUM` migrations back.\n" <> +            "`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"          )          {:error, "RUM Migrations detected"} @@ -140,4 +156,50 @@ defmodule Pleroma.ApplicationRequirements do          :ok      end    end + +  defp check_system_commands!(:ok) do +    filter_commands_statuses = [ +      check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"), +      check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"), +      check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify") +    ] + +    preview_proxy_commands_status = +      if !Config.get([:media_preview_proxy, :enabled]) or +           MediaHelper.missing_dependencies() == [] do +        true +      else +        Logger.error( +          "The following dependencies required by Media preview proxy " <> +            "(which is currently enabled) are not installed: " <> +            inspect(MediaHelper.missing_dependencies()) +        ) + +        false +      end + +    if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do +      :ok +    else +      {:error, +       "System commands missing. Check logs and see `docs/installation` for more details."} +    end +  end + +  defp check_system_commands!(result), do: result + +  defp check_filter(filter, command_required) do +    filters = Config.get([Pleroma.Upload, :filters]) + +    if filter in filters and not Pleroma.Utils.command_available?(command_required) do +      Logger.error( +        "#{filter} is specified in list of Pleroma.Upload filters, but the " <> +          "#{command_required} command is not found" +      ) + +      false +    else +      true +    end +  end  end diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex index 815de7002..83ebb756d 100644 --- a/lib/pleroma/bbs/authenticator.ex +++ b/lib/pleroma/bbs/authenticator.ex @@ -4,8 +4,8 @@  defmodule Pleroma.BBS.Authenticator do    use Sshd.PasswordAuthenticator -  alias Pleroma.Plugs.AuthenticationPlug    alias Pleroma.User +  alias Pleroma.Web.Plugs.AuthenticationPlug    def authenticate(username, password) do      username = to_string(username) diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha.ex index 6ab754b6f..6ab754b6f 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha.ex diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 337506647..201b55ab4 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Captcha.Kocaptcha do    def new do      endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) -    case Tesla.get(endpoint <> "/new") do +    case Pleroma.HTTP.get(endpoint <> "/new") do        {:error, _} ->          %{error: :kocaptcha_service_unavailable} diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/service.ex index 959038cef..959038cef 100644 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/service.ex diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 84f8806a0..28007cd9f 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Chat do    It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.    """ +  @type t :: %__MODULE__{}    @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}    schema "chats" do @@ -41,16 +42,28 @@ defmodule Pleroma.Chat do      |> unique_constraint(:user_id, name: :chats_user_id_recipient_index)    end +  @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) :: +          {:ok, t()} | {:error, :not_found} +  def get_by_user_and_id(%User{id: user_id}, id) do +    from(c in __MODULE__, +      where: c.id == ^id, +      where: c.user_id == ^user_id +    ) +    |> Repo.find_resource() +  end + +  @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil    def get_by_id(id) do -    __MODULE__ -    |> Repo.get(id) +    Repo.get(__MODULE__, id)    end +  @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil    def get(user_id, recipient) do -    __MODULE__ -    |> Repo.get_by(user_id: user_id, recipient: recipient) +    Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient)    end +  @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: +          {:ok, t()} | {:error, Ecto.Changeset.t()}    def get_or_create(user_id, recipient) do      %__MODULE__{}      |> changeset(%{user_id: user_id, recipient: recipient}) @@ -62,6 +75,8 @@ defmodule Pleroma.Chat do      )    end +  @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: +          {:ok, t()} | {:error, Ecto.Changeset.t()}    def bump_or_create(user_id, recipient) do      %__MODULE__{}      |> changeset(%{user_id: user_id, recipient: recipient}) diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 98c4dc9c8..59c6b0f58 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -33,39 +33,14 @@ defmodule Pleroma.Config.DeprecationWarnings do      end    end -  def mrf_user_allowlist do -    config = Config.get(:mrf_user_allowlist) - -    if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do -      rewritten = -        Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc -> -          Map.put(acc, to_string(k), v) -        end) - -      Config.put(:mrf_user_allowlist, rewritten) - -      Logger.error(""" -      !!!DEPRECATION WARNING!!! -      As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format. -      Pleroma 2.1 will remove support for the old format. Please change your configuration to match this: - -      config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} -      """) - -      :error -    else -      :ok -    end -  end -    def warn do      with :ok <- check_hellthread_threshold(), -         :ok <- mrf_user_allowlist(),           :ok <- check_old_mrf_config(),           :ok <- check_media_proxy_whitelist_config(),           :ok <- check_welcome_message_config(),           :ok <- check_gun_pool_options(), -         :ok <- check_activity_expiration_config() do +         :ok <- check_activity_expiration_config(), +         :ok <- check_remote_ip_plug_name() do        :ok      else        _ -> @@ -83,9 +58,9 @@ defmodule Pleroma.Config.DeprecationWarnings do      if use_old_config do        Logger.error("""        !!!DEPRECATION WARNING!!! -      Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace: -      \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` -      \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` +      Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g., +      \n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to: +      \n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`"        """)        :error @@ -148,7 +123,7 @@ defmodule Pleroma.Config.DeprecationWarnings do      if timeout = pool_config[:await_up_timeout] do        Logger.warn("""        !!!DEPRECATION WARNING!!! -      Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. +      Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases.        """)        Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout)) @@ -202,4 +177,20 @@ defmodule Pleroma.Config.DeprecationWarnings do        warning_preface      )    end + +  @spec check_remote_ip_plug_name() :: :ok | nil +  def check_remote_ip_plug_name do +    warning_preface = """ +    !!!DEPRECATION WARNING!!! +    Your config is using old namespace for RemoteIp Plug. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later: +    """ + +    move_namespace_and_warn( +      [ +        {Pleroma.Plugs.RemoteIp, Pleroma.Web.Plugs.RemoteIp, +         "\n* `config :pleroma, Pleroma.Plugs.RemoteIp` is now `config :pleroma, Pleroma.Web.Plugs.RemoteIp`"} +      ], +      warning_preface +    ) +  end  end diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index 9f601b1a3..8e0351d52 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Config.Oban do    require Logger diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config_db.ex index e5b7811aa..e5b7811aa 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config_db.ex diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index e76eb0087..77933f0be 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -43,7 +43,7 @@ defmodule Pleroma.Conversation do    def maybe_create_recipientships(participation, activity) do      participation = Repo.preload(participation, :recipients) -    if participation.recipients |> Enum.empty?() do +    if Enum.empty?(participation.recipients) do        recipients = User.get_all_by_ap_id(activity.recipients)        RecipientShip.create(recipients, participation)      end @@ -69,10 +69,6 @@ defmodule Pleroma.Conversation do          Enum.map(users, fn user ->            invisible_conversation = Enum.any?(users, &User.blocks?(user, &1)) -          unless invisible_conversation do -            User.increment_unread_conversation_count(conversation, user) -          end -            opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)            {:ok, participation} = diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 8bc3e85d6..4c32b273a 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -63,21 +63,10 @@ defmodule Pleroma.Conversation.Participation do      end    end -  def mark_as_read(participation) do -    __MODULE__ -    |> where(id: ^participation.id) -    |> update(set: [read: true]) -    |> select([p], p) -    |> Repo.update_all([]) -    |> case do -      {1, [participation]} -> -        participation = Repo.preload(participation, :user) -        User.set_unread_conversation_count(participation.user) -        {:ok, participation} - -      error -> -        error -    end +  def mark_as_read(%__MODULE__{} = participation) do +    participation +    |> change(read: true) +    |> Repo.update()    end    def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do @@ -93,7 +82,6 @@ defmodule Pleroma.Conversation.Participation do      |> update([p], set: [read: true])      |> Repo.update_all([]) -    {:ok, user} = User.set_unread_conversation_count(user)      {:ok, user, []}    end @@ -108,7 +96,6 @@ defmodule Pleroma.Conversation.Participation do        |> select([p], p)        |> Repo.update_all([]) -    {:ok, user} = User.set_unread_conversation_count(user)      {:ok, user, participations}    end @@ -220,6 +207,12 @@ defmodule Pleroma.Conversation.Participation do      {:ok, Repo.preload(participation, :recipients, force: true)}    end +  @spec unread_count(User.t()) :: integer() +  def unread_count(%User{id: user_id}) do +    from(q in __MODULE__, where: q.user_id == ^user_id and q.read == false) +    |> Repo.aggregate(:count, :id) +  end +    def unread_conversation_count_for_user(user) do      from(p in __MODULE__,        where: p.user_id == ^user.id, diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation/recipient_ship.ex index de40bacac..de40bacac 100644 --- a/lib/pleroma/conversation/participation_recipient_ship.ex +++ b/lib/pleroma/conversation/participation/recipient_ship.ex diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index a671a6278..a70f83b73 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Docs.Generator do    @callback process(keyword()) :: {:ok, String.t()} diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index feeb4320e..13618b509 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Docs.JSON do    @behaviour Pleroma.Docs.Generator    @external_resource "config/description.exs" diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index da3f20f43..eac0789a6 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Docs.Markdown do    @behaviour Pleroma.Docs.Generator diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index c27ad1065..8979db2f8 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Emails.AdminEmail do      html_body = """      <p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>      <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote> -    <a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a> +    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>      """      new() diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 1d8c72ae9..806a61fd2 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -189,4 +189,30 @@ defmodule Pleroma.Emails.UserEmail do      Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)    end + +  def backup_is_ready_email(backup, admin_user_id \\ nil) do +    %{user: user} = Pleroma.Repo.preload(backup, :user) +    download_url = Pleroma.Web.PleromaAPI.BackupView.download_url(backup) + +    html_body = +      if is_nil(admin_user_id) do +        """ +        <p>You requested a full backup of your Pleroma account. It's ready for download:</p> +        <p><a href="#{download_url}">#{download_url}</a></p> +        """ +      else +        admin = Pleroma.Repo.get(User, admin_user_id) + +        """ +        <p>Admin @#{admin.nickname} requested a full backup of your Pleroma account. It's ready for download:</p> +        <p><a href="#{download_url}">#{download_url}</a></p> +        """ +      end + +    new() +    |> to(recipient(user)) +    |> from(sender()) +    |> subject("Your account archive is ready") +    |> html_body(html_body) +  end  end diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 0b3f8f00b..ca58e5432 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Emoji.Pack do    @derive {Jason.Encoder, only: [:files, :pack, :files_count]}    defstruct files: %{}, @@ -198,13 +202,13 @@ defmodule Pleroma.Emoji.Pack do      end    end -  @spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()} -  def list_remote(url) do -    uri = url |> String.trim() |> URI.parse() +  @spec list_remote(keyword()) :: {:ok, map()} | {:error, atom()} +  def list_remote(opts) do +    uri = opts[:url] |> String.trim() |> URI.parse()      with :ok <- validate_shareable_packs_available(uri) do        uri -      |> URI.merge("/api/pleroma/emoji/packs") +      |> URI.merge("/api/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}")        |> http_get()      end    end @@ -244,7 +248,8 @@ defmodule Pleroma.Emoji.Pack do      uri = url |> String.trim() |> URI.parse()      with :ok <- validate_shareable_packs_available(uri), -         {:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(), +         {:ok, remote_pack} <- +           uri |> URI.merge("/api/pleroma/emoji/pack?name=#{name}") |> http_get(),           {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),           {:ok, archive} <- download_archive(url, sha),           pack <- copy_as(remote_pack, as || name), @@ -523,7 +528,7 @@ defmodule Pleroma.Emoji.Pack do    defp http_get(%URI{} = url), do: url |> to_string() |> http_get()    defp http_get(url) do -    with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do +    with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], pool: :default) do        Jason.decode(body)      end    end @@ -572,7 +577,7 @@ defmodule Pleroma.Emoji.Pack do          {:ok,           %{             sha: sha, -           url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string() +           url: URI.merge(uri, "/api/pleroma/emoji/packs/archive?name=#{name}") |> to_string()           }}        %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> @@ -589,7 +594,7 @@ defmodule Pleroma.Emoji.Pack do    end    defp download_archive(url, sha) do -    with {:ok, %{body: archive}} <- Tesla.get(url) do +    with {:ok, %{body: archive}} <- Pleroma.HTTP.get(url) do        if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do          {:ok, archive}        else @@ -612,7 +617,7 @@ defmodule Pleroma.Emoji.Pack do    end    defp update_sha_and_save_metadata(pack, data) do -    with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]), +    with {:ok, %{body: zip}} <- Pleroma.HTTP.get(data[:"fallback-src"]),           :ok <- validate_has_all_files(pack, zip) do        fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16() diff --git a/lib/pleroma/gun/gun.ex b/lib/pleroma/gun.ex index 4043e4880..4043e4880 100644 --- a/lib/pleroma/gun/gun.ex +++ b/lib/pleroma/gun.ex diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex index f34602b73..e322f192a 100644 --- a/lib/pleroma/gun/connection_pool.ex +++ b/lib/pleroma/gun/connection_pool.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Gun.ConnectionPool do    @registry __MODULE__ diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex index cea800882..241e8b04f 100644 --- a/lib/pleroma/gun/connection_pool/reclaimer.ex +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Gun.ConnectionPool.Reclaimer do    use GenServer, restart: :temporary diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index bf57e9e5f..b71816bed 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Gun.ConnectionPool.Worker do    alias Pleroma.Gun    use GenServer, restart: :temporary diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index 39615c956..4c23bcbd9 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do    @moduledoc "Supervisor for pool workers. Does not do anything except enforce max connection limit" diff --git a/lib/pleroma/helpers/inet_helper.ex b/lib/pleroma/helpers/inet_helper.ex new file mode 100644 index 000000000..126f82381 --- /dev/null +++ b/lib/pleroma/helpers/inet_helper.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.InetHelper do +  def parse_address(ip) when is_tuple(ip) do +    {:ok, ip} +  end + +  def parse_address(ip) when is_binary(ip) do +    ip +    |> String.to_charlist() +    |> parse_address() +  end + +  def parse_address(ip) do +    :inet.parse_address(ip) +  end +end diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index b6f35a24b..6b799173e 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -9,6 +9,18 @@ defmodule Pleroma.Helpers.MediaHelper do    alias Pleroma.HTTP +  require Logger + +  def missing_dependencies do +    Enum.reduce([imagemagick: "convert", ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> +      if Pleroma.Utils.command_available?(executable) do +        acc +      else +        [sym | acc] +      end +    end) +  end +    def image_resize(url, options) do      with executable when is_binary(executable) <- System.find_executable("convert"),           {:ok, args} <- prepare_image_resize_args(options), diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http.ex index 052597191..052597191 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http.ex diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex index e13441316..8567a616b 100644 --- a/lib/pleroma/http/adapter_helper/default.ex +++ b/lib/pleroma/http/adapter_helper/default.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.HTTP.AdapterHelper.Default do    alias Pleroma.HTTP.AdapterHelper diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index ef84553c1..ff60513fd 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.HTTP.AdapterHelper.Hackney do    @behaviour Pleroma.HTTP.AdapterHelper diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex new file mode 100644 index 000000000..78148a12e --- /dev/null +++ b/lib/pleroma/http/web_push.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.WebPush do +  @moduledoc false + +  def post(url, payload, headers) do +    list_headers = Map.to_list(headers) +    Pleroma.HTTP.post(url, payload, list_headers) +  end +end diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex index 557e8decf..7315bd7cb 100644 --- a/lib/pleroma/instances.ex +++ b/lib/pleroma/instances.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Instances do    defdelegate reachable?(url_or_host), to: @adapter    defdelegate set_reachable(url_or_host), to: @adapter    defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter +  defdelegate get_consistently_unreachable(), to: @adapter    def set_consistently_unreachable(url_or_host),      do: set_unreachable(url_or_host, reachability_datetime_threshold()) diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index f0f601469..df471a39d 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -119,6 +119,17 @@ defmodule Pleroma.Instances.Instance do    def set_unreachable(_, _), do: {:error, nil} +  def get_consistently_unreachable do +    reachability_datetime_threshold = Instances.reachability_datetime_threshold() + +    from(i in Instance, +      where: ^reachability_datetime_threshold > i.unreachable_since, +      order_by: i.unreachable_since, +      select: {i.host, i.unreachable_since} +    ) +    |> Repo.all() +  end +    defp parse_datetime(datetime) when is_binary(datetime) do      NaiveDateTime.from_iso8601(datetime)    end diff --git a/lib/pleroma/jwt.ex b/lib/pleroma/jwt.ex index 10102ff5d..faeb77781 100644 --- a/lib/pleroma/jwt.ex +++ b/lib/pleroma/jwt.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.JWT do    use Joken.Config diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex deleted file mode 100644 index 6ee055f50..000000000 --- a/lib/pleroma/mime.ex +++ /dev/null @@ -1,120 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.MIME do -  @moduledoc """ -  Returns the mime-type of a binary and optionally a normalized file-name. -  """ -  @default "application/octet-stream" -  @read_bytes 35 - -  @spec file_mime_type(String.t(), String.t()) :: -          {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error -  def file_mime_type(path, filename) do -    with {:ok, content_type} <- file_mime_type(path), -         filename <- fix_extension(filename, content_type) do -      {:ok, content_type, filename} -    end -  end - -  @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error -  def file_mime_type(filename) do -    File.open(filename, [:read], fn f -> -      check_mime_type(IO.binread(f, @read_bytes)) -    end) -  end - -  def bin_mime_type(binary, filename) do -    with {:ok, content_type} <- bin_mime_type(binary), -         filename <- fix_extension(filename, content_type) do -      {:ok, content_type, filename} -    end -  end - -  @spec bin_mime_type(binary()) :: {:ok, String.t()} | :error -  def bin_mime_type(<<head::binary-size(@read_bytes), _::binary>>) do -    {:ok, check_mime_type(head)} -  end - -  def bin_mime_type(_), do: :error - -  def mime_type(<<_::binary>>), do: {:ok, @default} - -  defp fix_extension(filename, content_type) do -    parts = String.split(filename, ".") - -    new_filename = -      if length(parts) > 1 do -        Enum.drop(parts, -1) |> Enum.join(".") -      else -        Enum.join(parts) -      end - -    cond do -      content_type == "application/octet-stream" -> -        filename - -      ext = List.first(MIME.extensions(content_type)) -> -        new_filename <> "." <> ext - -      true -> -        Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".") -    end -  end - -  defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do -    "image/png" -  end - -  defp check_mime_type(<<0x47, 0x49, 0x46, 0x38, _, 0x61, _::binary>>) do -    "image/gif" -  end - -  defp check_mime_type(<<0xFF, 0xD8, 0xFF, _::binary>>) do -    "image/jpeg" -  end - -  defp check_mime_type(<<0x1A, 0x45, 0xDF, 0xA3, _::binary>>) do -    "video/webm" -  end - -  defp check_mime_type(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do -    "video/mp4" -  end - -  defp check_mime_type(<<0x49, 0x44, 0x33, _::binary>>) do -    "audio/mpeg" -  end - -  defp check_mime_type(<<255, 251, _, 68, 0, 0, 0, 0, _::binary>>) do -    "audio/mpeg" -  end - -  defp check_mime_type( -         <<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::size(160), 0x80, 0x74, 0x68, 0x65, -           0x6F, 0x72, 0x61, _::binary>> -       ) do -    "video/ogg" -  end - -  defp check_mime_type(<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::binary>>) do -    "audio/ogg" -  end - -  defp check_mime_type(<<"RIFF", _::binary-size(4), "WAVE", _::binary>>) do -    "audio/wav" -  end - -  defp check_mime_type(<<"RIFF", _::binary-size(4), "WEBP", _::binary>>) do -    "image/webp" -  end - -  defp check_mime_type(<<"RIFF", _::binary-size(4), "AVI.", _::binary>>) do -    "video/avi" -  end - -  defp check_mime_type(_) do -    @default -  end -end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 47036a6f6..142dd8e0a 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.ModerationLog do    use Ecto.Schema @@ -651,6 +655,16 @@ defmodule Pleroma.ModerationLog do      "@#{actor_nickname} deleted chat message ##{subject_id}"    end +  def get_log_entry_message(%ModerationLog{ +        data: %{ +          "actor" => %{"nickname" => actor_nickname}, +          "action" => "create_backup", +          "subject" => %{"nickname" => user_nickname} +        } +      }) do +    "@#{actor_nickname} requested account backup for @#{user_nickname}" +  end +    defp nicknames_to_string(nicknames) do      nicknames      |> Enum.map(&"@#{&1}") diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex deleted file mode 100644 index 0ac9050d0..000000000 --- a/lib/pleroma/plugs/remote_ip.ex +++ /dev/null @@ -1,54 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Plugs.RemoteIp do -  @moduledoc """ -  This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. -  """ - -  import Plug.Conn - -  @behaviour Plug - -  @headers ~w[ -    x-forwarded-for -  ] - -  # https://en.wikipedia.org/wiki/Localhost -  # https://en.wikipedia.org/wiki/Private_network -  @reserved ~w[ -    127.0.0.0/8 -    ::1/128 -    fc00::/7 -    10.0.0.0/8 -    172.16.0.0/12 -    192.168.0.0/16 -  ] - -  def init(_), do: nil - -  def call(%{remote_ip: original_remote_ip} = conn, _) do -    config = Pleroma.Config.get(__MODULE__, []) - -    if Keyword.get(config, :enabled, false) do -      %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) -      assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) -    else -      conn -    end -  end - -  defp remote_ip_opts(config) do -    headers = config |> Keyword.get(:headers, @headers) |> MapSet.new() -    reserved = Keyword.get(config, :reserved, @reserved) - -    proxies = -      config -      |> Keyword.get(:proxies, []) -      |> Enum.concat(reserved) -      |> Enum.map(&InetCidr.parse/1) - -    {headers, proxies} -  end -end diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index 8ae1157df..8ae1157df 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 197b1d091..003079cf3 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Telemetry.Logger do    @moduledoc "Transforms Pleroma telemetry events to logs" diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex index fb04411d9..b30d83567 100644 --- a/lib/pleroma/tests/auth_test_controller.ex +++ b/lib/pleroma/tests/auth_test_controller.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Tests.AuthTestController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    # Serves only with proper OAuth token (:api and :authenticated_api)    # Skipping EnsurePublicOrAuthenticatedPlug has no effect in this case diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 015c87593..db2cc1dae 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -66,6 +66,7 @@ defmodule Pleroma.Upload do    end    @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} +  @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct."    def store(upload, opts \\ []) do      opts = get_opts(opts) @@ -139,14 +140,13 @@ defmodule Pleroma.Upload do    end    defp prepare_upload(%Plug.Upload{} = file, opts) do -    with :ok <- check_file_size(file.path, opts.size_limit), -         {:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do +    with :ok <- check_file_size(file.path, opts.size_limit) do        {:ok,         %__MODULE__{           id: UUID.generate(), -         name: name, +         name: file.filename,           tempfile: file.path, -         content_type: content_type +         content_type: file.content_type         }}      end    end @@ -154,16 +154,17 @@ defmodule Pleroma.Upload do    defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do      parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)      data = Base.decode64!(parsed["data"], ignore: :whitespace) -    hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data))) +    hash = Base.encode16(:crypto.hash(:sha256, data), lower: true)      with :ok <- check_binary_size(data, opts.size_limit),           tmp_path <- tempfile_for_image(data), -         {:ok, content_type, name} <- -           Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do +         {:ok, %{mime_type: content_type}} <- +           Majic.perform({:bytes, data}, pool: Pleroma.MajicPool), +         [ext | _] <- MIME.extensions(content_type) do        {:ok,         %__MODULE__{           id: UUID.generate(), -         name: name, +         name: hash <> "." <> ext,           tempfile: tmp_path,           content_type: content_type         }} @@ -172,7 +173,7 @@ defmodule Pleroma.Upload do    # For Mix.Tasks.MigrateLocalUploads    defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do -    with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do +    with {:ok, %{mime_type: content_type}} <- Majic.perform(path, pool: Pleroma.MajicPool) do        {:ok, %__MODULE__{upload | content_type: content_type}}      end    end diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index 9a94534e9..6249eceb1 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Uploaders.Uploader do    @doc """    Instructs how to get the file from the backend. -  Used by `Pleroma.Plugs.UploadedMedia`. +  Used by `Pleroma.Web.Plugs.UploadedMedia`.    """    @type get_method :: {:static_dir, directory :: String.t()} | {:url, url :: String.t()}    @callback get_file(file :: String.t()) :: {:ok, get_method()} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 5dad7285a..8e4ec8064 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -107,7 +107,7 @@ defmodule Pleroma.User do      field(:note_count, :integer, default: 0)      field(:follower_count, :integer, default: 0)      field(:following_count, :integer, default: 0) -    field(:locked, :boolean, default: false) +    field(:is_locked, :boolean, default: false)      field(:confirmation_pending, :boolean, default: false)      field(:password_reset_pending, :boolean, default: false)      field(:approval_pending, :boolean, default: false) @@ -128,7 +128,6 @@ defmodule Pleroma.User do      field(:hide_followers, :boolean, default: false)      field(:hide_follows, :boolean, default: false)      field(:hide_favorites, :boolean, default: true) -    field(:unread_conversation_count, :integer, default: 0)      field(:pinned_activities, {:array, :string}, default: [])      field(:email_notifications, :map, default: %{"digest" => false})      field(:mascot, :map, default: nil) @@ -136,7 +135,7 @@ defmodule Pleroma.User do      field(:pleroma_settings_store, :map, default: %{})      field(:fields, {:array, :map}, default: [])      field(:raw_fields, {:array, :map}, default: []) -    field(:discoverable, :boolean, default: false) +    field(:is_discoverable, :boolean, default: false)      field(:invisible, :boolean, default: false)      field(:allow_following_move, :boolean, default: true)      field(:skip_thread_containment, :boolean, default: false) @@ -426,7 +425,6 @@ defmodule Pleroma.User do        params,        [          :bio, -        :name,          :emoji,          :ap_id,          :inbox, @@ -436,7 +434,7 @@ defmodule Pleroma.User do          :avatar,          :ap_enabled,          :banner, -        :locked, +        :is_locked,          :last_refreshed_at,          :uri,          :follower_address, @@ -448,14 +446,16 @@ defmodule Pleroma.User do          :follower_count,          :fields,          :following_count, -        :discoverable, +        :is_discoverable,          :invisible,          :actor_type,          :also_known_as,          :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) @@ -479,7 +479,7 @@ defmodule Pleroma.User do          :public_key,          :inbox,          :shared_inbox, -        :locked, +        :is_locked,          :no_rich_text,          :default_scope,          :banner, @@ -495,7 +495,7 @@ defmodule Pleroma.User do          :fields,          :raw_fields,          :pleroma_settings_store, -        :discoverable, +        :is_discoverable,          :actor_type,          :also_known_as,          :accepts_chat_messages @@ -765,6 +765,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 +784,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), @@ -813,7 +824,8 @@ defmodule Pleroma.User do    def send_welcome_email(_), do: {:ok, :noop}    @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} -  def try_send_confirmation_email(%User{confirmation_pending: true} = user) do +  def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user) +      when is_binary(email) do      if Config.get([:instance, :account_activation_required]) do        send_confirmation_email(user)        {:ok, :enqueued} @@ -846,7 +858,7 @@ defmodule Pleroma.User do    @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}    # "Locked" (self-locked) users demand explicit authorization of follow requests -  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do +  def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do      follow(follower, followed, :follow_pending)    end @@ -914,9 +926,7 @@ defmodule Pleroma.User do          FollowingRelationship.unfollow(follower, followed)          {:ok, followed} = update_follower_count(followed) -        {:ok, follower} = -          follower -          |> update_following_count() +        {:ok, follower} = update_following_count(follower)          {:ok, follower, followed} @@ -955,7 +965,7 @@ defmodule Pleroma.User do    end    def locked?(%User{} = user) do -    user.locked || false +    user.is_locked || false    end    def get_by_id(id) do @@ -1294,47 +1304,6 @@ defmodule Pleroma.User do      |> update_and_set_cache()    end -  def set_unread_conversation_count(%User{local: true} = user) do -    unread_query = Participation.unread_conversation_count_for_user(user) - -    User -    |> join(:inner, [u], p in subquery(unread_query)) -    |> update([u, p], -      set: [unread_conversation_count: p.count] -    ) -    |> where([u], u.id == ^user.id) -    |> select([u], u) -    |> Repo.update_all([]) -    |> case do -      {1, [user]} -> set_cache(user) -      _ -> {:error, user} -    end -  end - -  def set_unread_conversation_count(user), do: {:ok, user} - -  def increment_unread_conversation_count(conversation, %User{local: true} = user) do -    unread_query = -      Participation.unread_conversation_count_for_user(user) -      |> where([p], p.conversation_id == ^conversation.id) - -    User -    |> join(:inner, [u], p in subquery(unread_query)) -    |> update([u, p], -      inc: [unread_conversation_count: 1] -    ) -    |> where([u], u.id == ^user.id) -    |> where([u, p], p.count == 0) -    |> select([u], u) -    |> Repo.update_all([]) -    |> case do -      {1, [user]} -> set_cache(user) -      _ -> {:error, user} -    end -  end - -  def increment_unread_conversation_count(_, user), do: {:ok, user} -    @spec get_users_from_set([String.t()], keyword()) :: [User.t()]    def get_users_from_set(ap_ids, opts \\ []) do      local_only = Keyword.get(opts, :local_only, true) @@ -1636,7 +1605,7 @@ defmodule Pleroma.User do        note_count: 0,        follower_count: 0,        following_count: 0, -      locked: false, +      is_locked: false,        confirmation_pending: false,        password_reset_pending: false,        approval_pending: false, @@ -1653,7 +1622,7 @@ defmodule Pleroma.User do        pleroma_settings_store: %{},        fields: [],        raw_fields: [], -      discoverable: false, +      is_discoverable: false,        also_known_as: []      })    end @@ -2105,6 +2074,13 @@ defmodule Pleroma.User do      Enum.map(users, &toggle_confirmation/1)    end +  @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} +  def need_confirmation(%User{} = user, bool) do +    user +    |> confirmation_changeset(need_confirmation: bool) +    |> update_and_set_cache() +  end +    def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do      mascot    end @@ -2319,7 +2295,9 @@ defmodule Pleroma.User do      # if pinned activity was scheduled for deletion, we reschedule it for deletion      if data["expires_at"] do -      {:ok, expires_at, _} = DateTime.from_iso8601(data["expires_at"]) +      # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation +      {:ok, expires_at} = +        data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()        Pleroma.Workers.PurgeExpiredActivity.enqueue(%{          activity_id: id, diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex new file mode 100644 index 000000000..a9041fd94 --- /dev/null +++ b/lib/pleroma/user/backup.ex @@ -0,0 +1,258 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.Backup do +  use Ecto.Schema + +  import Ecto.Changeset +  import Ecto.Query +  import Pleroma.Web.Gettext + +  require Pleroma.Constants + +  alias Pleroma.Activity +  alias Pleroma.Bookmark +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.ActivityPub.UserView +  alias Pleroma.Workers.BackupWorker + +  schema "backups" do +    field(:content_type, :string) +    field(:file_name, :string) +    field(:file_size, :integer, default: 0) +    field(:processed, :boolean, default: false) + +    belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + +    timestamps() +  end + +  def create(user, admin_id \\ nil) do +    with :ok <- validate_email_enabled(), +         :ok <- validate_user_email(user), +         :ok <- validate_limit(user, admin_id), +         {:ok, backup} <- user |> new() |> Repo.insert() do +      BackupWorker.process(backup, admin_id) +    end +  end + +  def new(user) do +    rand_str = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) +    datetime = Calendar.NaiveDateTime.Format.iso8601_basic(NaiveDateTime.utc_now()) +    name = "archive-#{user.nickname}-#{datetime}-#{rand_str}.zip" + +    %__MODULE__{ +      user_id: user.id, +      content_type: "application/zip", +      file_name: name +    } +  end + +  def delete(backup) do +    uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) + +    with :ok <- uploader.delete_file(Path.join("backups", backup.file_name)) do +      Repo.delete(backup) +    end +  end + +  defp validate_limit(_user, admin_id) when is_binary(admin_id), do: :ok + +  defp validate_limit(user, nil) do +    case get_last(user.id) do +      %__MODULE__{inserted_at: inserted_at} -> +        days = Pleroma.Config.get([__MODULE__, :limit_days]) +        diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days) + +        if diff > days do +          :ok +        else +          {:error, +           dngettext( +             "errors", +             "Last export was less than a day ago", +             "Last export was less than %{days} days ago", +             days, +             days: days +           )} +        end + +      nil -> +        :ok +    end +  end + +  defp validate_email_enabled do +    if Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do +      :ok +    else +      {:error, dgettext("errors", "Backups require enabled email")} +    end +  end + +  defp validate_user_email(%User{email: nil}) do +    {:error, dgettext("errors", "Email is required")} +  end + +  defp validate_user_email(%User{email: email}) when is_binary(email), do: :ok + +  def get_last(user_id) do +    __MODULE__ +    |> where(user_id: ^user_id) +    |> order_by(desc: :id) +    |> limit(1) +    |> Repo.one() +  end + +  def list(%User{id: user_id}) do +    __MODULE__ +    |> where(user_id: ^user_id) +    |> order_by(desc: :id) +    |> Repo.all() +  end + +  def remove_outdated(%__MODULE__{id: latest_id, user_id: user_id}) do +    __MODULE__ +    |> where(user_id: ^user_id) +    |> where([b], b.id != ^latest_id) +    |> Repo.all() +    |> Enum.each(&BackupWorker.delete/1) +  end + +  def get(id), do: Repo.get(__MODULE__, id) + +  def process(%__MODULE__{} = backup) do +    with {:ok, zip_file} <- export(backup), +         {:ok, %{size: size}} <- File.stat(zip_file), +         {:ok, _upload} <- upload(backup, zip_file) do +      backup +      |> cast(%{file_size: size, processed: true}, [:file_size, :processed]) +      |> Repo.update() +    end +  end + +  @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] +  def export(%__MODULE__{} = backup) do +    backup = Repo.preload(backup, :user) +    name = String.trim_trailing(backup.file_name, ".zip") +    dir = dir(name) + +    with :ok <- File.mkdir(dir), +         :ok <- actor(dir, backup.user), +         :ok <- statuses(dir, backup.user), +         :ok <- likes(dir, backup.user), +         :ok <- bookmarks(dir, backup.user), +         {:ok, zip_path} <- :zip.create(String.to_charlist(dir <> ".zip"), @files, cwd: dir), +         {:ok, _} <- File.rm_rf(dir) do +      {:ok, to_string(zip_path)} +    end +  end + +  def dir(name) do +    dir = Pleroma.Config.get([__MODULE__, :dir]) || System.tmp_dir!() +    Path.join(dir, name) +  end + +  def upload(%__MODULE__{} = backup, zip_path) do +    uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) + +    upload = %Pleroma.Upload{ +      name: backup.file_name, +      tempfile: zip_path, +      content_type: backup.content_type, +      path: Path.join("backups", backup.file_name) +    } + +    with {:ok, _} <- Pleroma.Uploaders.Uploader.put_file(uploader, upload), +         :ok <- File.rm(zip_path) do +      {:ok, upload} +    end +  end + +  defp actor(dir, user) do +    with {:ok, json} <- +           UserView.render("user.json", %{user: user}) +           |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"}) +           |> Jason.encode() do +      File.write(Path.join(dir, "actor.json"), json) +    end +  end + +  defp write_header(file, name) do +    IO.write( +      file, +      """ +      { +        "@context": "https://www.w3.org/ns/activitystreams", +        "id": "#{name}.json", +        "type": "OrderedCollection", +        "orderedItems": [ + +      """ +    ) +  end + +  defp write(query, dir, name, fun) do +    path = Path.join(dir, "#{name}.json") + +    with {:ok, file} <- File.open(path, [:write, :utf8]), +         :ok <- write_header(file, name) do +      total = +        query +        |> Pleroma.Repo.chunk_stream(100) +        |> Enum.reduce(0, fn i, acc -> +          with {:ok, data} <- fun.(i), +               {:ok, str} <- Jason.encode(data), +               :ok <- IO.write(file, str <> ",\n") do +            acc + 1 +          else +            _ -> acc +          end +        end) + +      with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n  \"totalItems\": #{total}}") do +        File.close(file) +      end +    end +  end + +  defp bookmarks(dir, %{id: user_id} = _user) do +    Bookmark +    |> where(user_id: ^user_id) +    |> join(:inner, [b], activity in assoc(b, :activity)) +    |> select([b, a], %{id: b.id, object: fragment("(?)->>'object'", a.data)}) +    |> write(dir, "bookmarks", fn a -> {:ok, a.object} end) +  end + +  defp likes(dir, user) do +    user.ap_id +    |> Activity.Queries.by_actor() +    |> Activity.Queries.by_type("Like") +    |> select([like], %{id: like.id, object: fragment("(?)->>'object'", like.data)}) +    |> write(dir, "likes", fn a -> {:ok, a.object} end) +  end + +  defp statuses(dir, user) do +    opts = +      %{} +      |> Map.put(:type, ["Create", "Announce"]) +      |> Map.put(:actor_id, user.ap_id) + +    [ +      [Pleroma.Constants.as_public(), user.ap_id], +      User.following(user), +      Pleroma.List.memberships(user) +    ] +    |> Enum.concat() +    |> ActivityPub.fetch_activities_query(opts) +    |> write(dir, "outbox", fn a -> +      with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do +        {:ok, Map.delete(activity, "@context")} +      end +    end) +  end +end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index d618432ff..7ef2a1455 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -43,10 +43,12 @@ defmodule Pleroma.User.Query do              active: boolean(),              deactivated: boolean(),              need_approval: boolean(), +            unconfirmed: boolean(),              is_admin: boolean(),              is_moderator: boolean(),              super_users: boolean(),              invisible: boolean(), +            internal: boolean(),              followers: User.t(),              friends: User.t(),              recipients_from_activity: [String.t()], @@ -54,7 +56,8 @@ defmodule Pleroma.User.Query do              ap_id: [String.t()],              order_by: term(),              select: term(), -            limit: pos_integer() +            limit: pos_integer(), +            actor_types: [String.t()]            }            | map() @@ -80,7 +83,9 @@ defmodule Pleroma.User.Query do    end    defp prepare_query(query, criteria) do -    Enum.reduce(criteria, query, &compose_query/2) +    criteria +    |> Map.put_new(:internal, false) +    |> Enum.reduce(query, &compose_query/2)    end    defp compose_query({key, value}, query) @@ -107,12 +112,16 @@ defmodule Pleroma.User.Query do      where(query, [u], fragment("? && ?", u.tags, ^tags))    end -  defp compose_query({:is_admin, _}, query) do -    where(query, [u], u.is_admin) +  defp compose_query({:is_admin, bool}, query) do +    where(query, [u], u.is_admin == ^bool)    end -  defp compose_query({:is_moderator, _}, query) do -    where(query, [u], u.is_moderator) +  defp compose_query({:actor_types, actor_types}, query) when is_list(actor_types) do +    where(query, [u], u.actor_type in ^actor_types) +  end + +  defp compose_query({:is_moderator, bool}, query) do +    where(query, [u], u.is_moderator == ^bool)    end    defp compose_query({:super_users, _}, query) do @@ -129,14 +138,12 @@ defmodule Pleroma.User.Query do    defp compose_query({:active, _}, query) do      User.restrict_deactivated(query) -    |> where([u], not is_nil(u.nickname))      |> where([u], u.approval_pending == false)    end    defp compose_query({:legacy_active, _}, query) do      query      |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info)) -    |> where([u], not is_nil(u.nickname))    end    defp compose_query({:deactivated, false}, query) do @@ -145,13 +152,20 @@ defmodule Pleroma.User.Query do    defp compose_query({:deactivated, true}, query) do      where(query, [u], u.deactivated == ^true) -    |> where([u], not is_nil(u.nickname)) +  end + +  defp compose_query({:confirmation_pending, bool}, query) do +    where(query, [u], u.confirmation_pending == ^bool)    end    defp compose_query({:need_approval, _}, query) do      where(query, [u], u.approval_pending)    end +  defp compose_query({:unconfirmed, _}, query) do +    where(query, [u], u.confirmation_pending) +  end +    defp compose_query({:followers, %User{id: id}}, query) do      query      |> where([u], u.id != ^id) @@ -199,10 +213,15 @@ defmodule Pleroma.User.Query do      limit(query, ^limit)    end +  defp compose_query({:internal, false}, query) do +    query +    |> where([u], not is_nil(u.nickname)) +    |> where([u], not like(u.nickname, "internal.%")) +  end +    defp compose_query(_unsupported_param, query), do: query    defp location_query(query, local) do      where(query, [u], u.local == ^local) -    |> where([u], not is_nil(u.nickname))    end  end diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index b8c648672..2dab67211 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -3,8 +3,10 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User.Search do +  alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType    alias Pleroma.Pagination    alias Pleroma.User +    import Ecto.Query    @limit 20 @@ -19,16 +21,47 @@ defmodule Pleroma.User.Search do      query_string = format_query(query_string) -    maybe_resolve(resolve, for_user, query_string) +    # If this returns anything, it should bounce to the top +    maybe_resolved = maybe_resolve(resolve, for_user, query_string) + +    top_user_ids = +      [] +      |> maybe_add_resolved(maybe_resolved) +      |> maybe_add_ap_id_match(query_string) +      |> maybe_add_uri_match(query_string)      results =        query_string -      |> search_query(for_user, following) +      |> search_query(for_user, following, top_user_ids)        |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)      results    end +  defp maybe_add_resolved(list, {:ok, %User{} = user}) do +    [user.id | list] +  end + +  defp maybe_add_resolved(list, _), do: list + +  defp maybe_add_ap_id_match(list, query) do +    if user = User.get_cached_by_ap_id(query) do +      [user.id | list] +    else +      list +    end +  end + +  defp maybe_add_uri_match(list, query) do +    with {:ok, query} <- UriType.cast(query), +         q = from(u in User, where: u.uri == ^query, select: u.id), +         users = Pleroma.Repo.all(q) do +      users ++ list +    else +      _ -> list +    end +  end +    defp format_query(query_string) do      # Strip the beginning @ off if there is a query      query_string = String.trim_leading(query_string, "@") @@ -47,7 +80,7 @@ defmodule Pleroma.User.Search do      end    end -  defp search_query(query_string, for_user, following) do +  defp search_query(query_string, for_user, following, top_user_ids) do      for_user      |> base_query(following)      |> filter_blocked_user(for_user) @@ -56,13 +89,20 @@ defmodule Pleroma.User.Search do      |> filter_internal_users()      |> filter_blocked_domains(for_user)      |> fts_search(query_string) +    |> select_top_users(top_user_ids)      |> trigram_rank(query_string) -    |> boost_search_rank(for_user) +    |> boost_search_rank(for_user, top_user_ids)      |> subquery()      |> order_by(desc: :search_rank)      |> maybe_restrict_local(for_user)    end +  defp select_top_users(query, top_user_ids) do +    from(u in query, +      or_where: u.id in ^top_user_ids +    ) +  end +    defp fts_search(query, query_string) do      query_string = to_tsquery(query_string) @@ -124,7 +164,7 @@ defmodule Pleroma.User.Search do    end    defp filter_discoverable_users(query) do -    from(q in query, where: q.discoverable == true) +    from(q in query, where: q.is_discoverable == true)    end    defp filter_internal_users(query) do @@ -180,7 +220,7 @@ defmodule Pleroma.User.Search do    defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -  defp boost_search_rank(query, %User{} = for_user) do +  defp boost_search_rank(query, %User{} = for_user, top_user_ids) do      friends_ids = User.get_friends_ids(for_user)      followers_ids = User.get_followers_ids(for_user) @@ -192,6 +232,7 @@ defmodule Pleroma.User.Search do               CASE WHEN (?) THEN (?) * 1.5               WHEN (?) THEN (?) * 1.3               WHEN (?) THEN (?) * 1.1 +             WHEN (?) THEN 9001               ELSE (?) END              """,              u.id in ^friends_ids and u.id in ^followers_ids, @@ -200,11 +241,26 @@ defmodule Pleroma.User.Search do              u.search_rank,              u.id in ^followers_ids,              u.search_rank, +            u.id in ^top_user_ids,              u.search_rank            )        }      )    end -  defp boost_search_rank(query, _for_user), do: query +  defp boost_search_rank(query, _for_user, top_user_ids) do +    from(u in subquery(query), +      select_merge: %{ +        search_rank: +          fragment( +            """ +             CASE WHEN (?) THEN 9001 +             ELSE (?) END +            """, +            u.id in ^top_user_ids, +            u.search_rank +          ) +      } +    ) +  end  end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web.ex index 4f9281851..6ed19d3dd 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web.ex @@ -2,11 +2,6 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.Plug do -  # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug` -  @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() -end -  defmodule Pleroma.Web do    @moduledoc """    A module that keeps using definitions for controllers, @@ -25,12 +20,12 @@ defmodule Pleroma.Web do    below.    """ -  alias Pleroma.Plugs.EnsureAuthenticatedPlug -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug -  alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.PlugHelper +  alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug +  alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.PlugHelper    def controller do      quote do @@ -177,7 +172,7 @@ defmodule Pleroma.Web do    def channel do      quote do        # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse -      use Phoenix.Channel +      import Phoenix.Channel        import Pleroma.Web.Gettext      end    end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index aacd58d03..d8f685d38 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -790,7 +790,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        [activity, object] in query,        where:          fragment( -          "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?", +          """ +          ?->>'type' != 'Create'     -- This isn't a Create       +          OR ?->>'inReplyTo' is null -- this isn't a reply +          OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,  +                                     -- unless they are the author (because authors  +                                     -- are also part of the recipients). This leads +                                     -- to a bug that self-replies by friends won't +                                     -- show up. +          OR ? = ?                   -- The actor is us +          """, +          activity.data,            object.data,            ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],            activity.recipients, @@ -817,7 +827,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      query =        from([activity] in query,          where: fragment("not (? = ANY(?))", activity.actor, ^mutes), -        where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes) +        where: +          fragment( +            "not (?->'to' \\?| ?) or ? = ?", +            activity.data, +            ^mutes, +            activity.actor, +            ^user.ap_id +          )        )      unless opts[:skip_preload] do @@ -920,16 +937,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_muted_reblogs(query, _), do: query -  defp restrict_instance(query, %{instance: instance}) do -    users = -      from( -        u in User, -        select: u.ap_id, -        where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}") -      ) -      |> Repo.all() - -    from(activity in query, where: activity.actor in ^users) +  defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do +    from( +      activity in query, +      where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance) +    )    end    defp restrict_instance(query, _), do: query @@ -1218,11 +1230,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          {String.trim(name, ":"), url}        end) -    locked = data["manuallyApprovesFollowers"] || false +    is_locked = data["manuallyApprovesFollowers"] || false      capabilities = data["capabilities"] || %{}      accepts_chat_messages = capabilities["acceptsChatMessages"]      data = Transmogrifier.maybe_fix_user_object(data) -    discoverable = data["discoverable"] || false +    is_discoverable = data["discoverable"] || false      invisible = data["invisible"] || false      actor_type = data["type"] || "Person" @@ -1247,8 +1259,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        banner: banner,        fields: fields,        emoji: emojis, -      locked: locked, -      discoverable: discoverable, +      is_locked: is_locked, +      is_discoverable: is_discoverable,        invisible: invisible,        avatar: avatar,        name: data["name"], @@ -1361,6 +1373,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} diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 732c44271..31df80adb 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    alias Pleroma.Delivery    alias Pleroma.Object    alias Pleroma.Object.Fetcher -  alias Pleroma.Plugs.EnsureAuthenticatedPlug    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder @@ -23,8 +22,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.ControllerHelper    alias Pleroma.Web.Endpoint -  alias Pleroma.Web.FederatingPlug    alias Pleroma.Web.Federator +  alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug +  alias Pleroma.Web.Plugs.FederatingPlug    require Logger @@ -45,8 +45,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      when action in [:read_inbox, :update_outbox, :whoami, :upload_media]    ) +  plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media]) +    plug( -    Pleroma.Plugs.Cache, +    Pleroma.Web.Plugs.Cache,      [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]      when action in [:activity, :object]    ) @@ -412,7 +414,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do        object =          object          |> Map.merge(Map.take(params, ["to", "cc"])) -        |> Map.put("attributedTo", user.ap_id()) +        |> Map.put("attributedTo", user.ap_id)          |> Transmogrifier.fix_object()        ActivityPub.create(%{ @@ -456,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do          %{assigns: %{user: %User{nickname: nickname} = user}} = conn,          %{"nickname" => nickname} = params        ) do -    actor = user.ap_id() +    actor = user.ap_id      params =        params @@ -523,19 +525,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      {new_user, for_user}    end -  @doc """ -  Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload> - -  Parameters: -  - (required) `file`: data of the media -  - (optionnal) `description`: description of the media, intended for accessibility - -  Response: -  - HTTP Code: 201 Created -  - HTTP Body: ActivityPub object to be inserted into another's `attachment` field - -  Note: Will not point to a URL with a `Location` header because no standalone Activity has been created. -  """    def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do      with {:ok, object} <-             ActivityPub.upload( diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 9a7b7d9de..298aff6b7 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.Builder do    @moduledoc """    This module builds the objects. Meant to be used for creating local objects. diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 9c3956683..a2930c1cd 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -242,9 +242,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      end)    end -  @doc """ -  Publishes an activity to all relevant peers. -  """ +  # Publishes an activity to all relevant peers.    def publish(%User{} = actor, %Activity{} = activity) do      public = is_public?(activity) diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index b65710a94..6606e1780 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -30,12 +30,16 @@ defmodule Pleroma.Web.ActivityPub.Relay do      end    end -  @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()} -  def unfollow(target_instance) do +  @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()} +  def unfollow(target_instance, opts \\ %{}) do      with %User{} = local_user <- get_actor(), -         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), +         {:ok, target_user} <- fetch_target_user(target_instance, opts),           {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do -      User.unfollow(local_user, target_user) +      case target_user.id do +        nil -> User.update_following_count(local_user) +        _ -> User.unfollow(local_user, target_user) +      end +        Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")        {:ok, activity}      else @@ -43,6 +47,14 @@ defmodule Pleroma.Web.ActivityPub.Relay do      end    end +  defp fetch_target_user(ap_id, opts) do +    case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do +      {_, {:ok, %User{} = user}} -> {:ok, user} +      {true, _} -> {:ok, %User{ap_id: ap_id}} +      {_, error} -> error +    end +  end +    @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()}    def publish(%Activity{data: %{"type" => "Create"}} = activity) do      with %User{} = user <- get_actor(), diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b9a83a544..bbff35c36 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.SideEffects do    @moduledoc """    This module looks at an inserted object and executes the side effects that it @@ -98,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do           %User{} = followed <- User.get_cached_by_ap_id(followed_user),           {_, {:ok, _}, _, _} <-             {:following, User.follow(follower, followed, :follow_pending), follower, followed} do -      if followed.local && !followed.locked do +      if followed.local && !followed.is_locked do          {:ok, accept_data, _} = Builder.accept(followed, object)          {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)        end @@ -183,7 +187,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do        {:ok, notifications} = Notification.create_notifications(activity, do_send: false)        {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) -      if in_reply_to = object.data["inReplyTo"] do +      if in_reply_to = object.data["inReplyTo"] && object.data["type"] != "Answer" do          Object.increase_replies_count(in_reply_to)        end @@ -302,11 +306,18 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do        streamables =          [[actor, recipient], [recipient, actor]] +        |> Enum.uniq()          |> Enum.map(fn [user, other_user] ->            if user.local do              {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)              {:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id) +            Cachex.put( +              :chat_message_id_idempotency_key_cache, +              cm_ref.id, +              meta[:idempotency_key] +            ) +              {                ["user", "user:pleroma_chat"],                {user, %{cm_ref | chat: chat, object: object}} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index aa6a69463..39c8f7e39 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -40,6 +40,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      |> fix_in_reply_to(options)      |> fix_emoji      |> fix_tag +    |> set_sensitive      |> fix_content_map      |> fix_addressing      |> fix_summary @@ -313,19 +314,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      tags =        tag        |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) -      |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end) +      |> Enum.map(fn %{"name" => name} -> +        name +        |> String.slice(1..-1) +        |> String.downcase() +      end)      Map.put(object, "tag", tag ++ tags)    end -  def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do -    combined = [tag, String.slice(hashtag, 1..-1)] - -    Map.put(object, "tag", combined) +  def fix_tag(%{"tag" => %{} = tag} = object) do +    object +    |> Map.put("tag", [tag]) +    |> fix_tag    end -  def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag]) -    def fix_tag(object), do: object    # content map usually only has one language so this will do for now. @@ -515,15 +518,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    end    def handle_incoming( -        %{"type" => "Create", "object" => %{"type" => objtype}} = data, +        %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,          _options        )        when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do      data = Map.put(data, "object", strip_internal_fields(data["object"]))      with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), +         nil <- Activity.get_create_by_object_ap_id(obj_id),           {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do        {:ok, activity} +    else +      %Activity{} = activity -> {:ok, activity} +      e -> e      end    end @@ -923,7 +930,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      Map.put(object, "conversation", object["context"])    end -  def set_sensitive(%{"sensitive" => true} = object) do +  def set_sensitive(%{"sensitive" => _} = object) do      object    end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 3a4564912..4dc45cde3 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -101,7 +101,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do        "name" => user.name,        "summary" => user.bio,        "url" => user.ap_id, -      "manuallyApprovesFollowers" => user.locked, +      "manuallyApprovesFollowers" => user.is_locked,        "publicKey" => %{          "id" => "#{user.ap_id}#main-key",          "owner" => user.ap_id, @@ -110,7 +110,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do        "endpoints" => endpoints,        "attachment" => fields,        "tag" => emoji_tags, -      "discoverable" => user.discoverable, +      "discoverable" => user.is_discoverable,        "capabilities" => capabilities      }      |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 5c349bb7a..76bd54a42 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -44,29 +44,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)) @@ -82,6 +83,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"] || [] diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index d5713c3dd..5c2c282b3 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -5,22 +5,20 @@  defmodule Pleroma.Web.AdminAPI.AdminAPIController do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [json_response: 3] +  import Pleroma.Web.ControllerHelper, +    only: [json_response: 3, fetch_integer_param: 3]    alias Pleroma.Config    alias Pleroma.MFA    alias Pleroma.ModerationLog -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Stats    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Builder -  alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.AdminAPI.ModerationLogView -  alias Pleroma.Web.AdminAPI.Search    alias Pleroma.Web.Endpoint +  alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.Router    @users_page_size 50 @@ -28,7 +26,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug,      %{scopes: ["read:accounts"], admin: true} -    when action in [:list_users, :user_show, :right_get, :show_user_credentials] +    when action in [:right_get, :show_user_credentials, :create_backup]    )    plug( @@ -37,12 +35,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      when action in [             :get_password_reset,             :force_password_reset, -           :user_delete, -           :users_create, -           :user_toggle_activation, -           :user_activate, -           :user_deactivate, -           :user_approve,             :tag_users,             :untag_users,             :right_add, @@ -56,12 +48,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug, -    %{scopes: ["write:follows"], admin: true} -    when action in [:user_follow, :user_unfollow] -  ) - -  plug( -    OAuthScopesPlug,      %{scopes: ["read:statuses"], admin: true}      when action in [:list_user_statuses, :list_instance_statuses]    ) @@ -95,132 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    action_fallback(AdminAPI.FallbackController) -  def user_delete(conn, %{"nickname" => nickname}) do -    user_delete(conn, %{"nicknames" => [nickname]}) -  end - -  def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = -      nicknames -      |> Enum.map(&User.get_cached_by_nickname/1) - -    users -    |> Enum.each(fn user -> -      {:ok, delete_data, _} = Builder.delete(admin, user.ap_id) -      Pipeline.common_pipeline(delete_data, local: true) -    end) - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "delete" -    }) - -    json(conn, nicknames) -  end - -  def user_follow(%{assigns: %{user: admin}} = conn, %{ -        "follower" => follower_nick, -        "followed" => followed_nick -      }) do -    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), -         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do -      User.follow(follower, followed) - -      ModerationLog.insert_log(%{ -        actor: admin, -        followed: followed, -        follower: follower, -        action: "follow" -      }) -    end - -    json(conn, "ok") -  end - -  def user_unfollow(%{assigns: %{user: admin}} = conn, %{ -        "follower" => follower_nick, -        "followed" => followed_nick -      }) do -    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), -         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do -      User.unfollow(follower, followed) - -      ModerationLog.insert_log(%{ -        actor: admin, -        followed: followed, -        follower: follower, -        action: "unfollow" -      }) -    end - -    json(conn, "ok") -  end - -  def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do -    changesets = -      Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} -> -        user_data = %{ -          nickname: nickname, -          name: nickname, -          email: email, -          password: password, -          password_confirmation: password, -          bio: "." -        } - -        User.register_changeset(%User{}, user_data, need_confirmation: false) -      end) -      |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi -> -        Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset) -      end) - -    case Pleroma.Repo.transaction(changesets) do -      {:ok, users} -> -        res = -          users -          |> Map.values() -          |> Enum.map(fn user -> -            {:ok, user} = User.post_register_action(user) - -            user -          end) -          |> Enum.map(&AccountView.render("created.json", %{user: &1})) - -        ModerationLog.insert_log(%{ -          actor: admin, -          subjects: Map.values(users), -          action: "create" -        }) - -        json(conn, res) - -      {:error, id, changeset, _} -> -        res = -          Enum.map(changesets.operations, fn -            {current_id, {:changeset, _current_changeset, _}} when current_id == id -> -              AccountView.render("create-error.json", %{changeset: changeset}) - -            {_, {:changeset, current_changeset, _}} -> -              AccountView.render("create-error.json", %{changeset: current_changeset}) -          end) - -        conn -        |> put_status(:conflict) -        |> json(res) -    end -  end - -  def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do -    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do -      conn -      |> put_view(AccountView) -      |> render("show.json", %{user: user}) -    else -      _ -> {:error, :not_found} -    end -  end -    def list_instance_statuses(conn, %{"instance" => instance} = params) do      with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true      {page, page_size} = page_params(params) @@ -274,69 +134,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      end    end -  def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do -    user = User.get_cached_by_nickname(nickname) - -    {:ok, updated_user} = User.deactivate(user, !user.deactivated) - -    action = if user.deactivated, do: "activate", else: "deactivate" - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: [user], -      action: action -    }) - -    conn -    |> put_view(AccountView) -    |> render("show.json", %{user: updated_user}) -  end - -  def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) -    {:ok, updated_users} = User.deactivate(users, false) - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "activate" -    }) - -    conn -    |> put_view(AccountView) -    |> render("index.json", %{users: Keyword.values(updated_users)}) -  end - -  def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) -    {:ok, updated_users} = User.deactivate(users, true) - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "deactivate" -    }) - -    conn -    |> put_view(AccountView) -    |> render("index.json", %{users: Keyword.values(updated_users)}) -  end - -  def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) -    {:ok, updated_users} = User.approve(users) - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: users, -      action: "approve" -    }) - -    conn -    |> put_view(AccountView) -    |> render("index.json", %{users: updated_users}) -  end -    def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do      with {:ok, _} <- User.tag(nicknames, tags) do        ModerationLog.insert_log(%{ @@ -363,43 +160,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      end    end -  def list_users(conn, params) do -    {page, page_size} = page_params(params) -    filters = maybe_parse_filters(params["filters"]) - -    search_params = %{ -      query: params["query"], -      page: page, -      page_size: page_size, -      tags: params["tags"], -      name: params["name"], -      email: params["email"] -    } - -    with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do -      json( -        conn, -        AccountView.render("index.json", -          users: users, -          count: count, -          page_size: page_size -        ) -      ) -    end -  end - -  @filters ~w(local external active deactivated need_approval is_admin is_moderator) - -  @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} -  defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} - -  defp maybe_parse_filters(filters) do -    filters -    |> String.split(",") -    |> Enum.filter(&Enum.member?(@filters, &1)) -    |> Map.new(&{String.to_existing_atom(&1), true}) -  end -    def right_add_multiple(%{assigns: %{user: admin}} = conn, %{          "permission_group" => permission_group,          "nicknames" => nicknames @@ -681,25 +441,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      json(conn, %{"status_visibility" => counters})    end -  defp page_params(params) do -    {get_page(params["page"]), get_page_size(params["page_size"])} -  end +  def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do +    with %User{} = user <- User.get_by_nickname(nickname), +         {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do +      ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"}) -  defp get_page(page_string) when is_nil(page_string), do: 1 - -  defp get_page(page_string) do -    case Integer.parse(page_string) do -      {page, _} -> page -      :error -> 1 +      json(conn, "")      end    end -  defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size - -  defp get_page_size(page_size_string) do -    case Integer.parse(page_size_string) do -      {page_size, _} -> page_size -      :error -> @users_page_size -    end +  defp page_params(params) do +    { +      fetch_integer_param(params, "page", 1), +      fetch_integer_param(params, "page_size", @users_page_size) +    }    end  end diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex index 967600d69..af8ff8292 100644 --- a/lib/pleroma/web/admin_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -10,10 +10,10 @@ defmodule Pleroma.Web.AdminAPI.ChatController do    alias Pleroma.Chat.MessageReference    alias Pleroma.ModerationLog    alias Pleroma.Pagination -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index 0df13007f..5d155af3d 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do    alias Pleroma.Config    alias Pleroma.ConfigDB -  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update) diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex index 504d9b517..37dbfeb72 100644 --- a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -5,9 +5,9 @@  defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.InstanceStatic -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.InstanceDocument +  alias Pleroma.Web.Plugs.InstanceStatic +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex index 7d169b8d2..6a9b4038a 100644 --- a/lib/pleroma/web/admin_api/controllers/invite_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/invite_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.AdminAPI.InviteController do    import Pleroma.Web.ControllerHelper, only: [json_response: 3]    alias Pleroma.Config -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.UserInviteToken +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex index 131e22d78..6d92e9f7f 100644 --- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex @@ -5,9 +5,9 @@  defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.ApiSpec.Admin, as: Spec    alias Pleroma.Web.MediaProxy +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex index dca23ea73..116a05a4d 100644 --- a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do    import Pleroma.Web.ControllerHelper, only: [json_response: 3] -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.OAuth.App +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex index 95d06dde7..611388447 100644 --- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.AdminAPI.RelayController do    use Pleroma.Web, :controller    alias Pleroma.ModerationLog -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.ActivityPub.Relay +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger @@ -33,11 +33,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do    def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do      with {:ok, _message} <- Relay.follow(target) do -      ModerationLog.insert_log(%{ -        action: "relay_follow", -        actor: admin, -        target: target -      }) +      ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target})        json(conn, %{actor: target, followed_back: target in Relay.following()})      else @@ -48,13 +44,9 @@ defmodule Pleroma.Web.AdminAPI.RelayController do      end    end -  def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do -    with {:ok, _message} <- Relay.unfollow(target) do -      ModerationLog.insert_log(%{ -        action: "relay_unfollow", -        actor: admin, -        target: target -      }) +  def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do +    with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do +      ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target})        json(conn, target)      else diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex index 4c011e174..6a0e56f5f 100644 --- a/lib/pleroma/web/admin_api/controllers/report_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex @@ -9,12 +9,12 @@ defmodule Pleroma.Web.AdminAPI.ReportController do    alias Pleroma.Activity    alias Pleroma.ModerationLog -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.ReportNote    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.AdminAPI.Report    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger @@ -38,7 +38,7 @@ defmodule Pleroma.Web.AdminAPI.ReportController do    end    def show(conn, %{id: id}) do -    with %Activity{} = report <- Activity.get_by_id(id) do +    with %Activity{} = report <- Activity.get_report(id) do        render(conn, "show.json", Report.extract_report_info(report))      else        _ -> {:error, :not_found} diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex index bc48cc527..2bb437cfe 100644 --- a/lib/pleroma/web/admin_api/controllers/status_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -7,10 +7,10 @@ defmodule Pleroma.Web.AdminAPI.StatusController do    alias Pleroma.Activity    alias Pleroma.ModerationLog -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    require Logger diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex new file mode 100644 index 000000000..a2a1c875d --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex @@ -0,0 +1,281 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.UserController do +  use Pleroma.Web, :controller + +  import Pleroma.Web.ControllerHelper, +    only: [fetch_integer_param: 3] + +  alias Pleroma.ModerationLog +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Builder +  alias Pleroma.Web.ActivityPub.Pipeline +  alias Pleroma.Web.AdminAPI +  alias Pleroma.Web.AdminAPI.AccountView +  alias Pleroma.Web.AdminAPI.Search +  alias Pleroma.Web.Plugs.OAuthScopesPlug + +  @users_page_size 50 + +  plug( +    OAuthScopesPlug, +    %{scopes: ["read:accounts"], admin: true} +    when action in [:list, :show] +  ) + +  plug( +    OAuthScopesPlug, +    %{scopes: ["write:accounts"], admin: true} +    when action in [ +           :delete, +           :create, +           :toggle_activation, +           :activate, +           :deactivate, +           :approve +         ] +  ) + +  plug( +    OAuthScopesPlug, +    %{scopes: ["write:follows"], admin: true} +    when action in [:follow, :unfollow] +  ) + +  action_fallback(AdminAPI.FallbackController) + +  def delete(conn, %{"nickname" => nickname}) do +    delete(conn, %{"nicknames" => [nickname]}) +  end + +  def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) + +    Enum.each(users, fn user -> +      {:ok, delete_data, _} = Builder.delete(admin, user.ap_id) +      Pipeline.common_pipeline(delete_data, local: true) +    end) + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: users, +      action: "delete" +    }) + +    json(conn, nicknames) +  end + +  def follow(%{assigns: %{user: admin}} = conn, %{ +        "follower" => follower_nick, +        "followed" => followed_nick +      }) do +    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), +         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do +      User.follow(follower, followed) + +      ModerationLog.insert_log(%{ +        actor: admin, +        followed: followed, +        follower: follower, +        action: "follow" +      }) +    end + +    json(conn, "ok") +  end + +  def unfollow(%{assigns: %{user: admin}} = conn, %{ +        "follower" => follower_nick, +        "followed" => followed_nick +      }) do +    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), +         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do +      User.unfollow(follower, followed) + +      ModerationLog.insert_log(%{ +        actor: admin, +        followed: followed, +        follower: follower, +        action: "unfollow" +      }) +    end + +    json(conn, "ok") +  end + +  def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do +    changesets = +      Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} -> +        user_data = %{ +          nickname: nickname, +          name: nickname, +          email: email, +          password: password, +          password_confirmation: password, +          bio: "." +        } + +        User.register_changeset(%User{}, user_data, need_confirmation: false) +      end) +      |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi -> +        Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset) +      end) + +    case Pleroma.Repo.transaction(changesets) do +      {:ok, users} -> +        res = +          users +          |> Map.values() +          |> Enum.map(fn user -> +            {:ok, user} = User.post_register_action(user) + +            user +          end) +          |> Enum.map(&AccountView.render("created.json", %{user: &1})) + +        ModerationLog.insert_log(%{ +          actor: admin, +          subjects: Map.values(users), +          action: "create" +        }) + +        json(conn, res) + +      {:error, id, changeset, _} -> +        res = +          Enum.map(changesets.operations, fn +            {current_id, {:changeset, _current_changeset, _}} when current_id == id -> +              AccountView.render("create-error.json", %{changeset: changeset}) + +            {_, {:changeset, current_changeset, _}} -> +              AccountView.render("create-error.json", %{changeset: current_changeset}) +          end) + +        conn +        |> put_status(:conflict) +        |> json(res) +    end +  end + +  def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do +    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do +      conn +      |> put_view(AccountView) +      |> render("show.json", %{user: user}) +    else +      _ -> {:error, :not_found} +    end +  end + +  def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do +    user = User.get_cached_by_nickname(nickname) + +    {:ok, updated_user} = User.deactivate(user, !user.deactivated) + +    action = if user.deactivated, do: "activate", else: "deactivate" + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: [user], +      action: action +    }) + +    conn +    |> put_view(AccountView) +    |> render("show.json", %{user: updated_user}) +  end + +  def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) +    {:ok, updated_users} = User.deactivate(users, false) + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: users, +      action: "activate" +    }) + +    conn +    |> put_view(AccountView) +    |> render("index.json", %{users: Keyword.values(updated_users)}) +  end + +  def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) +    {:ok, updated_users} = User.deactivate(users, true) + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: users, +      action: "deactivate" +    }) + +    conn +    |> put_view(AccountView) +    |> render("index.json", %{users: Keyword.values(updated_users)}) +  end + +  def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do +    users = Enum.map(nicknames, &User.get_cached_by_nickname/1) +    {:ok, updated_users} = User.approve(users) + +    ModerationLog.insert_log(%{ +      actor: admin, +      subject: users, +      action: "approve" +    }) + +    conn +    |> put_view(AccountView) +    |> render("index.json", %{users: updated_users}) +  end + +  def list(conn, params) do +    {page, page_size} = page_params(params) +    filters = maybe_parse_filters(params["filters"]) + +    search_params = +      %{ +        query: params["query"], +        page: page, +        page_size: page_size, +        tags: params["tags"], +        name: params["name"], +        email: params["email"], +        actor_types: params["actor_types"] +      } +      |> Map.merge(filters) + +    with {:ok, users, count} <- Search.user(search_params) do +      json( +        conn, +        AccountView.render("index.json", +          users: users, +          count: count, +          page_size: page_size +        ) +      ) +    end +  end + +  @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator) + +  @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} +  defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} + +  defp maybe_parse_filters(filters) do +    filters +    |> String.split(",") +    |> Enum.filter(&Enum.member?(@filters, &1)) +    |> Map.new(&{String.to_existing_atom(&1), true}) +  end + +  defp page_params(params) do +    { +      fetch_integer_param(params, "page", 1), +      fetch_integer_param(params, "page_size", @users_page_size) +    } +  end +end diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 9c477feab..8bac24d3e 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -39,7 +39,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do        :fields,        :name,        :nickname, -      :locked, +      :is_locked,        :no_rich_text,        :default_scope,        :hide_follows, @@ -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(%{ diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 773f798fe..535556370 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -52,7 +52,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do    end    def render("index_notes.json", %{notes: notes}) when is_list(notes) do -    Enum.map(notes, &render(__MODULE__, "show_note.json", &1)) +    Enum.map(notes, &render(__MODULE__, "show_note.json", Map.from_struct(&1)))    end    def render("index_notes.json", _), do: [] diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index fbfc27d6f..6d1a7ebbc 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -115,6 +115,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do              %{reason: :unexpected_field, name: name, path: [name]}, params ->                Map.delete(params, name) +            # Filter out empty params +            %{reason: :invalid_type, path: [name_atom], value: ""}, params -> +              Map.delete(params, to_string(name_atom)) +              %{reason: :invalid_enum, name: nil, path: path, value: value}, params ->                path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()                update_in(params, path, &List.delete(&1, value)) diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index a0933ba52..451aa2477 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -341,6 +341,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do        operationId: "AccountController.mutes",        description: "Accounts the user has muted.",        security: [%{"oAuth" => ["follow", "read:mutes"]}], +      parameters: pagination_params(),        responses: %{          200 => Operation.response("Accounts", "application/json", array_of_accounts())        } @@ -354,6 +355,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do        operationId: "AccountController.blocks",        description: "View your blocks. See also accounts/:id/{block,unblock}",        security: [%{"oAuth" => ["read:blocks"]}], +      parameters: pagination_params(),        responses: %{          200 => Operation.response("Accounts", "application/json", array_of_accounts())        } diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index a75f3e622..a75f3e622 100644 --- a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex index e06b2d164..f754bb9f5 100644 --- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do        operationId: "AdminAPI.RelayController.unfollow",        security: [%{"oAuth" => ["write:follows"]}],        parameters: admin_api_params(), -      requestBody: request_body("Parameters", relay_url()), +      requestBody: request_body("Parameters", relay_unfollow()),        responses: %{          200 =>            Operation.response("Status", "application/json", %Schema{ @@ -91,4 +91,14 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do        }      }    end + +  defp relay_unfollow do +    %Schema{ +      type: :object, +      properties: %{ +        relay_url: %Schema{type: :string, format: :uri}, +        force: %Schema{type: :boolean, default: false} +      } +    } +  end  end diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index 56554d5b4..560b81f17 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do    alias OpenApiSpex.Operation    alias OpenApiSpex.Schema    alias Pleroma.Web.ApiSpec.Schemas.ApiError +  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike    alias Pleroma.Web.ApiSpec.Schemas.Chat    alias Pleroma.Web.ApiSpec.Schemas.ChatMessage @@ -132,7 +133,10 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do        tags: ["chat"],        summary: "Get a list of chats that you participated in",        operationId: "ChatController.index", -      parameters: pagination_params(), +      parameters: [ +        Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users") +        | pagination_params() +      ],        responses: %{          200 => Operation.response("The chats of the user", "application/json", chats_response())        }, @@ -158,7 +162,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do              "The messages in the chat",              "application/json",              chat_messages_response() -          ) +          ), +        404 => Operation.response("Not Found", "application/json", ApiError)        },        security: [          %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex new file mode 100644 index 000000000..6993794db --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -0,0 +1,79 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.ApiError + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def index_operation do +    %Operation{ +      tags: ["Backups"], +      summary: "List backups", +      security: [%{"oAuth" => ["read:account"]}], +      operationId: "PleromaAPI.BackupController.index", +      responses: %{ +        200 => +          Operation.response( +            "An array of backups", +            "application/json", +            %Schema{ +              type: :array, +              items: backup() +            } +          ), +        400 => Operation.response("Bad Request", "application/json", ApiError) +      } +    } +  end + +  def create_operation do +    %Operation{ +      tags: ["Backups"], +      summary: "Create a backup", +      security: [%{"oAuth" => ["read:account"]}], +      operationId: "PleromaAPI.BackupController.create", +      responses: %{ +        200 => +          Operation.response( +            "An array of backups", +            "application/json", +            %Schema{ +              type: :array, +              items: backup() +            } +          ), +        400 => Operation.response("Bad Request", "application/json", ApiError) +      } +    } +  end + +  defp backup do +    %Schema{ +      title: "Backup", +      description: "Response schema for a backup", +      type: :object, +      properties: %{ +        inserted_at: %Schema{type: :string, format: :"date-time"}, +        content_type: %Schema{type: :string}, +        file_name: %Schema{type: :string}, +        file_size: %Schema{type: :integer}, +        processed: %Schema{type: :boolean} +      }, +      example: %{ +        "content_type" => "application/zip", +        "file_name" => +          "https://cofe.fe:4000/media/backups/archive-foobar-20200908T164207-Yr7vuT5Wycv-sN3kSN2iJ0k-9pMo60j9qmvRCdDqIew.zip", +        "file_size" => 4105, +        "inserted_at" => "2020-09-08T16:42:07.000Z", +        "processed" => true +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index efbfce75f..a56641426 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -126,7 +126,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do    end    defp name_param do -    Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) +    Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)    end    defp files_object do diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index 59548af13..79f52dcb3 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -19,7 +19,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do        tags: ["Emoji Packs"],        summary: "Make request to another instance for emoji packs list",        security: [%{"oAuth" => ["write"]}], -      parameters: [url_param()], +      parameters: [ +        url_param(), +        Operation.parameter( +          :page, +          :query, +          %Schema{type: :integer, default: 1}, +          "Page" +        ), +        Operation.parameter( +          :page_size, +          :query, +          %Schema{type: :integer, default: 30}, +          "Number of emoji to return" +        ) +      ],        operationId: "PleromaAPI.EmojiPackController.remote",        responses: %{          200 => emoji_packs_response(), @@ -192,7 +206,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do    end    defp name_param do -    Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) +    Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)    end    defp url_param do diff --git a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex new file mode 100644 index 000000000..2c455b0df --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaInstancesOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def show_operation do +    %Operation{ +      tags: ["PleromaInstances"], +      summary: "Instances federation status", +      description: "Information about instances deemed unreachable by the server", +      operationId: "PleromaInstances.show", +      responses: %{ +        200 => Operation.response("PleromaInstances", "application/json", pleroma_instances()) +      } +    } +  end + +  def pleroma_instances do +    %Schema{ +      type: :object, +      properties: %{ +        unreachable: %Schema{ +          type: :object, +          properties: %{hostname: %Schema{type: :string, format: :"date-time"}} +        } +      }, +      example: %{ +        "unreachable" => %{"consistently-unreachable.name" => "2020-10-14 22:07:58.216473"} +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex index 8e19bace7..95720df9f 100644 --- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex +++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -59,6 +59,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do        security: [%{"oAuth" => ["read:statuses"]}],        parameters: [          local_param(), +        instance_param(),          only_media_param(),          with_muted_param(),          exclude_visibilities_param(), @@ -158,8 +159,17 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do      )    end +  defp instance_param do +    Operation.parameter( +      :instance, +      :query, +      %Schema{type: :string}, +      "Show only statuses from the given domain" +    ) +  end +    defp with_muted_param do -    Operation.parameter(:with_muted, :query, BooleanLike, "Includeactivities by muted users") +    Operation.parameter(:with_muted, :query, BooleanLike, "Include activities by muted users")    end    defp exclude_visibilities_param do diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex index b4986b734..65f908e33 100644 --- a/lib/pleroma/web/api_spec/schemas/chat.ex +++ b/lib/pleroma/web/api_spec/schemas/chat.ex @@ -50,7 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do            "fields" => []          },          "statuses_count" => 1, -        "locked" => false, +        "is_locked" => false,          "created_at" => "2020-04-16T13:40:15.000Z",          "display_name" => "lain",          "fields" => [], diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index c62096db0..0dfa60b97 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -28,8 +28,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do        },        votes_count: %Schema{          type: :integer, -        nullable: true, -        description: "How many votes have been received. Number, or null if `multiple` is false." +        description: "How many votes have been received. Number." +      }, +      voters_count: %Schema{ +        type: :integer, +        description: "How many unique accounts have voted. Number."        },        voted: %Schema{          type: :boolean, @@ -61,7 +64,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do        expired: true,        multiple: false,        votes_count: 10, -      voters_count: nil, +      voters_count: 10,        voted: true,        own_votes: [          1 diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 947e42890..e6890df2d 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -252,7 +252,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do          "header" => "http://localhost:4001/images/banner.png",          "header_static" => "http://localhost:4001/images/banner.png",          "id" => "9toJCsKN7SmSf3aj5c", -        "locked" => false, +        "is_locked" => false,          "note" => "Tester Number 6",          "pleroma" => %{            "background_image" => nil, diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index c611b3e09..d6d2a8d06 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -3,10 +3,10 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Auth.PleromaAuthenticator do -  alias Pleroma.Plugs.AuthenticationPlug    alias Pleroma.Registration    alias Pleroma.Repo    alias Pleroma.User +  alias Pleroma.Web.Plugs.AuthenticationPlug    import Pleroma.Web.Auth.Authenticator,      only: [fetch_credentials: 1, fetch_user: 1] diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex index 1794e407c..edc9871ea 100644 --- a/lib/pleroma/web/auth/totp_authenticator.ex +++ b/lib/pleroma/web/auth/totp_authenticator.ex @@ -5,8 +5,8 @@  defmodule Pleroma.Web.Auth.TOTPAuthenticator do    alias Pleroma.MFA    alias Pleroma.MFA.TOTP -  alias Pleroma.Plugs.AuthenticationPlug    alias Pleroma.User +  alias Pleroma.Web.Plugs.AuthenticationPlug    @doc "Verify code or check backup code."    @spec verify(String.t(), User.t()) :: diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api.ex index aa4c6ddab..0ab1b115d 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -45,7 +45,8 @@ defmodule Pleroma.Web.CommonAPI do           {_, {:ok, %Activity{} = activity, _meta}} <-             {:common_pipeline,              Pipeline.common_pipeline(create_activity_data, -              local: true +              local: true, +              idempotency_key: opts[:idempotency_key]              )} do        {:ok, activity}      else diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9d7b24eb2..3b71adf0e 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -12,12 +12,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do    alias Pleroma.Conversation.Participation    alias Pleroma.Formatter    alias Pleroma.Object -  alias Pleroma.Plugs.AuthenticationPlug    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.MediaProxy +  alias Pleroma.Web.Plugs.AuthenticationPlug    require Logger    require Pleroma.Constants @@ -274,7 +274,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def format_input(text, format, options \\ [])    @doc """ -  Formatting text to plain text. +  Formatting text to plain text, BBCode, HTML, or Markdown    """    def format_input(text, "text/plain", options) do      text @@ -285,9 +285,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do          end).()    end -  @doc """ -  Formatting text as BBCode. -  """    def format_input(text, "text/bbcode", options) do      text      |> String.replace(~r/\r/, "") @@ -297,18 +294,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do      |> Formatter.linkify(options)    end -  @doc """ -  Formatting text to html. -  """    def format_input(text, "text/html", options) do      text      |> Formatter.html_escape("text/html")      |> Formatter.linkify(options)    end -  @doc """ -  Formatting text to markdown. -  """    def format_input(text, "text/markdown", options) do      text      |> Formatter.mentions_escape(options) diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 6445966e0..69188a882 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -48,13 +48,13 @@ defmodule Pleroma.Web.ControllerHelper do    defp param_to_integer(_, default), do: default -  def add_link_headers(conn, activities, extra_params \\ %{}) +  def add_link_headers(conn, entries, extra_params \\ %{}) -  def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params), +  def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params),      do: conn -  def add_link_headers(conn, activities, extra_params) do -    case get_pagination_fields(conn, activities, extra_params) do +  def add_link_headers(conn, entries, extra_params) do +    case get_pagination_fields(conn, entries, extra_params) do        %{"next" => next_url, "prev" => prev_url} ->          put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") @@ -78,19 +78,15 @@ defmodule Pleroma.Web.ControllerHelper do      }    end -  def get_pagination_fields(conn, activities, extra_params \\ %{}) do -    case List.last(activities) do +  def get_pagination_fields(conn, entries, extra_params \\ %{}) do +    case List.last(entries) do        %{pagination_id: max_id} when not is_nil(max_id) -> -        %{pagination_id: min_id} = -          activities -          |> List.first() +        %{pagination_id: min_id} = List.first(entries)          build_pagination_fields(conn, min_id, max_id, extra_params)        %{id: max_id} -> -        %{id: min_id} = -          activities -          |> List.first() +        %{id: min_id} = List.first(entries)          build_pagination_fields(conn, min_id, max_id, extra_params) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 8b153763d..f26542e88 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -7,19 +7,23 @@ defmodule Pleroma.Web.Endpoint do    require Pleroma.Constants +  alias Pleroma.Config +    socket("/socket", Pleroma.Web.UserSocket) -  plug(Pleroma.Plugs.SetLocalePlug) +  plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) + +  plug(Pleroma.Web.Plugs.SetLocalePlug)    plug(CORSPlug) -  plug(Pleroma.Plugs.HTTPSecurityPlug) -  plug(Pleroma.Plugs.UploadedMedia) +  plug(Pleroma.Web.Plugs.HTTPSecurityPlug) +  plug(Pleroma.Web.Plugs.UploadedMedia)    @static_cache_control "public, no-cache"    # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files    # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well    # Cache-control headers are duplicated in case we turn off etags in the future -  plug(Pleroma.Plugs.InstanceStatic, +  plug(Pleroma.Web.Plugs.InstanceStatic,      at: "/",      gzip: true,      cache_control_for_etags: @static_cache_control, @@ -29,7 +33,7 @@ defmodule Pleroma.Web.Endpoint do    )    # Careful! No `only` restriction here, as we don't know what frontends contain. -  plug(Pleroma.Plugs.FrontendStatic, +  plug(Pleroma.Web.Plugs.FrontendStatic,      at: "/",      frontend_type: :primary,      gzip: true, @@ -41,7 +45,7 @@ defmodule Pleroma.Web.Endpoint do    plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") -  plug(Pleroma.Plugs.FrontendStatic, +  plug(Pleroma.Web.Plugs.FrontendStatic,      at: "/pleroma/admin",      frontend_type: :admin,      gzip: true, @@ -79,26 +83,26 @@ defmodule Pleroma.Web.Endpoint do      plug(Phoenix.CodeReloader)    end -  plug(Pleroma.Plugs.TrailingFormatPlug) +  plug(Pleroma.Web.Plugs.TrailingFormatPlug)    plug(Plug.RequestId)    plug(Plug.Logger, log: :debug)    plug(Plug.Parsers,      parsers: [        :urlencoded, -      {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}}, +      {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},        :json      ],      pass: ["*/*"],      json_decoder: Jason, -    length: Pleroma.Config.get([:instance, :upload_limit]), +    length: Config.get([:instance, :upload_limit]),      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}    )    plug(Plug.MethodOverride)    plug(Plug.Head) -  secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag]) +  secure_cookies = Config.get([__MODULE__, :secure_cookie_flag])    cookie_name =      if secure_cookies, @@ -106,7 +110,7 @@ defmodule Pleroma.Web.Endpoint do        else: "pleroma_key"    extra = -    Pleroma.Config.get([__MODULE__, :extra_cookie_attrs]) +    Config.get([__MODULE__, :extra_cookie_attrs])      |> Enum.join(";")    # The session will be stored in the cookie and signed, @@ -116,13 +120,13 @@ defmodule Pleroma.Web.Endpoint do      Plug.Session,      store: :cookie,      key: cookie_name, -    signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"), +    signing_salt: Config.get([__MODULE__, :signing_salt], "CqaoopA2"),      http_only: true,      secure: secure_cookies,      extra: extra    ) -  plug(Pleroma.Plugs.RemoteIp) +  plug(Pleroma.Web.Plugs.RemoteIp)    defmodule Instrumenter do      use Prometheus.PhoenixInstrumenter @@ -136,8 +140,34 @@ defmodule Pleroma.Web.Endpoint do      use Prometheus.PlugExporter    end +  defmodule MetricsExporterCaller do +    @behaviour Plug + +    def init(opts), do: opts + +    def call(conn, opts) do +      prometheus_config = Application.get_env(:prometheus, MetricsExporter, []) +      ip_whitelist = List.wrap(prometheus_config[:ip_whitelist]) + +      cond do +        !prometheus_config[:enabled] -> +          conn + +        ip_whitelist != [] and +            !Enum.find(ip_whitelist, fn ip -> +              Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip} +            end) -> +          conn + +        true -> +          MetricsExporter.call(conn, opts) +      end +    end +  end +    plug(PipelineInstrumenter) -  plug(MetricsExporter) + +  plug(MetricsExporterCaller)    plug(Pleroma.Web.Router) diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 431ad5485..6f759d559 100644 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Fallback.RedirectController do +defmodule Pleroma.Web.Fallback.RedirectController do    use Pleroma.Web, :controller    require Logger @@ -75,7 +75,7 @@ defmodule Fallback.RedirectController do    end    defp index_file_path do -    Pleroma.Plugs.InstanceStatic.file_path("index.html") +    Pleroma.Web.Plugs.InstanceStatic.file_path("index.html")    end    defp build_tags(conn, params) do diff --git a/lib/pleroma/web/fed_sockets/fed_sockets.ex b/lib/pleroma/web/fed_sockets.ex index 1fd5899c8..1fd5899c8 100644 --- a/lib/pleroma/web/fed_sockets/fed_sockets.ex +++ b/lib/pleroma/web/fed_sockets.ex diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator.ex index 130654145..130654145 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator.ex diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index 93a8294b7..218cdbdf3 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -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 diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 71eb1ea7e..a5013d2c0 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -5,30 +5,25 @@  defmodule Pleroma.Web.Feed.UserController do    use Pleroma.Web, :controller -  alias Fallback.RedirectController +  alias Pleroma.Config    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.ActivityPubController    alias Pleroma.Web.Feed.FeedView -  plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) +  plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect])    action_fallback(:errors)    def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do      with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do -      RedirectController.redirector_with_meta(conn, %{user: user}) +      Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})      end    end    def feed_redirect(%{assigns: %{format: format}} = conn, _params)        when format in ["json", "activity+json"] do -    with %{halted: false} = conn <- -           Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, -             unless_func: &Pleroma.Web.FederatingPlug.federating?/1 -           ) do -      ActivityPubController.call(conn, :user) -    end +    ActivityPubController.call(conn, :user)    end    def feed_redirect(conn, %{"nickname" => nickname}) do @@ -37,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"], @@ -70,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 @@ -82,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 diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex index 478a83518..ace44afd1 100644 --- a/lib/pleroma/web/mailer/subscription_controller.ex +++ b/lib/pleroma/web/mailer/subscription_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.Mailer.SubscriptionController do    use Pleroma.Web, :controller diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 43ec70021..08f92d55f 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -5,9 +5,9 @@  defmodule Pleroma.Web.MastoFEController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index ca1a79f5e..784fdc975 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -15,9 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do      ]    alias Pleroma.Maps -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder @@ -29,6 +26,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.OAuth.OAuthController    alias Pleroma.Web.OAuth.OAuthView +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    alias Pleroma.Web.TwitterAPI.TwitterAPI    plug(Pleroma.Web.ApiSpec.CastAndValidate) @@ -177,7 +177,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do      user_params =        [          :no_rich_text, -        :locked,          :hide_followers_count,          :hide_follows_count,          :hide_followers, @@ -186,7 +185,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do          :show_role,          :skip_thread_containment,          :allow_following_move, -        :discoverable,          :accepts_chat_messages        ]        |> Enum.reduce(%{}, fn key, acc -> @@ -210,6 +208,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do          if bot, do: {:ok, "Service"}, else: {:ok, "Person"}        end)        |> Maps.put_if_present(:actor_type, params[:actor_type]) +      |> Maps.put_if_present(:is_locked, params[:locked]) +      |> Maps.put_if_present(:is_discoverable, params[:discoverable])      # What happens here:      # @@ -442,15 +442,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    end    @doc "GET /api/v1/mutes" -  def mutes(%{assigns: %{user: user}} = conn, _) do -    users = User.muted_users(user, _restrict_deactivated = true) -    render(conn, "index.json", users: users, for: user, as: :user) +  def mutes(%{assigns: %{user: user}} = conn, params) do +    users = +      user +      |> User.muted_users_relation(_restrict_deactivated = true) +      |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true)) + +    conn +    |> add_link_headers(users) +    |> render("index.json", users: users, for: user, as: :user)    end    @doc "GET /api/v1/blocks" -  def blocks(%{assigns: %{user: user}} = conn, _) do -    users = User.blocked_users(user, _restrict_deactivated = true) -    render(conn, "index.json", users: users, for: user, as: :user) +  def blocks(%{assigns: %{user: user}} = conn, params) do +    users = +      user +      |> User.blocked_users_relation(_restrict_deactivated = true) +      |> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true)) + +    conn +    |> add_link_headers(users) +    |> render("index.json", users: users, for: user, as: :user)    end    @doc "GET /api/v1/endorsements" diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index a516b6c20..143dcf80c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -5,12 +5,12 @@  defmodule Pleroma.Web.MastodonAPI.AppController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Repo    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Scopes    alias Pleroma.Web.OAuth.Token +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 57c0be5fe..9cc3984d0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) -  plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) +  plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)    @local_mastodon_name "Mastodon-Local" @@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do      redirect(conn, to: local_mastodon_root_path(conn))    end -  @doc "Local Mastodon FE login init action" +  # Local Mastodon FE login init action    def login(conn, %{"code" => auth_token}) do      with {:ok, app} <- get_or_make_app(),           {:ok, auth} <- Authorization.get_by_token(app, auth_token), @@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do      end    end -  @doc "Local Mastodon FE callback action" +  # Local Mastodon FE callback action    def login(conn, _) do      with {:ok, app} <- get_or_make_app() do        path = diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index f35ec3596..61347d8db 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]    alias Pleroma.Conversation.Participation -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Repo +  alias Pleroma.Web.Plugs.OAuthScopesPlug    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index c5f47c5df..872cb1f4d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do    plug(      :skip_plug, -    [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] +    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]      when action == :index    ) diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 9c2d093cd..503bd7d5f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -5,8 +5,8 @@  defmodule Pleroma.Web.MastodonAPI.DomainBlockController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index abbf0ce02..c71a34b15 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do    use Pleroma.Web, :controller    alias Pleroma.Filter -  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 748b6b475..f8cd7fa9f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -5,9 +5,9 @@  defmodule Pleroma.Web.MastodonAPI.FollowRequestController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)    plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index d8859731d..07a32491a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do    plug(      :skip_plug, -    [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] +    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]      when action in [:show, :peers]    ) diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 5daeaa780..f6b51bf02 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -5,9 +5,9 @@  defmodule Pleroma.Web.MastodonAPI.ListController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.Plugs.OAuthScopesPlug    @oauth_read_actions [:index, :show, :list_accounts] diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 85310edfa..0628b2b49 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -4,7 +4,7 @@  defmodule Pleroma.Web.MastodonAPI.MarkerController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index e7767de4e..9cf682c7b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    plug(      :skip_plug, -    [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] +    [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]      when action in [:empty_array, :empty_object]    ) diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 513de279f..161193134 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -6,11 +6,12 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do    use Pleroma.Web, :controller    alias Pleroma.Object -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.Plugs.OAuthScopesPlug    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) +  plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index e25cef30b..c3c8606f2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]    alias Pleroma.Notification -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.MastodonAPI.MastodonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index db46ffcfc..3dcd1c44f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -9,9 +9,9 @@ defmodule Pleroma.Web.MastodonAPI.PollController do    alias Pleroma.Activity    alias Pleroma.Object -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 405167108..156544f40 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -3,14 +3,12 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.ReportController do -  alias Pleroma.Plugs.OAuthScopesPlug -    use Pleroma.Web, :controller    action_fallback(Pleroma.Web.MastodonAPI.FallbackController)    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) +  plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 1719c67ea..322a46497 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.ScheduledActivity    alias Pleroma.Web.MastodonAPI.MastodonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5a983db39..0043c3a56 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do    use Pleroma.Web, :controller    alias Pleroma.Activity -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web    alias Pleroma.Web.ControllerHelper    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    require Logger diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index da14c0b6c..4d9be5240 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -13,8 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    alias Pleroma.Activity    alias Pleroma.Bookmark    alias Pleroma.Object -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.Repo    alias Pleroma.ScheduledActivity    alias Pleroma.User @@ -23,9 +21,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.ScheduledActivityView +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) + +  plug( +    :skip_plug, +    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show] +  )    @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} @@ -123,9 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    @doc """    POST /api/v1/statuses - -  Creates a scheduled status when `scheduled_at` param is present and it's far enough    """ +  # Creates a scheduled status when `scheduled_at` param is present and it's far enough    def create(          %{            assigns: %{user: user}, @@ -156,11 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do      end    end -  @doc """ -  POST /api/v1/statuses - -  Creates a regular status -  """ +  # Creates a regular status    def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do      params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index 34eac97c5..20138908c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:restrict_push_enabled) -  plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) +  plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["push"]})    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index f91df9ab7..5765271cf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do    require Logger    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) +  plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)    def open_api_operation(action) do      operation = String.to_existing_atom("#{action}_operation") diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 5272790d3..ac96520a3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -10,11 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    alias Pleroma.Config    alias Pleroma.Pagination -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) @@ -111,6 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do          |> Map.put(:blocking_user, user)          |> Map.put(:muting_user, user)          |> Map.put(:reply_filtering_user, user) +        |> Map.put(:instance, params[:instance])          |> ActivityPub.fetch_public_activities()        conn diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 121ba1693..3158d09ed 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -242,7 +242,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do        username: username_from_nickname(user.nickname),        acct: user.nickname,        display_name: display_name, -      locked: user.locked, +      locked: user.is_locked,        created_at: Utils.to_masto_date(user.inserted_at),        followers_count: followers_count,        following_count: following_count, @@ -261,7 +261,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          sensitive: false,          fields: user.raw_fields,          pleroma: %{ -          discoverable: user.discoverable, +          discoverable: user.is_discoverable,            actor_type: user.actor_type          }        }, @@ -388,7 +388,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      data      |> Kernel.put_in(        [:pleroma, :unread_conversation_count], -      user.unread_conversation_count +      Pleroma.Conversation.Participation.unread_count(user)      )    end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index a91994915..82fcff062 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -33,8 +33,15 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do        end      activity = Activity.get_by_id_with_object(last_activity_id) -    # Conversations return all users except the current user. -    users = Enum.reject(participation.recipients, &(&1.id == user.id)) + +    # Conversations return all users except the current user, +    # except when the current user is the only participant +    users = +      if length(participation.recipients) > 1 do +        Enum.reject(participation.recipients, &(&1.id == user.id)) +      else +        participation.recipients +      end      %{        id: participation.id |> to_string(), @@ -43,7 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do        last_status:          render(StatusView, "show.json",            activity: activity, -          direct_conversation_id: participation.id +          direct_conversation_id: participation.id, +          for: user          )      }    end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 1208dc9a0..4101f21d0 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do        expired: expired,        multiple: multiple,        votes_count: votes_count, -      voters_count: (multiple || nil) && voters_count(object), +      voters_count: voters_count(object),        options: options,        voted: voted?(params),        emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy.ex index 8656b8cad..8656b8cad 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy.ex diff --git a/lib/pleroma/web/media_proxy/invalidations/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex index bb81d8888..0b0cde68c 100644 --- a/lib/pleroma/web/media_proxy/invalidations/http.ex +++ b/lib/pleroma/web/media_proxy/invalidation/http.ex @@ -30,7 +30,7 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do        {:ok, %{status: status} = env} when 400 <= status and status < 500 ->          {:error, env} -      {:error, error} = error -> +      {:error, _} = error ->          error        _ -> diff --git a/lib/pleroma/web/media_proxy/invalidations/script.ex b/lib/pleroma/web/media_proxy/invalidation/script.ex index d32ffc50b..d32ffc50b 100644 --- a/lib/pleroma/web/media_proxy/invalidations/script.ex +++ b/lib/pleroma/web/media_proxy/invalidation/script.ex diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex index bd1459a17..bd1459a17 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/providers/feed.ex diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex index bb1b23208..bb1b23208 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/providers/open_graph.ex diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex index 767288f9c..767288f9c 100644 --- a/lib/pleroma/web/metadata/provider.ex +++ b/lib/pleroma/web/metadata/providers/provider.ex diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex index 8905c9c72..8905c9c72 100644 --- a/lib/pleroma/web/metadata/rel_me.ex +++ b/lib/pleroma/web/metadata/providers/rel_me.ex diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex index a1dcb6e15..900c2434d 100644 --- a/lib/pleroma/web/metadata/restrict_indexing.ex +++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do    """    @impl true -  def build_tags(%{user: %{local: true, discoverable: true}}), do: [] +  def build_tags(%{user: %{local: true, is_discoverable: true}}), do: []    def build_tags(_) do      [ diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index df34b033f..df34b033f 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex index 6cbbe8fd8..2a5c7c356 100644 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex @@ -5,10 +5,10 @@  defmodule Pleroma.Web.MongooseIM.MongooseIMController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.AuthenticationPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.Repo    alias Pleroma.User +  alias Pleroma.Web.Plugs.AuthenticationPlug +  alias Pleroma.Web.Plugs.RateLimiter    plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])    plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password) diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/o_auth.ex index 2f1b8708d..2f1b8708d 100644 --- a/lib/pleroma/web/oauth.ex +++ b/lib/pleroma/web/o_auth.ex diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/o_auth/app.ex index df99472e1..df99472e1 100644 --- a/lib/pleroma/web/oauth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex index 268ee5b63..268ee5b63 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/o_auth/authorization.ex diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex index a89ced886..a89ced886 100644 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ b/lib/pleroma/web/o_auth/fallback_controller.ex diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex index f102c93e7..f102c93e7 100644 --- a/lib/pleroma/web/oauth/mfa_controller.ex +++ b/lib/pleroma/web/o_auth/mfa_controller.ex diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex index 5d87db268..5d87db268 100644 --- a/lib/pleroma/web/oauth/mfa_view.ex +++ b/lib/pleroma/web/o_auth/mfa_view.ex diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index a4152e840..d2f9d1ceb 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Helpers.UriHelper    alias Pleroma.Maps    alias Pleroma.MFA -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.Registration    alias Pleroma.Repo    alias Pleroma.User @@ -23,6 +22,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken    alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken +  alias Pleroma.Web.Plugs.RateLimiter    require Logger @@ -31,7 +31,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do    plug(:fetch_session)    plug(:fetch_flash) -  plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]) +  plug(:skip_plug, [ +    Pleroma.Web.Plugs.OAuthScopesPlug, +    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  ])    plug(RateLimiter, [name: :authentication] when action == :create_authorization) diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex index f55247ebd..f55247ebd 100644 --- a/lib/pleroma/web/oauth/oauth_view.ex +++ b/lib/pleroma/web/o_auth/o_auth_view.ex diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex index 6f06f1431..90b9a0471 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/o_auth/scopes.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.OAuth.Scopes do    Functions for dealing with scopes.    """ -  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    @doc """    Fetch scopes from request params. diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/o_auth/token.ex index de37998f2..de37998f2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex index fd6d9b112..fd6d9b112 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/o_auth/token/query.ex diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex index 625b0fde2..625b0fde2 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex index 069c1ee21..069c1ee21 100644 --- a/lib/pleroma/web/oauth/token/strategy/revoke.ex +++ b/lib/pleroma/web/o_auth/token/strategy/revoke.ex diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex index 43aeab6b0..43aeab6b0 100644 --- a/lib/pleroma/web/oauth/token/utils.ex +++ b/lib/pleroma/web/o_auth/token/utils.ex diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index de1b0b3f0..668ae0ea4 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -5,28 +5,24 @@  defmodule Pleroma.Web.OStatus.OStatusController do    use Pleroma.Web, :controller -  alias Fallback.RedirectController    alias Pleroma.Activity    alias Pleroma.Object -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPubController    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.Endpoint +  alias Pleroma.Web.Fallback.RedirectController    alias Pleroma.Web.Metadata.PlayerView +  alias Pleroma.Web.Plugs.RateLimiter    alias Pleroma.Web.Router -  plug(Pleroma.Plugs.EnsureAuthenticatedPlug, -    unless_func: &Pleroma.Web.FederatingPlug.federating?/1 -  ) -    plug(      RateLimiter,      [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]    )    plug( -    Pleroma.Plugs.SetFormatPlug +    Pleroma.Web.Plugs.SetFormatPlug      when action in [:object, :activity, :notice]    ) @@ -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 diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 563edded7..30cf83567 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -8,16 +8,21 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do    import Pleroma.Web.ControllerHelper,      only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    require Pleroma.Constants    plug( +    Majic.Plug, +    [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] +  ) + +  plug(      OpenApiSpex.Plug.PutApiSpec,      [module: Pleroma.Web.ApiSpec] when action == :confirmation_resend    ) diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex new file mode 100644 index 000000000..dd0a2e22f --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BackupController do +  use Pleroma.Web, :controller + +  alias Pleroma.User.Backup +  alias Pleroma.Web.Plugs.OAuthScopesPlug + +  action_fallback(Pleroma.Web.MastodonAPI.FallbackController) +  plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create]) +  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation + +  def index(%{assigns: %{user: user}} = conn, _params) do +    backups = Backup.list(user) +    render(conn, "index.json", backups: backups) +  end + +  def create(%{assigns: %{user: user}} = conn, _params) do +    with {:ok, _} <- Backup.create(user) do +      backups = Backup.list(user) +      render(conn, "index.json", backups: backups) +    end +  end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index 867cff829..77564b342 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -4,17 +4,18 @@  defmodule Pleroma.Web.PleromaAPI.ChatController do    use Pleroma.Web, :controller +  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] +    alias Pleroma.Activity    alias Pleroma.Chat    alias Pleroma.Chat.MessageReference    alias Pleroma.Object    alias Pleroma.Pagination -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView -  alias Pleroma.Web.PleromaAPI.ChatView +  alias Pleroma.Web.Plugs.OAuthScopesPlug    import Ecto.Query @@ -47,7 +48,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do        }) do      with %MessageReference{} = cm_ref <-             MessageReference.get_by_id(message_id), -         ^chat_id <- cm_ref.chat_id |> to_string(), +         ^chat_id <- to_string(cm_ref.chat_id),           %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),           {:ok, _} <- remove_or_delete(cm_ref, user) do        conn @@ -68,22 +69,18 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  defp remove_or_delete(cm_ref, _) do -    cm_ref -    |> MessageReference.delete() -  end +  defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)    def post_chat_message( -        %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn, -        %{ -          id: id -        } +        %{body_params: params, assigns: %{user: user}} = conn, +        %{id: id}        ) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id),           %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),           {:ok, activity} <-             CommonAPI.post_chat_message(user, recipient, params[:content], -             media_id: params[:media_id] +             media_id: params[:media_id], +             idempotency_key: idempotency_key(conn)             ),           message <- Object.normalize(activity, false),           cm_ref <- MessageReference.for_chat_and_object(chat, message) do @@ -103,13 +100,12 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ -        id: chat_id, -        message_id: message_id -      }) do -    with %MessageReference{} = cm_ref <- -           MessageReference.get_by_id(message_id), -         ^chat_id <- cm_ref.chat_id |> to_string(), +  def mark_message_as_read( +        %{assigns: %{user: %{id: user_id}}} = conn, +        %{id: chat_id, message_id: message_id} +      ) do +    with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), +         ^chat_id <- to_string(cm_ref.chat_id),           %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),           {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do        conn @@ -119,66 +115,60 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do    end    def mark_as_read( -        %{ -          body_params: %{last_read_id: last_read_id}, -          assigns: %{user: %{id: user_id}} -        } = conn, +        %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,          %{id: id}        ) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), -         {_n, _} <- -           MessageReference.set_all_seen_for_chat(chat, last_read_id) do -      conn -      |> put_view(ChatView) -      |> render("show.json", chat: chat) +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id), +         {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do +      render(conn, "show.json", chat: chat)      end    end -  def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do -      cm_refs = +  def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do +      chat_message_refs =          chat          |> MessageReference.for_chat_query()          |> Pagination.fetch_paginated(params)        conn +      |> add_link_headers(chat_message_refs)        |> put_view(MessageReferenceView) -      |> render("index.json", chat_message_references: cm_refs) -    else -      _ -> -        conn -        |> put_status(:not_found) -        |> json(%{error: "not found"}) +      |> render("index.json", chat_message_references: chat_message_refs)      end    end -  def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do -    blocked_ap_ids = User.blocked_users_ap_ids(user) +  def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do +    exclude_users = +      User.blocked_users_ap_ids(user) ++ +        if params[:with_muted], do: [], else: User.muted_users_ap_ids(user)      chats = -      Chat.for_user_query(user_id) -      |> where([c], c.recipient not in ^blocked_ap_ids) +      user_id +      |> Chat.for_user_query() +      |> where([c], c.recipient not in ^exclude_users)        |> Repo.all() -    conn -    |> put_view(ChatView) -    |> render("index.json", chats: chats) +    render(conn, "index.json", chats: chats)    end -  def create(%{assigns: %{user: user}} = conn, params) do -    with %User{ap_id: recipient} <- User.get_by_id(params[:id]), +  def create(%{assigns: %{user: user}} = conn, %{id: id}) do +    with %User{ap_id: recipient} <- User.get_cached_by_id(id),           {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do -      conn -      |> put_view(ChatView) -      |> render("show.json", chat: chat) +      render(conn, "show.json", chat: chat)      end    end -  def show(%{assigns: %{user: user}} = conn, params) do -    with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do -      conn -      |> put_view(ChatView) -      |> render("show.json", chat: chat) +  def show(%{assigns: %{user: user}} = conn, %{id: id}) do +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do +      render(conn, "show.json", chat: chat) +    end +  end + +  defp idempotency_key(conn) do +    case get_req_header(conn, "idempotency-key") do +      [key] -> key +      _ -> nil      end    end  end diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex index 3d007f324..df52b7566 100644 --- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]    alias Pleroma.Conversation.Participation -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index 71c53df1d..428c97de6 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.PleromaAPI.EmojiFileController do    use Pleroma.Web, :controller @@ -7,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug( -    Pleroma.Plugs.OAuthScopesPlug, +    Pleroma.Web.Plugs.OAuthScopesPlug,      %{scopes: ["write"], admin: true}      when action in [             :create, diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index e3969fee1..a9accc5af 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.PleromaAPI.EmojiPackController do    use Pleroma.Web, :controller @@ -6,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug( -    Pleroma.Plugs.OAuthScopesPlug, +    Pleroma.Web.Plugs.OAuthScopesPlug,      %{scopes: ["write"], admin: true}      when action in [             :import_from_filesystem, @@ -18,13 +22,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do           ]    ) -  @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] -  plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive]) +  @skip_plugs [ +    Pleroma.Web.Plugs.OAuthScopesPlug, +    Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  ] +  plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show])    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation -  def remote(conn, %{url: url}) do -    with {:ok, packs} <- Pack.list_remote(url) do +  def remote(conn, params) do +    with {:ok, packs} <- +           Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do        json(conn, packs)      else        {:error, :not_shareable} -> 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 7f9254c13..ae199a50f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do    alias Pleroma.Activity    alias Pleroma.Object -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete]) diff --git a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex new file mode 100644 index 000000000..9e97480df --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.InstancesController do +  use Pleroma.Web, :controller + +  alias Pleroma.Instances + +  plug(Pleroma.Web.ApiSpec.CastAndValidate) + +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaInstancesOperation + +  def show(conn, _params) do +    unreachable = +      Instances.get_consistently_unreachable() +      |> Map.new(fn {host, date} -> {host, to_string(date)} end) + +    json(conn, %{"unreachable" => unreachable}) +  end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index df6c50ca5..15210f1e6 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -5,10 +5,11 @@  defmodule Pleroma.Web.PleromaAPI.MascotController do    use Pleroma.Web, :controller -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update])    plug(Pleroma.Web.ApiSpec.CastAndValidate)    plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)    plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) @@ -22,14 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do    @doc "PUT /api/v1/pleroma/mascot"    def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do -    with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), -         # Reject if not an image -         %{type: "image"} = attachment <- render_attachment(object) do +    with {:content_type, "image" <> _} <- {:content_type, file.content_type}, +         {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do +      attachment = render_attachment(object)        {:ok, _user} = User.mascot_update(user, attachment)        json(conn, attachment)      else -      %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") +      {:content_type, _} -> +        render_error(conn, :unsupported_media_type, "mascots can only be images")      end    end diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex index 3ed8bd294..fa32aaa84 100644 --- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -6,10 +6,14 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do    use Pleroma.Web, :controller    alias Pleroma.Notification -  alias Pleroma.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) -  plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read) + +  plug( +    Pleroma.Web.Plugs.OAuthScopesPlug, +    %{scopes: ["write:notifications"]} when action == :mark_as_read +  ) +    plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)    defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index e9a4fba92..632d65434 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -7,10 +7,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex index b86791d09..eba452300 100644 --- a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do    alias Pleroma.MFA    alias Pleroma.MFA.TOTP -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings]) diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex index f10c45750..7f089af1c 100644 --- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do    require Logger -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.ApiSpec +  alias Pleroma.Web.Plugs.OAuthScopesPlug    plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow)    plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) diff --git a/lib/pleroma/web/pleroma_api/views/backup_view.ex b/lib/pleroma/web/pleroma_api/views/backup_view.ex new file mode 100644 index 000000000..af75876aa --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/backup_view.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BackupView do +  use Pleroma.Web, :view + +  alias Pleroma.User.Backup +  alias Pleroma.Web.CommonAPI.Utils + +  def render("show.json", %{backup: %Backup{} = backup}) do +    %{ +      content_type: backup.content_type, +      url: download_url(backup), +      file_size: backup.file_size, +      processed: backup.processed, +      inserted_at: Utils.to_masto_date(backup.inserted_at) +    } +  end + +  def render("index.json", %{backups: backups}) do +    render_many(backups, __MODULE__, "show.json") +  end + +  def download_url(%Backup{file_name: file_name}) do +    Pleroma.Web.Endpoint.url() <> "/media/backups/" <> file_name +  end +end diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex index d4e08b50d..c058fb340 100644 --- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do    use Pleroma.Web, :view +  alias Pleroma.Maps    alias Pleroma.User    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.MastodonAPI.StatusView @@ -37,6 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do            Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)          )      } +    |> put_idempotency_key()    end    def render("index.json", opts) do @@ -47,4 +49,13 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do        Map.put(opts, :as, :chat_message_reference)      )    end + +  defp put_idempotency_key(data) do +    with {:ok, idempotency_key} <- Cachex.get(:chat_message_id_idempotency_key_cache, data.id) do +      data +      |> Maps.put_if_present(:idempotency_key, idempotency_key) +    else +      _ -> data +    end +  end  end diff --git a/lib/pleroma/web/plug.ex b/lib/pleroma/web/plug.ex new file mode 100644 index 000000000..840b35072 --- /dev/null +++ b/lib/pleroma/web/plug.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plug do +  # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug` +  @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() +end diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex index 2e54df47a..d7d4e4092 100644 --- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex @@ -2,12 +2,12 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do +defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do    import Plug.Conn -  alias Pleroma.Plugs.OAuthScopesPlug -  alias Pleroma.Plugs.RateLimiter    alias Pleroma.User +  alias Pleroma.Web.Plugs.OAuthScopesPlug +  alias Pleroma.Web.Plugs.RateLimiter    def init(options) do      options diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index 057ea42f1..e2a8b1b69 100644 --- a/lib/pleroma/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -2,8 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AuthenticationPlug do -  alias Pleroma.Plugs.OAuthScopesPlug +defmodule Pleroma.Web.Plugs.AuthenticationPlug do    alias Pleroma.User    import Plug.Conn @@ -65,7 +64,7 @@ defmodule Pleroma.Plugs.AuthenticationPlug do        conn        |> assign(:user, auth_user) -      |> OAuthScopesPlug.skip_plug() +      |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()      else        conn      end diff --git a/lib/pleroma/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex index af7ecb0d8..4dadfb000 100644 --- a/lib/pleroma/plugs/basic_auth_decoder_plug.ex +++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.BasicAuthDecoderPlug do +defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do    import Plug.Conn    def init(options) do diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex index f65c2a189..6de01804a 100644 --- a/lib/pleroma/plugs/cache.ex +++ b/lib/pleroma/web/plugs/cache.ex @@ -2,19 +2,19 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.Cache do +defmodule Pleroma.Web.Plugs.Cache do    @moduledoc """    Caches successful GET responses.    To enable the cache add the plug to a router pipeline or controller: -      plug(Pleroma.Plugs.Cache) +      plug(Pleroma.Web.Plugs.Cache)    ## Configuration    To configure the plug you need to pass settings as the second argument to the `plug/2` macro: -      plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true]) +      plug(Pleroma.Web.Plugs.Cache, [ttl: nil, query_params: true])    Available options: diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/web/plugs/digest_plug.ex index b521b3073..b521b3073 100644 --- a/lib/pleroma/plugs/digest.ex +++ b/lib/pleroma/web/plugs/digest_plug.ex diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex index 3fe550806..ea2af6881 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do +defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do    import Plug.Conn    import Pleroma.Web.TranslationHelpers diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex index 7265bb87a..3bebdac6d 100644 --- a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex +++ b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do +defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do    import Pleroma.Web.TranslationHelpers    import Plug.Conn diff --git a/lib/pleroma/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex index 9795cdbde..70d3091f0 100644 --- a/lib/pleroma/plugs/ensure_user_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_key_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureUserKeyPlug do +defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do    import Plug.Conn    def init(opts) do diff --git a/lib/pleroma/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex index 66b8d5de5..0925ded4d 100644 --- a/lib/pleroma/plugs/expect_authenticated_check_plug.ex +++ b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex @@ -2,9 +2,9 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.ExpectAuthenticatedCheckPlug do +defmodule Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug do    @moduledoc """ -  Marks `Pleroma.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain. +  Marks `Pleroma.Web.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain.    No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).    """ diff --git a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex index ba0ef76bd..ace512a78 100644 --- a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex +++ b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex @@ -2,9 +2,9 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug do +defmodule Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug do    @moduledoc """ -  Marks `Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug +  Marks `Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug    chain.    No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`). diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/web/plugs/federating_plug.ex index 09038f3c6..3c90a7644 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/web/plugs/federating_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.FederatingPlug do +defmodule Pleroma.Web.Plugs.FederatingPlug do    import Plug.Conn    def init(options) do diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 11a0d5382..1b0b36813 100644 --- a/lib/pleroma/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.FrontendStatic do +defmodule Pleroma.Web.Plugs.FrontendStatic do    require Pleroma.Constants    @moduledoc """ @@ -34,22 +34,26 @@ defmodule Pleroma.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 diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index c363b193b..45aaf188e 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.HTTPSecurityPlug do +defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do    alias Pleroma.Config    import Plug.Conn diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/web/plugs/http_signature_plug.ex index 036e2a773..036e2a773 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/web/plugs/http_signature_plug.ex diff --git a/lib/pleroma/plugs/idempotency_plug.ex b/lib/pleroma/web/plugs/idempotency_plug.ex index f41397075..254a790b0 100644 --- a/lib/pleroma/plugs/idempotency_plug.ex +++ b/lib/pleroma/web/plugs/idempotency_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.IdempotencyPlug do +defmodule Pleroma.Web.Plugs.IdempotencyPlug do    import Phoenix.Controller, only: [json: 2]    import Plug.Conn diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex index 0fb57e422..54b9175df 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.InstanceStatic do +defmodule Pleroma.Web.Plugs.InstanceStatic do    require Pleroma.Constants    @moduledoc """ @@ -16,7 +16,7 @@ defmodule Pleroma.Plugs.InstanceStatic do      instance_path =        Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) -    frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary) +    frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary)      (File.exists?(instance_path) && instance_path) ||        (frontend_path && File.exists?(frontend_path) && frontend_path) || diff --git a/lib/pleroma/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex index d346e01a6..2a54d0b59 100644 --- a/lib/pleroma/plugs/legacy_authentication_plug.ex +++ b/lib/pleroma/web/plugs/legacy_authentication_plug.ex @@ -2,10 +2,9 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.LegacyAuthenticationPlug do +defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do    import Plug.Conn -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    def init(options) do @@ -29,7 +28,7 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlug do        conn        |> assign(:auth_user, user)        |> assign(:user, user) -      |> OAuthScopesPlug.skip_plug() +      |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()      else        _ ->          conn diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index f44d4dee5..f44d4dee5 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index 6fa71ef47..c7b58d90f 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthPlug do +defmodule Pleroma.Web.Plugs.OAuthPlug do    import Plug.Conn    import Ecto.Query diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex index b1a736d78..cfc30837c 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthScopesPlug do +defmodule Pleroma.Web.Plugs.OAuthScopesPlug do    import Plug.Conn    import Pleroma.Web.Gettext diff --git a/lib/pleroma/plugs/plug_helper.ex b/lib/pleroma/web/plugs/plug_helper.ex index 9c67be8ef..b314e7596 100644 --- a/lib/pleroma/plugs/plug_helper.ex +++ b/lib/pleroma/web/plugs/plug_helper.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.PlugHelper do +defmodule Pleroma.Web.Plugs.PlugHelper do    @moduledoc "Pleroma Plug helper"    @called_plugs_list_id :called_plugs diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex index c51e2c634..a589610d1 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/web/plugs/rate_limiter.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.RateLimiter do +defmodule Pleroma.Web.Plugs.RateLimiter do    @moduledoc """    ## Configuration @@ -35,8 +35,8 @@ defmodule Pleroma.Plugs.RateLimiter do    AllowedSyntax: -      plug(Pleroma.Plugs.RateLimiter, name: :limiter_name) -      plug(Pleroma.Plugs.RateLimiter, options)   # :name is a required option +      plug(Pleroma.Web.Plugs.RateLimiter, name: :limiter_name) +      plug(Pleroma.Web.Plugs.RateLimiter, options)   # :name is a required option    Allowed options: @@ -46,11 +46,11 @@ defmodule Pleroma.Plugs.RateLimiter do    Inside a controller: -      plug(Pleroma.Plugs.RateLimiter, [name: :one] when action == :one) -      plug(Pleroma.Plugs.RateLimiter, [name: :two] when action in [:two, :three]) +      plug(Pleroma.Web.Plugs.RateLimiter, [name: :one] when action == :one) +      plug(Pleroma.Web.Plugs.RateLimiter, [name: :two] when action in [:two, :three])        plug( -        Pleroma.Plugs.RateLimiter, +        Pleroma.Web.Plugs.RateLimiter,          [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]          when action in ~w(fav_status unfav_status)a        ) @@ -59,7 +59,7 @@ defmodule Pleroma.Plugs.RateLimiter do        pipeline :api do          ... -        plug(Pleroma.Plugs.RateLimiter, name: :one) +        plug(Pleroma.Web.Plugs.RateLimiter, name: :one)          ...        end    """ @@ -67,8 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do    import Plug.Conn    alias Pleroma.Config -  alias Pleroma.Plugs.RateLimiter.LimiterSupervisor    alias Pleroma.User +  alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor    require Logger diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex index 884268d96..5642bb205 100644 --- a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex +++ b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex @@ -1,4 +1,8 @@ -defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor do    use DynamicSupervisor    import Cachex.Spec diff --git a/lib/pleroma/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex index 9672f7876..a1c84063d 100644 --- a/lib/pleroma/plugs/rate_limiter/supervisor.ex +++ b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex @@ -1,4 +1,8 @@ -defmodule Pleroma.Plugs.RateLimiter.Supervisor do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do    use Supervisor    def start_link(opts) do @@ -7,7 +11,7 @@ defmodule Pleroma.Plugs.RateLimiter.Supervisor do    def init(_args) do      children = [ -      Pleroma.Plugs.RateLimiter.LimiterSupervisor +      Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor      ]      opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex new file mode 100644 index 000000000..401e2cbfa --- /dev/null +++ b/lib/pleroma/web/plugs/remote_ip.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RemoteIp do +  @moduledoc """ +  This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. +  """ + +  alias Pleroma.Config +  import Plug.Conn + +  @behaviour Plug + +  def init(_), do: nil + +  def call(%{remote_ip: original_remote_ip} = conn, _) do +    if Config.get([__MODULE__, :enabled]) do +      %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts()) +      assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) +    else +      conn +    end +  end + +  defp remote_ip_opts do +    headers = Config.get([__MODULE__, :headers], []) |> MapSet.new() +    reserved = Config.get([__MODULE__, :reserved], []) + +    proxies = +      Config.get([__MODULE__, :proxies], []) +      |> Enum.concat(reserved) +      |> Enum.map(&maybe_add_cidr/1) + +    {headers, proxies} +  end + +  defp maybe_add_cidr(proxy) when is_binary(proxy) do +    proxy = +      cond do +        "/" in String.codepoints(proxy) -> proxy +        InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32" +        InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128" +      end + +    InetCidr.parse(proxy, true) +  end +end diff --git a/lib/pleroma/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex index 0f83a5e53..6e176d553 100644 --- a/lib/pleroma/plugs/session_authentication_plug.ex +++ b/lib/pleroma/web/plugs/session_authentication_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SessionAuthenticationPlug do +defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do    import Plug.Conn    def init(options) do diff --git a/lib/pleroma/plugs/set_format_plug.ex b/lib/pleroma/web/plugs/set_format_plug.ex index c03fcb28d..c16d2f81d 100644 --- a/lib/pleroma/plugs/set_format_plug.ex +++ b/lib/pleroma/web/plugs/set_format_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetFormatPlug do +defmodule Pleroma.Web.Plugs.SetFormatPlug do    import Plug.Conn, only: [assign: 3, fetch_query_params: 1]    def init(_), do: nil diff --git a/lib/pleroma/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex index 9a21d0a9d..d9d24b93f 100644 --- a/lib/pleroma/plugs/set_locale_plug.ex +++ b/lib/pleroma/web/plugs/set_locale_plug.ex @@ -3,7 +3,7 @@  # SPDX-License-Identifier: AGPL-3.0-only  # NOTE: this module is based on https://github.com/smeevil/set_locale -defmodule Pleroma.Plugs.SetLocalePlug do +defmodule Pleroma.Web.Plugs.SetLocalePlug do    import Plug.Conn, only: [get_req_header: 2, assign: 3]    def init(_), do: nil diff --git a/lib/pleroma/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex index 730c4ac74..e520159e4 100644 --- a/lib/pleroma/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetUserSessionIdPlug do +defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do    import Plug.Conn    alias Pleroma.User diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/web/plugs/static_fe_plug.ex index 143665c71..658a1052e 100644 --- a/lib/pleroma/plugs/static_fe_plug.ex +++ b/lib/pleroma/web/plugs/static_fe_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.StaticFEPlug do +defmodule Pleroma.Web.Plugs.StaticFEPlug do    import Plug.Conn    alias Pleroma.Web.StaticFE.StaticFEController diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/web/plugs/trailing_format_plug.ex index 8b4d5fc9f..e3f57c14a 100644 --- a/lib/pleroma/plugs/trailing_format_plug.ex +++ b/lib/pleroma/web/plugs/trailing_format_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.TrailingFormatPlug do +defmodule Pleroma.Web.Plugs.TrailingFormatPlug do    @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."    @behaviour Plug diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 40984cfc0..402a8bb34 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UploadedMedia do +defmodule Pleroma.Web.Plugs.UploadedMedia do    @moduledoc """    """ diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex index 23e800a74..fa28ee48b 100644 --- a/lib/pleroma/plugs/user_enabled_plug.ex +++ b/lib/pleroma/web/plugs/user_enabled_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserEnabledPlug do +defmodule Pleroma.Web.Plugs.UserEnabledPlug do    import Plug.Conn    alias Pleroma.User diff --git a/lib/pleroma/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex index 235c77d85..4039600da 100644 --- a/lib/pleroma/plugs/user_fetcher_plug.ex +++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserFetcherPlug do +defmodule Pleroma.Web.Plugs.UserFetcherPlug do    alias Pleroma.User    import Plug.Conn diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/web/plugs/user_is_admin_plug.ex index 488a61d1d..531c965f0 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/web/plugs/user_is_admin_plug.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserIsAdminPlug do +defmodule Pleroma.Web.Plugs.UserIsAdminPlug do    import Pleroma.Web.TranslationHelpers    import Plug.Conn diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/providers/instance.ex index 50d1f3382..a549bb1eb 100644 --- a/lib/pleroma/web/preload/instance.ex +++ b/lib/pleroma/web/preload/providers/instance.ex @@ -3,15 +3,17 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Preload.Providers.Instance do -  alias Pleroma.Plugs.InstanceStatic    alias Pleroma.Web.MastodonAPI.InstanceView    alias Pleroma.Web.Nodeinfo.Nodeinfo +  alias Pleroma.Web.Plugs.InstanceStatic    alias Pleroma.Web.Preload.Providers.Provider +  alias Pleroma.Web.TwitterAPI.UtilView    @behaviour Provider    @instance_url "/api/v1/instance"    @panel_url "/instance/panel.html"    @nodeinfo_url "/nodeinfo/2.0.json" +  @fe_config_url "/api/pleroma/frontend_configurations"    @impl Provider    def generate_terms(_params) do @@ -19,6 +21,7 @@ defmodule Pleroma.Web.Preload.Providers.Instance do      |> build_info_tag()      |> build_panel_tag()      |> build_nodeinfo_tag() +    |> build_fe_config_tag()    end    defp build_info_tag(acc) do @@ -47,4 +50,10 @@ defmodule Pleroma.Web.Preload.Providers.Instance do          Map.put(acc, @nodeinfo_url, nodeinfo_data)      end    end + +  defp build_fe_config_tag(acc) do +    fe_data = UtilView.render("frontend_configurations.json", %{}) + +    Map.put(acc, @fe_config_url, fe_data) +  end  end diff --git a/lib/pleroma/web/preload/provider.ex b/lib/pleroma/web/preload/providers/provider.ex index 7ef595a34..7ef595a34 100644 --- a/lib/pleroma/web/preload/provider.ex +++ b/lib/pleroma/web/preload/providers/provider.ex diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/providers/timelines.ex index b279a865d..b279a865d 100644 --- a/lib/pleroma/web/preload/timelines.ex +++ b/lib/pleroma/web/preload/providers/timelines.ex diff --git a/lib/pleroma/web/preload/user.ex b/lib/pleroma/web/preload/providers/user.ex index b3d2e9b8d..b3d2e9b8d 100644 --- a/lib/pleroma/web/preload/user.ex +++ b/lib/pleroma/web/preload/providers/user.ex diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push.ex index b80a6438d..b80a6438d 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push.ex diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 16368485e..da535aa68 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do    @types ["Create", "Follow", "Announce", "Like", "Move"]    @doc "Performs sending notifications for user subscriptions" -  @spec perform(Notification.t()) :: list(any) | :error +  @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}    def perform(          %{            activity: %{data: %{"type" => activity_type}} = activity, @@ -64,20 +64,20 @@ defmodule Pleroma.Web.Push.Impl do    @doc "Push message to web"    def push_message(body, sub, api_key, subscription) do      case WebPushEncryption.send_web_push(body, sub, api_key) do -      {:ok, %{status_code: code}} when 400 <= code and code < 500 -> +      {:ok, %{status: code}} when code in 400..499 ->          Logger.debug("Removing subscription record")          Repo.delete!(subscription)          :ok -      {:ok, %{status_code: code}} when 200 <= code and code < 300 -> +      {:ok, %{status: code}} when code in 200..299 ->          :ok -      {:ok, %{status_code: code}} -> +      {:ok, %{status: code}} ->          Logger.error("Web Push Notification failed with code: #{code}")          :error -      _ -> -        Logger.error("Web Push Notification failed with unknown error") +      error -> +        Logger.error("Web Push Notification failed with #{inspect(error)}")          :error      end    end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index d7a19df4a..d67b594b5 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -57,7 +57,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do    def fetch_data_for_object(object) do      with true <- Config.get([:rich_media, :enabled]), -         false <- object.data["sensitive"] || false,           {:ok, page_url} <-             HTML.extract_first_external_url_from_object(object),           :ok <- validate_page_url(page_url), diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex new file mode 100644 index 000000000..8353f0fff --- /dev/null +++ b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parser.TTL do +  @callback ttl(Map.t(), String.t()) :: Integer.t() | nil +end diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex index c5aaea2d4..fc4ef79c0 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -1,7 +1,11 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do    @behaviour Pleroma.Web.RichMedia.Parser.TTL -  @impl Pleroma.Web.RichMedia.Parser.TTL +  @impl true    def ttl(data, _url) do      image = Map.get(data, :image) diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex index 1fe6729c3..1fe6729c3 100644 --- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex deleted file mode 100644 index 6b3ec6d30..000000000 --- a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Pleroma.Web.RichMedia.Parser.TTL do -  @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()} -end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 42a9db21d..0f0538182 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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) @@ -12,31 +32,31 @@ defmodule Pleroma.Web.Router do    pipeline :oauth do      plug(:fetch_session) -    plug(Pleroma.Plugs.OAuthPlug) -    plug(Pleroma.Plugs.UserEnabledPlug) +    plug(Pleroma.Web.Plugs.OAuthPlug) +    plug(Pleroma.Web.Plugs.UserEnabledPlug)    end    pipeline :expect_authentication do -    plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug) +    plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)    end    pipeline :expect_public_instance_or_authentication do -    plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug) +    plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)    end    pipeline :authenticate do -    plug(Pleroma.Plugs.OAuthPlug) -    plug(Pleroma.Plugs.BasicAuthDecoderPlug) -    plug(Pleroma.Plugs.UserFetcherPlug) -    plug(Pleroma.Plugs.SessionAuthenticationPlug) -    plug(Pleroma.Plugs.LegacyAuthenticationPlug) -    plug(Pleroma.Plugs.AuthenticationPlug) +    plug(Pleroma.Web.Plugs.OAuthPlug) +    plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) +    plug(Pleroma.Web.Plugs.UserFetcherPlug) +    plug(Pleroma.Web.Plugs.SessionAuthenticationPlug) +    plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug) +    plug(Pleroma.Web.Plugs.AuthenticationPlug)    end    pipeline :after_auth do -    plug(Pleroma.Plugs.UserEnabledPlug) -    plug(Pleroma.Plugs.SetUserSessionIdPlug) -    plug(Pleroma.Plugs.EnsureUserKeyPlug) +    plug(Pleroma.Web.Plugs.UserEnabledPlug) +    plug(Pleroma.Web.Plugs.SetUserSessionIdPlug) +    plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)    end    pipeline :base_api do @@ -50,25 +70,25 @@ defmodule Pleroma.Web.Router do      plug(:expect_public_instance_or_authentication)      plug(:base_api)      plug(:after_auth) -    plug(Pleroma.Plugs.IdempotencyPlug) +    plug(Pleroma.Web.Plugs.IdempotencyPlug)    end    pipeline :authenticated_api do      plug(:expect_authentication)      plug(:base_api)      plug(:after_auth) -    plug(Pleroma.Plugs.EnsureAuthenticatedPlug) -    plug(Pleroma.Plugs.IdempotencyPlug) +    plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) +    plug(Pleroma.Web.Plugs.IdempotencyPlug)    end    pipeline :admin_api do      plug(:expect_authentication)      plug(:base_api) -    plug(Pleroma.Plugs.AdminSecretAuthenticationPlug) +    plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)      plug(:after_auth) -    plug(Pleroma.Plugs.EnsureAuthenticatedPlug) -    plug(Pleroma.Plugs.UserIsAdminPlug) -    plug(Pleroma.Plugs.IdempotencyPlug) +    plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) +    plug(Pleroma.Web.Plugs.UserIsAdminPlug) +    plug(Pleroma.Web.Plugs.IdempotencyPlug)    end    pipeline :mastodon_html do @@ -80,7 +100,7 @@ defmodule Pleroma.Web.Router do    pipeline :pleroma_html do      plug(:browser)      plug(:authenticate) -    plug(Pleroma.Plugs.EnsureUserKeyPlug) +    plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)    end    pipeline :well_known do @@ -129,16 +149,7 @@ defmodule Pleroma.Web.Router do    scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do      pipe_through(:admin_api) -    post("/users/follow", AdminAPIController, :user_follow) -    post("/users/unfollow", AdminAPIController, :user_unfollow) -      put("/users/disable_mfa", AdminAPIController, :disable_mfa) -    delete("/users", AdminAPIController, :user_delete) -    post("/users", AdminAPIController, :users_create) -    patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) -    patch("/users/activate", AdminAPIController, :user_activate) -    patch("/users/deactivate", AdminAPIController, :user_deactivate) -    patch("/users/approve", AdminAPIController, :user_approve)      put("/users/tag", AdminAPIController, :tag_users)      delete("/users/tag", AdminAPIController, :untag_users) @@ -161,6 +172,15 @@ defmodule Pleroma.Web.Router do        :right_delete_multiple      ) +    post("/users/follow", UserController, :follow) +    post("/users/unfollow", UserController, :unfollow) +    delete("/users", UserController, :delete) +    post("/users", UserController, :create) +    patch("/users/:nickname/toggle_activation", UserController, :toggle_activation) +    patch("/users/activate", UserController, :activate) +    patch("/users/deactivate", UserController, :deactivate) +    patch("/users/approve", UserController, :approve) +      get("/relay", RelayController, :index)      post("/relay", RelayController, :follow)      delete("/relay", RelayController, :unfollow) @@ -175,8 +195,8 @@ defmodule Pleroma.Web.Router do      get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)      patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) -    get("/users", AdminAPIController, :list_users) -    get("/users/:nickname", AdminAPIController, :user_show) +    get("/users", UserController, :list) +    get("/users/:nickname", UserController, :show)      get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)      get("/users/:nickname/chats", AdminAPIController, :list_user_chats) @@ -223,9 +243,25 @@ defmodule Pleroma.Web.Router do      get("/chats/:id", ChatController, :show)      get("/chats/:id/messages", ChatController, :messages)      delete("/chats/:id/messages/:message_id", ChatController, :delete_message) + +    post("/backups", AdminAPIController, :create_backup)    end    scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do +    scope "/pack" do +      pipe_through(:admin_api) + +      post("/", EmojiPackController, :create) +      patch("/", EmojiPackController, :update) +      delete("/", EmojiPackController, :delete) +    end + +    scope "/pack" do +      pipe_through(:api) + +      get("/", EmojiPackController, :show) +    end +      # Modifying packs      scope "/packs" do        pipe_through(:admin_api) @@ -234,21 +270,17 @@ defmodule Pleroma.Web.Router do        get("/remote", EmojiPackController, :remote)        post("/download", EmojiPackController, :download) -      post("/:name", EmojiPackController, :create) -      patch("/:name", EmojiPackController, :update) -      delete("/:name", EmojiPackController, :delete) - -      post("/:name/files", EmojiFileController, :create) -      patch("/:name/files", EmojiFileController, :update) -      delete("/:name/files", EmojiFileController, :delete) +      post("/files", EmojiFileController, :create) +      patch("/files", EmojiFileController, :update) +      delete("/files", EmojiFileController, :delete)      end      # Pack info / downloading      scope "/packs" do        pipe_through(:api) +        get("/", EmojiPackController, :index) -      get("/:name", EmojiPackController, :show) -      get("/:name/archive", EmojiPackController, :archive) +      get("/archive", EmojiPackController, :archive)      end    end @@ -343,6 +375,9 @@ defmodule Pleroma.Web.Router do        put("/mascot", MascotController, :update)        post("/scrobble", ScrobbleController, :create) + +      get("/backups", BackupController, :index) +      post("/backups", BackupController, :create)      end      scope [] do @@ -363,6 +398,7 @@ defmodule Pleroma.Web.Router do    scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do      pipe_through(:api)      get("/accounts/:id/scrobbles", ScrobbleController, :index) +    get("/federation_status", InstancesController, :show)    end    scope "/api/v1", Pleroma.Web.MastodonAPI do @@ -556,30 +592,43 @@ defmodule Pleroma.Web.Router do      )    end -  pipeline :ostatus do -    plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) -    plug(Pleroma.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 @@ -727,7 +776,7 @@ defmodule Pleroma.Web.Router do      get("/check_password", MongooseIMController, :check_password)    end -  scope "/", Fallback do +  scope "/", Pleroma.Web.Fallback do      get("/registration/:token", RedirectController, :registration_page)      get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)      get("/api*path", RedirectController, :api_not_implemented) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index a7a891b13..bdec0897a 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -17,74 +17,14 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do    plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)    plug(:assign_id) -  plug(Pleroma.Plugs.EnsureAuthenticatedPlug, -    unless_func: &Pleroma.Web.FederatingPlug.federating?/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/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer.ex index 5475f18a6..71fe27c89 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Web.Streamer do    alias Pleroma.Conversation.Participation    alias Pleroma.Notification    alias Pleroma.Object -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.OAuth.Token +  alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.StreamerView    @mix_env Mix.env() @@ -57,6 +57,15 @@ defmodule Pleroma.Web.Streamer do      {:ok, "hashtag:" <> tag}    end +  # Allow remote instance streams. +  def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do +    {:ok, "public:remote:" <> instance} +  end + +  def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do +    {:ok, "public:remote:media:" <> instance} +  end +    # Expand user streams.    def get_topic(          stream, diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 51603fe0c..3f28f1920 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -228,7 +228,7 @@    <body>      <div class="container">        <h1><%= Pleroma.Config.get([:instance, :name]) %></h1> -      <%= render @view_module, @view_template, assigns %> +      <%= @inner_content %>      </div>    </body>  </html> diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex index ca2caaf4d..82cabd889 100644 --- a/lib/pleroma/web/templates/layout/email_styled.html.eex +++ b/lib/pleroma/web/templates/layout/email_styled.html.eex @@ -181,7 +181,7 @@  							</div>  						</div>  					<% end %> -					<%= render @view_module, @view_template, assigns %> +					<%= @inner_content %>  				</td>  			</tr> diff --git a/lib/pleroma/web/templates/layout/metadata_player.html.eex b/lib/pleroma/web/templates/layout/metadata_player.html.eex index 460f28094..c00f6fa21 100644 --- a/lib/pleroma/web/templates/layout/metadata_player.html.eex +++ b/lib/pleroma/web/templates/layout/metadata_player.html.eex @@ -10,7 +10,7 @@ video, audio {  }  </style> -<%= render @view_module, @view_template, assigns %> +<%= @inner_content %>  </body>  </html> diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex index dc0ee2a5c..e6adb526b 100644 --- a/lib/pleroma/web/templates/layout/static_fe.html.eex +++ b/lib/pleroma/web/templates/layout/static_fe.html.eex @@ -9,7 +9,7 @@    </head>    <body>      <div class="container"> -      <%= render @view_module, @view_template, assigns %> +      <%= @inner_content %>      </div>    </body>  </html> diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/controller.ex index c2de26b0b..f42dba442 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/controller.ex @@ -6,10 +6,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    use Pleroma.Web, :controller    alias Pleroma.Notification -  alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.OAuth.Token +  alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug +  alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.TwitterAPI.TokenView    require Logger diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 072d889e2..4480a4922 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    alias Pleroma.Activity    alias Pleroma.MFA    alias Pleroma.Object.Fetcher -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.Auth.Authenticator    alias Pleroma.Web.Auth.TOTPAuthenticator @@ -18,11 +17,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do    @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] -  plug(Pleroma.Web.FederatingPlug) +  plug(Pleroma.Web.Plugs.FederatingPlug)    # Note: follower can submit the form (with password auth) not being signed in (having no token)    plug( -    OAuthScopesPlug, +    Pleroma.Web.Plugs.OAuthScopesPlug,      %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}      when action in [:do_follow]    ) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 70b0fbd54..9ead0d626 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    alias Pleroma.Emoji    alias Pleroma.Healthcheck    alias Pleroma.Notification -  alias Pleroma.Plugs.OAuthScopesPlug    alias Pleroma.User    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Plugs.OAuthScopesPlug    alias Pleroma.Web.WebFinger -  plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) +  plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)    plug(      OAuthScopesPlug, @@ -74,11 +74,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def frontend_configurations(conn, _params) do -    config = -      Config.get(:frontend_configurations, %{}) -      |> Enum.into(%{}) - -    json(conn, config) +    render(conn, "frontend_configurations.json")    end    def emoji(conn, _params) do diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index d3bdb4f62..98eea1d18 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.TwitterAPI.UtilView do    use Pleroma.Web, :view    import Phoenix.HTML.Form +  alias Pleroma.Config    alias Pleroma.Web    def status_net_config(instance) do @@ -19,4 +20,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do      </config>      """    end + +  def render("frontend_configurations.json", _) do +    Config.get(:frontend_configurations, %{}) +    |> Enum.into(%{}) +  end  end diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index 6b0fbe61e..bcdee6571 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.EmailView do    use Pleroma.Web, :view    import Phoenix.HTML diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex index fc3d20816..4562a9d6c 100644 --- a/lib/pleroma/web/views/mailer/subscription_view.ex +++ b/lib/pleroma/web/views/mailer/subscription_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.Mailer.SubscriptionView do    use Pleroma.Web, :view  end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger.ex index 6629f5356..6629f5356 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 7077b20d2..9f0938fc0 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do    alias Pleroma.Web.WebFinger -  plug(Pleroma.Plugs.SetFormatPlug) -  plug(Pleroma.Web.FederatingPlug) +  plug(Pleroma.Web.Plugs.SetFormatPlug) +  plug(Pleroma.Web.Plugs.FederatingPlug)    def host_meta(conn, _params) do      xml = WebFinger.host_meta() diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml.ex index c69a86a1e..c69a86a1e 100644 --- a/lib/pleroma/web/xml/xml.ex +++ b/lib/pleroma/web/xml.ex diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex new file mode 100644 index 000000000..5b4985983 --- /dev/null +++ b/lib/pleroma/workers/backup_worker.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.BackupWorker do +  use Oban.Worker, queue: :backup, max_attempts: 1 + +  alias Oban.Job +  alias Pleroma.User.Backup + +  def process(backup, admin_user_id \\ nil) do +    %{"op" => "process", "backup_id" => backup.id, "admin_user_id" => admin_user_id} +    |> new() +    |> Oban.insert() +  end + +  def schedule_deletion(backup) do +    days = Pleroma.Config.get([Backup, :purge_after_days]) +    time = 60 * 60 * 24 * days +    scheduled_at = Calendar.NaiveDateTime.add!(backup.inserted_at, time) + +    %{"op" => "delete", "backup_id" => backup.id} +    |> new(scheduled_at: scheduled_at) +    |> Oban.insert() +  end + +  def delete(backup) do +    %{"op" => "delete", "backup_id" => backup.id} +    |> new() +    |> Oban.insert() +  end + +  def perform(%Job{ +        args: %{"op" => "process", "backup_id" => backup_id, "admin_user_id" => admin_user_id} +      }) do +    with {:ok, %Backup{} = backup} <- +           backup_id |> Backup.get() |> Backup.process(), +         {:ok, _job} <- schedule_deletion(backup), +         :ok <- Backup.remove_outdated(backup), +         {:ok, _} <- +           backup +           |> Pleroma.Emails.UserEmail.backup_is_ready_email(admin_user_id) +           |> Pleroma.Emails.Mailer.deliver() do +      {:ok, backup} +    end +  end + +  def perform(%Job{args: %{"op" => "delete", "backup_id" => backup_id}}) do +    case Backup.get(backup_id) do +      %Backup{} = backup -> Backup.delete(backup) +      nil -> :ok +    end +  end +end diff --git a/lib/xml_builder.ex b/lib/pleroma/xml_builder.ex index 33b63a71f..33b63a71f 100644 --- a/lib/xml_builder.ex +++ b/lib/pleroma/xml_builder.ex  | 
