Merge branch 'develop' into feature/account-export
authorMark Felder <feld@FreeBSD.org>
Wed, 14 Oct 2020 20:27:15 +0000 (15:27 -0500)
committerMark Felder <feld@FreeBSD.org>
Wed, 14 Oct 2020 20:27:15 +0000 (15:27 -0500)
1  2 
CHANGELOG.md
config/config.exs
config/description.exs
docs/configuration/cheatsheet.md
lib/pleroma/moderation_log.ex
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/router.ex
test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs

diff --combined CHANGELOG.md
index 04b49d80a9ca8297beac0da7db96cc144a9989fd,36a84b1a895442822570cce1fe9ce63b510e3ade..d78670dcdc911d5b0158f1b469cb2a29a3e0a4ca
@@@ -9,10 -9,10 +9,11 @@@ The format is based on [Keep a Changelo
  - Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
  - Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
  - Mix task option for force-unfollowing relays
 +- Account backup
  
  ### Changed
  
+ - **Breaking** Requires `libmagic` (or `file`) to guess file types.
  - **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
  - **Breaking:** Sensitive/NSFW statuses no longer disable link previews.
  - Search: Users are now findable by their urls.
diff --combined config/config.exs
index 0e12d6e15f58f2bf4e13a7a2bc0dab43664c3529,2c614236033863a8560fbeae068f54eb0c24fe51..63e386250022cb901e48e6f62c5635814f992e8d
@@@ -551,7 -551,6 +551,7 @@@ config :pleroma, Oban
    queues: [
      activity_expiration: 10,
      token_expiration: 5,
 +    backup: 1,
      federator_incoming: 50,
      federator_outgoing: 50,
      ingestion_queue: 50,
@@@ -678,7 -677,18 +678,18 @@@ config :pleroma, :rate_limit
  
  config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
  
- config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true
+ config :pleroma, Pleroma.Web.Plugs.RemoteIp,
+   enabled: true,
+   headers: ["x-forwarded-for"],
+   proxies: [],
+   reserved: [
+     "127.0.0.0/8",
+     "::1/128",
+     "fc00::/7",
+     "10.0.0.0/8",
+     "172.16.0.0/12",
+     "192.168.0.0/16"
+   ]
  
  config :pleroma, :static_fe, enabled: false
  
@@@ -792,6 -802,8 +803,8 @@@ config :pleroma, :hackney_pools
      timeout: 300_000
    ]
  
+ config :pleroma, :majic_pool, size: 2
  private_instance? = :if_instance_is_private
  
  config :pleroma, :restrict_unauthenticated,
@@@ -818,11 -830,6 +831,11 @@@ config :floki, :html_parser, Floki.HTML
  
  config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
  
 +config :pleroma, Pleroma.Backup,
 +  purge_after_days: 30,
 +  limit_days: 7,
 +  dir: nil
 +
  # Import environment specific config. This must remain at the bottom
  # of this file so it overrides the configuration defined above.
  import_config "#{Mix.env()}.exs"
diff --combined config/description.exs
index 4942e196d27e03eb5403d7a771b500fae13bab45,2a18989224fd12e9c0f87a16d55ba63ea4b81847..88f2a613344be9fbf84aeed146ef6b252dd418f3
@@@ -2288,12 -2288,6 +2288,12 @@@ config :pleroma, :config_description, 
              description: "Activity expiration queue",
              suggestions: [10]
            },
 +          %{
 +            key: :backup,
 +            type: :integer,
 +            description: "Backup queue",
 +            suggestions: [1]
 +          },
            %{
              key: :attachments_cleanup,
              type: :integer,
    },
    %{
      group: :pleroma,
-     key: Pleroma.Plugs.RemoteIp,
+     key: Pleroma.Web.Plugs.RemoteIp,
      type: :group,
      description: """
-     `Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+     `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
      **If your instance is not behind at least one reverse proxy, you should not enable this plug.**
      """,
      children: [
        %{
          key: :headers,
          type: {:list, :string},
-         description:
-           "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Default: `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`."
+         description: """
+           A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`.
+         """
        },
        %{
          key: :proxies,
          type: {:list, :string},
          description:
-           "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Default: `[]`."
+           "A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128."
        },
        %{
          key: :reserved,
          type: {:list, :string},
-         description:
-           "Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network)."
+         description: """
+           A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `["127.0.0.0/8", "::1/128", "fc00::/7", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`
+         """
        }
      ]
    },
        }
      ]
    },
+   %{
+     group: :pleroma,
+     key: :majic_pool,
+     type: :group,
+     description: "Majic/libmagic configuration",
+     children: [
+       %{
+         key: :size,
+         type: :integer,
+         description: "Number of majic workers to start.",
+         suggestions: [2]
+       }
+     ]
++  },
 +  %{
 +    group: :pleroma,
 +    key: Pleroma.Backup,
 +    type: :group,
 +    description: "Account Backup",
 +    children: [
 +      %{
 +        key: :purge_after_days,
 +        type: :integer,
 +        description: "Remove backup achives after N days",
 +        suggestions: [30]
 +      },
 +      %{
 +        key: :limit_days,
 +        type: :integer,
 +        description: "Limit user to export not more often than once per N days",
 +        suggestions: [7]
 +      }
 +    ]
    }
  ]
index 9271964f1dea08c087fc1aadc855372a875f065f,0b13d7e88197890c8f630dc1cfdefdeb6db1833b..aafc43f3d03590f83257ea0e15297f6aff84cd0f
@@@ -113,7 -113,7 +113,7 @@@ To add configuration to your config fil
      * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
      * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
      * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
-     * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.ActivityExpiration` to be enabled for processing the scheduled delections.
+     * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
      * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
  * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
  * `transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
@@@ -219,12 -219,6 +219,6 @@@ config :pleroma, :mrf_user_allowlist, %
  * `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
  * `enabled`: whether scheduled activities are sent to the job queue to be executed
  
- ## Pleroma.ActivityExpiration
- Enables the worker which processes posts scheduled for deletion. Pinned posts are exempt from expiration.
- * `enabled`: whether expired activities will be sent to the job queue to be deleted
  ## FedSockets
  FedSockets is an experimental feature allowing for Pleroma backends to federate using a persistant websocket connection as opposed to making each federation a seperate http connection. This feature is currently off by default. It is configurable throught he following options.
  
@@@ -416,25 -410,25 +410,25 @@@ This will make Pleroma listen on `127.0
  * ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
  * ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
  
- ### Pleroma.Plugs.RemoteIp
+ ### Pleroma.Web.Plugs.RemoteIp
  
  !!! warning
      If your instance is not behind at least one reverse proxy, you should not enable this plug.
  
- `Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+ `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
  
  Available options:
  
  * `enabled` - Enable/disable the plug. Defaults to `false`.
- * `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `["x-forwarded-for"]`.
- * `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`.
- * `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network).
+ * `headers` - A list of strings naming the HTTP headers to use when deriving the true client IP address. Defaults to `["x-forwarded-for"]`.
+ * `proxies` - A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128.
+ * `reserved` - A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `["127.0.0.0/8", "::1/128", "fc00::/7", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`.
  
  
  ### :rate_limit
  
  !!! note
-    If your instance is behind a reverse proxy ensure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
+    If your instance is behind a reverse proxy ensure [`Pleroma.Web.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
  
  A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
  
@@@ -1083,20 -1077,6 +1077,20 @@@ Control favicons for instances
  
  * `enabled`: Allow/disallow displaying and getting instances favicons
  
 +## Account Backup
 +
 +!!! note
 +    Requires enabled email
 +
 +* `:purge_after_days` an integer, remove backup achives after N days.
 +* `:limit_days` an integer, limit user to export not more often than once per N days.
 +* `:dir` a string with a path to backup temporary directory or `nil` to let Pleroma choose temporary directory in the following order:
 +    1. the directory named by the TMPDIR environment variable
 +    2. the directory named by the TEMP environment variable
 +    3. the directory named by the TMP environment variable
 +    4. C:\TMP on Windows or /tmp on Unix-like operating systems
 +    5. as a last resort, the current working directory
 +
  ## Frontend management
  
  Frontends in Pleroma are swappable - you can specify which one to use here.
index be1e81467ce254f43b0338f5f74a996aaad843a8,38a8634437e15ab13893f52ce99f6d2a3ef15b88..142dd8e0a404300a0fefae72d2301a77acb3d888
@@@ -1,3 -1,7 +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
  
      "@#{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}")
index 8b5310d8098959ae7039e8ca98c4f19fb7c4d56f,bdd3e195d177b80b83bc21c92e792bd5c3b6ac14..a4f0d7d348c277eb80334ecda8d5422d58564647
@@@ -10,7 -10,6 +10,6 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
    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.AdminAPI.ModerationLogView
    alias Pleroma.Web.AdminAPI.Search
    alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Plugs.OAuthScopesPlug
    alias Pleroma.Web.Router
  
 +  require Logger
 +
    @users_page_size 50
  
    plug(
      OAuthScopesPlug,
      %{scopes: ["read:accounts"], admin: true}
 -    when action in [:list_users, :user_show, :right_get, :show_user_credentials]
 +    when action in [:list_users, :user_show, :right_get, :show_user_credentials, :create_backup]
    )
  
    plug(
      json(conn, %{"status_visibility" => counters})
    end
  
 +  def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
 +    with %User{} = user <- User.get_by_nickname(nickname),
 +         {:ok, _} <- Pleroma.Backup.create(user, admin.id) do
 +      ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
 +
 +      json(conn, "")
 +    end
 +  end
 +
    defp page_params(params) do
      {get_page(params["page"]), get_page_size(params["page_size"])}
    end
index ad7e315c72138543c1c98e021eff3e8f6495499f,d2d93998965e8592cd72429abacd96b46a7e88d0..1126536a35b5ec81ec3ce6c931d3412ffa06e373
@@@ -12,31 -12,31 +12,31 @@@ defmodule Pleroma.Web.Router d
  
    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
      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 -80,7 +80,7 @@@
    pipeline :pleroma_html do
      plug(:browser)
      plug(:authenticate)
-     plug(Pleroma.Plugs.EnsureUserKeyPlug)
+     plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
    end
  
    pipeline :well_known do
    scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
      pipe_through(:admin_api)
  
 +    post("/backups", AdminAPIController, :create_backup)
 +
      post("/users/follow", AdminAPIController, :user_follow)
      post("/users/unfollow", AdminAPIController, :user_unfollow)
  
        put("/mascot", MascotController, :update)
  
        post("/scrobble", ScrobbleController, :create)
 +
 +      get("/backups", BackupController, :index)
 +      post("/backups", BackupController, :create)
      end
  
      scope [] do
  
    pipeline :ostatus do
      plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
-     plug(Pleroma.Plugs.StaticFEPlug)
+     plug(Pleroma.Web.Plugs.StaticFEPlug)
    end
  
    pipeline :oembed 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)
index 34d48c2c19d8cddfa2b5d83f38e4c719389c67d2,cba6b43d32a7d1a3e8519d5ebcc74d55e025256b..34d48c2c19d8cddfa2b5d83f38e4c719389c67d2
@@@ -2024,73 -2024,6 +2024,73 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
                 response["status_visibility"]
      end
    end
 +
 +  describe "/api/pleroma/backups" do
 +    test "it creates a backup", %{conn: conn} do
 +      admin = %{id: admin_id, nickname: admin_nickname} = insert(:user, is_admin: true)
 +      token = insert(:oauth_admin_token, user: admin)
 +      user = %{id: user_id, nickname: user_nickname} = insert(:user)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert [backup] = Repo.all(Pleroma.Backup)
 +
 +      ObanHelpers.perform_all()
 +
 +      email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup, admin.id)
 +
 +      assert String.contains?(email.html_body, "Admin @#{admin.nickname} requested a full backup")
 +      assert_email_sent(to: {user.name, user.email}, html_body: email.html_body)
 +
 +      log_message = "@#{admin_nickname} requested account backup for @#{user_nickname}"
 +
 +      assert [
 +               %{
 +                 data: %{
 +                   "action" => "create_backup",
 +                   "actor" => %{
 +                     "id" => ^admin_id,
 +                     "nickname" => ^admin_nickname
 +                   },
 +                   "message" => ^log_message,
 +                   "subject" => %{
 +                     "id" => ^user_id,
 +                     "nickname" => ^user_nickname
 +                   }
 +                 }
 +               }
 +             ] = Pleroma.ModerationLog |> Repo.all()
 +    end
 +
 +    test "it doesn't limit admins", %{conn: conn} do
 +      admin = insert(:user, is_admin: true)
 +      token = insert(:oauth_admin_token, user: admin)
 +      user = insert(:user)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert [_backup] = Repo.all(Pleroma.Backup)
 +
 +      assert "" ==
 +               conn
 +               |> assign(:user, admin)
 +               |> assign(:token, token)
 +               |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
 +               |> json_response(200)
 +
 +      assert Repo.aggregate(Pleroma.Backup, :count) == 2
 +    end
 +  end
  end
  
  # Needed for testing