commit: bae48c98e32af6c43fa668771c6253f299d4bf8a
parent 04b7505c75afbaa1f785b2adaf0313e9b0935fab
Author: lain <lain@soykaf.club>
Date: Wed, 4 Nov 2020 16:18:11 +0100
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into alexgleason/pleroma-block-behavior
Diffstat:
104 files changed, 3544 insertions(+), 1802 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
- Pleroma API: Importing the mutes users from CSV files.
- Experimental websocket-based federation between Pleroma instances.
+- Support pagination of blocks and mutes
+- App metrics: ability to restrict access to specified IP whitelist.
+- Account backup
+- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
+- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`
- `[:activitypub, :blockers_visible]` config to control visibility of blockers.
### Changed
@@ -19,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **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.
+- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring.
- Search: Users are now findable by their urls.
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
@@ -26,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Users with the `discoverable` field set to false will not show up in searches.
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
+- Polls now always return a `voters_count`, even if they are single-choice
<details>
<summary>API Changes</summary>
@@ -33,6 +40,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: Importing the mutes users from CSV files.
- Admin API: Importing emoji from a zip file
- Pleroma API: Pagination for remote/local packs and emoji.
+- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
+- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
+- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
+- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
</details>
@@ -48,8 +59,12 @@ switched to a new configuration mechanism, however it was not officially removed
- Add documented-but-missing chat pagination.
- Allow sending out emails again.
+- Allow sending chat messages to yourself.
+- Fix remote users with a whitespace name.
+- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting.
+- Mastodon API: Current user is now included in conversation if it's the only participant
+- Mastodon API: Fixed last_status.account being not filled with account data
- See your own post when addressing a user from a blocked domain.
-- Allow sending chat messages to yourself
## Unreleased (Patch)
diff --git a/config/config.exs b/config/config.exs
@@ -234,6 +234,7 @@ config :pleroma, :instance,
"text/bbcode"
],
autofollowed_nicknames: [],
+ autofollowing_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
max_report_comment_size: 1000,
@@ -551,6 +552,7 @@ config :pleroma, Oban,
queues: [
activity_expiration: 10,
token_expiration: 5,
+ backup: 1,
federator_incoming: 50,
federator_outgoing: 50,
ingestion_queue: 50,
@@ -636,7 +638,12 @@ config :pleroma, Pleroma.Emails.UserEmail,
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
-config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
+config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
+ enabled: false,
+ auth: false,
+ ip_whitelist: [],
+ path: "/api/pleroma/app_metrics",
+ format: :text
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 25,
@@ -830,6 +837,11 @@ config :floki, :html_parser, Floki.HTMLParser.FastHtml
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator
+config :pleroma, Pleroma.User.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 --git a/config/description.exs b/config/description.exs
@@ -832,6 +832,12 @@ config :pleroma, :config_description, [
"Set to nicknames of (local) users that every new user should automatically follow"
},
%{
+ key: :autofollowing_nicknames,
+ type: {:list, :string},
+ description:
+ "Set to nicknames of (local) users that automatically follows every newly registered user"
+ },
+ %{
key: :attachment_links,
type: :boolean,
description: "Enable to automatically add attachment link text to statuses"
@@ -1751,28 +1757,37 @@ config :pleroma, :config_description, [
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
label: "MRF Keyword",
type: :group,
- description: "Reject or Word-Replace messages with a keyword or regex",
+ description:
+ "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
children: [
%{
key: :reject,
type: {:list, :string},
- description:
- "A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
+ description: """
+ A list of patterns which result in message being rejected.
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :federated_timeline_removal,
type: {:list, :string},
- description:
- "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
+ description: """
+ A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
+
+ Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+ """,
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :replace,
type: {:list, :tuple},
- description:
- "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
- suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
+ description: """
+ **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
+
+ **Replacement**: a string. Leaving the field empty is permitted.
+ """
}
]
},
@@ -2288,6 +2303,12 @@ config :pleroma, :config_description, [
suggestions: [10]
},
%{
+ key: :backup,
+ type: :integer,
+ description: "Backup queue",
+ suggestions: [1]
+ },
+ %{
key: :attachments_cleanup,
type: :integer,
description: "Attachment deletion queue",
@@ -3721,5 +3742,62 @@ config :pleroma, :config_description, [
suggestions: [2]
}
]
+ },
+ %{
+ group: :pleroma,
+ key: Pleroma.User.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]
+ }
+ ]
+ },
+ %{
+ group: :prometheus,
+ key: Pleroma.Web.Endpoint.MetricsExporter,
+ type: :group,
+ description: "Prometheus app metrics endpoint configuration",
+ children: [
+ %{
+ key: :enabled,
+ type: :boolean,
+ description: "[Pleroma extension] Enables app metrics endpoint."
+ },
+ %{
+ key: :ip_whitelist,
+ type: [{:list, :string}, {:list, :charlist}, {:list, :tuple}],
+ description:
+ "[Pleroma extension] If non-empty, restricts access to app metrics endpoint to specified IP addresses."
+ },
+ %{
+ key: :auth,
+ type: [:boolean, :tuple],
+ description: "Enables HTTP Basic Auth for app metrics endpoint.",
+ suggestion: [false, {:basic, "myusername", "mypassword"}]
+ },
+ %{
+ key: :path,
+ type: :string,
+ description: "App metrics endpoint URI path.",
+ suggestions: ["/api/pleroma/app_metrics"]
+ },
+ %{
+ key: :format,
+ type: :atom,
+ description: "App metrics endpoint output format.",
+ suggestions: [:text, :protobuf]
+ }
+ ]
}
]
diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
@@ -20,12 +20,14 @@ Configuration options:
- `external`: only external users
- `active`: only active users
- `need_approval`: only unapproved users
+ - `unconfirmed`: only unconfirmed users
- `deactivated`: only deactivated users
- `is_admin`: users with admin role
- `is_moderator`: users with moderator role
- *optional* `page`: **integer** page number
- *optional* `page_size`: **integer** number of users per page (default is `50`)
- *optional* `tags`: **[string]** tags list
+ - *optional* `actor_types`: **[string]** actor type list (`Person`, `Service`, `Application`)
- *optional* `name`: **string** user display name
- *optional* `email`: **string** user email
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
diff --git a/docs/API/chats.md b/docs/API/chats.md
@@ -116,6 +116,10 @@ The modified chat message
This will return a list of chats that you have been involved in, sorted by their
last update (so new chats will be at the top).
+Parameters:
+
+- with_muted: Include chats from muted users (boolean).
+
Returned data:
```json
@@ -173,11 +177,14 @@ Returned data:
"created_at": "2020-04-21T15:06:45.000Z",
"emojis": [],
"id": "12",
- "unread": false
+ "unread": false,
+ "idempotency_key": "75442486-0874-440c-9db1-a7006c25a31f"
}
]
```
+- idempotency_key: The copy of the `idempotency-key` HTTP request header that can be used for optimistic message sending. Included only during the first few minutes after the message creation.
+
### Posting a chat message
Posting a chat message for given Chat id works like this:
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
@@ -9,9 +9,13 @@ Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mas
## Timelines
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
+
Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
+
Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
+Adding the parameter `instance=lain.com` to the public timeline will show only statuses originating from `lain.com` (or any remote instance).
+
## Statuses
- `visibility`: has an additional possible value `list`
@@ -249,6 +253,8 @@ Has these additional fields under the `pleroma` object:
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
+For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
+
## Not implemented
Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority.
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
@@ -615,3 +615,41 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
{"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}
]
```
+
+## `POST /api/v1/pleroma/backups`
+### Create a user backup archive
+
+* Method: `POST`
+* Authentication: required
+* Params: none
+* Response: JSON
+* Example response:
+
+```json
+[{
+ "content_type": "application/zip",
+ "file_size": 0,
+ "inserted_at": "2020-09-10T16:18:03.000Z",
+ "processed": false,
+ "url": "https://example.com/media/backups/archive-foobar-20200910T161803-QUhx6VYDRQ2wfV0SdA2Pfj_2CLM_ATUlw-D5l5TJf4Q.zip"
+}]
+```
+
+## `GET /api/v1/pleroma/backups`
+### Lists user backups
+
+* Method: `GET`
+* Authentication: not required
+* Params: none
+* Response: JSON
+* Example response:
+
+```json
+[{
+ "content_type": "application/zip",
+ "file_size": 55457,
+ "inserted_at": "2020-09-10T16:18:03.000Z",
+ "processed": true,
+ "url": "https://example.com/media/backups/archive-foobar-20200910T161803-QUhx6VYDRQ2wfV0SdA2Pfj_2CLM_ATUlw-D5l5TJf4Q.zip"
+}]
+```
diff --git a/docs/API/prometheus.md b/docs/API/prometheus.md
@@ -2,15 +2,37 @@
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
+Config example:
+
+```
+config :prometheus, Pleroma.Web.Endpoint.MetricsExporter,
+ enabled: true,
+ auth: {:basic, "myusername", "mypassword"},
+ ip_whitelist: ["127.0.0.1"],
+ path: "/api/pleroma/app_metrics",
+ format: :text
+```
+
+* `enabled` (Pleroma extension) enables the endpoint
+* `ip_whitelist` (Pleroma extension) could be used to restrict access only to specified IPs
+* `auth` sets the authentication (`false` for no auth; configurable to HTTP Basic Auth, see [prometheus-plugs](https://github.com/deadtrickster/prometheus-plugs#exporting) documentation)
+* `format` sets the output format (`:text` or `:protobuf`)
+* `path` sets the path to app metrics page
+
+
## `/api/pleroma/app_metrics`
+
### Exports Prometheus application metrics
+
* Method: `GET`
-* Authentication: not required
+* Authentication: not required by default (see configuration options above)
* Params: none
-* Response: JSON
+* Response: text
## Grafana
+
### Config example
+
The following is a config example to use with [Grafana](https://grafana.com)
```
diff --git a/docs/administration/CLI_tasks/frontend.md b/docs/administration/CLI_tasks/frontend.md
@@ -1,12 +1,23 @@
# Managing frontends
-`mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]`
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.frontend install <frontend> [--ref <ref>] [--file <file>] [--build-url <build-url>] [--path <path>] [--build-dir <build-dir>]
+ ```
Frontend can be installed either from local zip file, or automatically downloaded from the web.
-You can give all the options directly on the command like, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
+You can give all the options directly on the command line, but missing information will be filled out by looking at the data configured under `frontends.available` in the config files.
+
+Currently, known `<frontend>` values are:
-Currently known `<frontend>` values are:
- [admin-fe](https://git.pleroma.social/pleroma/admin-fe)
- [kenoma](http://git.pleroma.social/lambadalambda/kenoma)
- [pleroma-fe](http://git.pleroma.social/pleroma/pleroma-fe)
@@ -19,51 +30,67 @@ You can still install frontends that are not configured, see below.
For a frontend configured under the `available` key, it's enough to install it by name.
-```sh tab="OTP"
-./bin/pleroma_ctl frontend install pleroma
-```
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl frontend install pleroma
+ ```
+
+=== "From Source"
-```sh tab="From Source"
-mix pleroma.frontend install pleroma
-```
+ ```sh
+ mix pleroma.frontend install pleroma
+ ```
-This will download the latest build for the the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
+This will download the latest build for the pre-configured `ref` and install it. It can then be configured as the one of the served frontends in the config file (see `primary` or `admin`).
-You can override any of the details. To install a pleroma build from a different url, you could do this:
+You can override any of the details. To install a pleroma build from a different URL, you could do this:
-```sh tab="OPT"
-./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
-```
+=== "OTP"
-```sh tab="From Source"
-mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
-```
+ ```sh
+ ./bin/pleroma_ctl frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
+ ```
+
+=== "From Source"
+
+ ```sh
+ mix pleroma.frontend install pleroma --ref 2hu_edition --build-url https://example.org/raymoo.zip
+ ```
Similarly, you can also install from a local zip file.
-```sh tab="OTP"
-./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
-```
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
+ ```
-```sh tab="From Source"
-mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
-```
+=== "From Source"
-The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`
+ ```sh
+ mix pleroma.frontend install pleroma --ref mybuild --file ~/Downloads/doomfe.zip
+ ```
-Careful: This folder will be completely replaced on installation
+The resulting frontend will always be installed into a folder of this template: `${instance_static}/frontends/${name}/${ref}`.
+
+Careful: This folder will be completely replaced on installation.
## Example installation for an unknown frontend
-The installation process is the same, but you will have to give all the needed options on the commond line. For example:
+The installation process is the same, but you will have to give all the needed options on the command line. For example:
+
+=== "OTP"
+
+ ```sh
+ ./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
+ ```
-```sh tab="OTP"
-./bin/pleroma_ctl frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
-```
+=== "From Source"
-```sh tab="From Source"
-mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
-```
+ ```sh
+ mix pleroma.frontend install gensokyo --ref master --build-url https://gensokyo.2hu/builds/marisa.zip
+ ```
-If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`
+If you don't have a zip file but just want to install a frontend from a local path, you can simply copy the files over a folder of this template: `${instance_static}/frontends/${name}/${ref}`.
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
@@ -45,6 +45,7 @@ To add configuration to your config file, you can copy it from the base config.
older software for theses nicknames.
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
+* `autofollowing_nicknames`: Set to nicknames of (local) users that automatically follows every newly registered user.
* `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
@@ -1077,6 +1078,20 @@ Control favicons for instances.
* `enabled`: Allow/disallow displaying and getting instances favicons
+## Pleroma.User.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.
diff --git 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
@@ -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
@@ -168,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
@@ -178,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]))
diff --git 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/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
@@ -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/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
@@ -594,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
@@ -617,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/helpers/inet_helper.ex 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/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
@@ -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/moderation_log.ex b/lib/pleroma/moderation_log.ex
@@ -655,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/user.ex b/lib/pleroma/user.ex
@@ -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)
@@ -426,7 +425,6 @@ defmodule Pleroma.User do
params,
[
:bio,
- :name,
:emoji,
:ap_id,
:inbox,
@@ -455,7 +453,9 @@ defmodule Pleroma.User do
:accepts_chat_messages
]
)
- |> validate_required([:name, :ap_id])
+ |> cast(params, [:name], empty_values: [])
+ |> validate_required([:ap_id])
+ |> validate_required([:name], trim: false)
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
@@ -765,6 +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),
@@ -1293,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)
diff --git a/lib/pleroma/user/backup.ex 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
@@ -43,6 +43,7 @@ defmodule Pleroma.User.Query do
active: boolean(),
deactivated: boolean(),
need_approval: boolean(),
+ unconfirmed: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
@@ -55,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()
@@ -114,6 +116,10 @@ defmodule Pleroma.User.Query do
where(query, [u], u.is_admin == ^bool)
end
+ 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
@@ -156,6 +162,10 @@ defmodule Pleroma.User.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)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -976,16 +976,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
@@ -1418,6 +1413,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/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -187,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
@@ -312,6 +312,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects 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/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
@@ -5,7 +5,8 @@
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
@@ -13,12 +14,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
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
@@ -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/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -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/user_controller.ex 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
@@ -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/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -335,6 +335,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())
}
@@ -348,6 +349,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/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())
},
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex 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_instances_operation.ex 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
@@ -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/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/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/endpoint.ex b/lib/pleroma/web/endpoint.ex
@@ -7,6 +7,8 @@ defmodule Pleroma.Web.Endpoint do
require Pleroma.Constants
+ alias Pleroma.Config
+
socket("/socket", Pleroma.Web.UserSocket)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
@@ -88,19 +90,19 @@ defmodule Pleroma.Web.Endpoint do
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,
@@ -108,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,
@@ -118,7 +120,7 @@ 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
@@ -138,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/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
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
@@ -22,12 +23,7 @@ defmodule Pleroma.Web.Feed.UserController do
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
- with %{halted: false} = conn <-
- Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- ) do
- ActivityPubController.call(conn, :user)
- end
+ ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
@@ -36,25 +32,18 @@ defmodule Pleroma.Web.Feed.UserController do
end
end
- def feed(conn, params) do
- unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
- render_feed(conn, params)
- else
- errors(conn, {:error, :not_found})
- end
- end
-
- def render_feed(conn, %{"nickname" => nickname} = params) do
+ def feed(conn, %{"nickname" => nickname} = params) do
format = get_format(conn)
format =
- if format in ["rss", "atom"] do
+ if format in ["atom", "rss"] do
format
else
"atom"
end
- with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
activities =
%{
type: ["Create"],
@@ -69,7 +58,7 @@ defmodule Pleroma.Web.Feed.UserController do
|> render("user.#{format}",
user: user,
activities: activities,
- feed_config: Pleroma.Config.get([:feed])
+ feed_config: Config.get([:feed])
)
end
end
@@ -81,6 +70,8 @@ defmodule Pleroma.Web.Feed.UserController do
def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+ def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found})
+
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -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/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -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
@@ -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
@@ -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
@@ -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/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -16,10 +16,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.Router
- plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- )
-
plug(
RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@@ -37,14 +33,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :object)
end
- def object(%{assigns: %{format: format}} = conn, _params) do
+ def object(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -59,13 +53,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :activity)
end
- def activity(%{assigns: %{format: format}} = conn, _params) do
+ def activity(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -119,6 +111,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%Object{} = object <- Object.normalize(activity),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex 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
@@ -15,7 +15,6 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
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
@@ -80,7 +79,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
%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
@@ -120,9 +120,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
) do
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
- conn
- |> put_view(ChatView)
- |> render("show.json", chat: chat)
+ render(conn, "show.json", chat: chat)
end
end
@@ -140,33 +138,37 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
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, %{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, %{id: id}) do
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
- conn
- |> put_view(ChatView)
- |> render("show.json", chat: chat)
+ 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/instances_controller.ex 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/views/backup_view.ex 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
@@ -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/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
@@ -34,22 +34,26 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
end
def call(conn, opts) do
- frontend_type = Map.get(opts, :frontend_type, :primary)
- path = file_path("", frontend_type)
-
- if path do
- conn
- |> call_static(opts, path)
+ with false <- invalid_path?(conn.path_info),
+ frontend_type <- Map.get(opts, :frontend_type, :primary),
+ path when not is_nil(path) <- file_path("", frontend_type) do
+ call_static(conn, opts, path)
else
- conn
+ _ ->
+ conn
end
end
- defp call_static(conn, opts, from) do
- opts =
- opts
- |> Map.put(:from, from)
+ defp invalid_path?(list) do
+ invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
+ end
+ defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
+ defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
+ defp invalid_path?([], _match), do: false
+
+ defp call_static(conn, opts, from) do
+ opts = Map.put(opts, :from, from)
Plug.Static.call(conn, opts)
end
end
diff --git 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)
@@ -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,6 +243,8 @@ 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
@@ -353,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
@@ -373,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
@@ -566,30 +592,43 @@ defmodule Pleroma.Web.Router do
)
end
- pipeline :ostatus do
- plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
- plug(Pleroma.Web.Plugs.StaticFEPlug)
- end
-
- pipeline :oembed do
- plug(:accepts, ["json", "xml"])
- end
-
scope "/", Pleroma.Web do
- pipe_through([:ostatus, :http_signature])
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
- get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
# Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
+ end
- get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+
+ # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+ end
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+
+ get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ end
+
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_html)
+ get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
+ end
+
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_xml_rss_atom)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end
diff --git 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.Web.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
- )
-
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
- defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
- do: name
-
- defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
- do: summary
-
- defp get_title(_), do: nil
-
- defp not_found(conn, message) do
- conn
- |> put_status(404)
- |> render("error.html", %{message: message, meta: ""})
- end
-
- defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
-
- %{
- likes: data["like_count"] || 0,
- replies: data["repliesCount"] || 0,
- announces: data["announcement_count"] || 0
- }
- end
-
- defp represent(%Activity{} = activity), do: represent(activity, false)
-
- defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
- {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
-
- link =
- case user.local do
- true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
- _ -> data["url"] || data["external_url"] || data["id"]
- end
-
- content =
- if data["content"] do
- data["content"]
- |> Pleroma.HTML.filter_tags()
- |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
- else
- nil
- end
-
- %{
- user: User.sanitize_html(user),
- title: get_title(activity.object),
- content: content,
- attachment: data["attachment"],
- link: link,
- published: data["published"],
- sensitive: data["sensitive"],
- selected: selected,
- counts: get_counts(activity),
- id: activity.id
- }
- end
-
+ @doc "Renders requested local public activity or public activities of requested user"
def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
@@ -107,34 +47,35 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
- case User.get_cached_by_nickname_or_id(username_or_id) do
- %User{} = user ->
- meta = Metadata.build_tags(%{user: user})
-
- params =
- params
- |> Map.take(@page_keys)
- |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
-
- timeline =
- user
- |> ActivityPub.fetch_user_activities(nil, params)
- |> Enum.map(&represent/1)
-
- prev_page_id =
- (params["min_id"] || params["max_id"]) &&
- List.first(timeline) && List.first(timeline).id
-
- next_page_id = List.last(timeline) && List.last(timeline).id
-
- render(conn, "profile.html", %{
- user: User.sanitize_html(user),
- timeline: timeline,
- prev_page_id: prev_page_id,
- next_page_id: next_page_id,
- meta: meta
- })
+ with {_, %User{local: true} = user} <-
+ {:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
+ meta = Metadata.build_tags(%{user: user})
+
+ params =
+ params
+ |> Map.take(@page_keys)
+ |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+ timeline =
+ user
+ |> ActivityPub.fetch_user_activities(_reading_user = nil, params)
+ |> Enum.map(&represent/1)
+
+ prev_page_id =
+ (params["min_id"] || params["max_id"]) &&
+ List.first(timeline) && List.first(timeline).id
+
+ next_page_id = List.last(timeline) && List.last(timeline).id
+
+ render(conn, "profile.html", %{
+ user: User.sanitize_html(user),
+ timeline: timeline,
+ prev_page_id: prev_page_id,
+ next_page_id: next_page_id,
+ meta: meta
+ })
+ else
_ ->
not_found(conn, "User not found.")
end
@@ -166,6 +107,64 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
+ defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
+ do: name
+
+ defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
+ do: summary
+
+ defp get_title(_), do: nil
+
+ defp not_found(conn, message) do
+ conn
+ |> put_status(404)
+ |> render("error.html", %{message: message, meta: ""})
+ end
+
+ defp get_counts(%Activity{} = activity) do
+ %Object{data: data} = Object.normalize(activity)
+
+ %{
+ likes: data["like_count"] || 0,
+ replies: data["repliesCount"] || 0,
+ announces: data["announcement_count"] || 0
+ }
+ end
+
+ defp represent(%Activity{} = activity), do: represent(activity, false)
+
+ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
+
+ link =
+ case user.local do
+ true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+ _ -> data["url"] || data["external_url"] || data["id"]
+ end
+
+ content =
+ if data["content"] do
+ data["content"]
+ |> Pleroma.HTML.filter_tags()
+ |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
+ else
+ nil
+ end
+
+ %{
+ user: User.sanitize_html(user),
+ title: get_title(activity.object),
+ content: content,
+ attachment: data["attachment"],
+ link: link,
+ published: data["published"],
+ sensitive: data["sensitive"],
+ selected: selected,
+ counts: get_counts(activity),
+ id: activity.id
+ }
+ end
+
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
@@ -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/workers/backup_worker.ex 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/mix.exs b/mix.exs
@@ -134,7 +134,7 @@ defmodule Pleroma.Mixfile do
{:cachex, "~> 3.2"},
{:poison, "~> 3.0", override: true},
{:tesla,
- git: "https://github.com/teamon/tesla/",
+ git: "https://github.com/teamon/tesla.git",
ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30",
override: true},
{:castore, "~> 0.1"},
@@ -196,7 +196,7 @@ defmodule Pleroma.Mixfile do
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:restarter, path: "./restarter"},
{:majic,
- git: "https://git.pleroma.social/pleroma/elixir-libraries/majic", branch: "develop"},
+ git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", branch: "develop"},
{:open_api_spex,
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
diff --git a/mix.lock b/mix.lock
@@ -66,7 +66,7 @@
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"},
- "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [branch: "develop"]},
+ "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [branch: "develop"]},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
@@ -115,7 +115,7 @@
"swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
- "tesla": {:git, "https://github.com/teamon/tesla/", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]},
+ "tesla": {:git, "https://github.com/teamon/tesla.git", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]},
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"},
diff --git a/priv/repo/migrations/20200831114918_remove_unread_conversation_count_from_user.exs b/priv/repo/migrations/20200831114918_remove_unread_conversation_count_from_user.exs
@@ -0,0 +1,38 @@
+defmodule Pleroma.Repo.Migrations.RemoveUnreadConversationCountFromUser do
+ use Ecto.Migration
+ import Ecto.Query
+ alias Pleroma.Repo
+
+ def up do
+ alter table(:users) do
+ remove_if_exists(:unread_conversation_count, :integer)
+ end
+ end
+
+ def down do
+ alter table(:users) do
+ add_if_not_exists(:unread_conversation_count, :integer, default: 0)
+ end
+
+ flush()
+ recalc_unread_conversation_count()
+ end
+
+ defp recalc_unread_conversation_count do
+ participations_subquery =
+ from(
+ p in "conversation_participations",
+ where: p.read == false,
+ group_by: p.user_id,
+ select: %{user_id: p.user_id, unread_conversation_count: count(p.id)}
+ )
+
+ from(
+ u in "users",
+ join: p in subquery(participations_subquery),
+ on: p.user_id == u.id,
+ update: [set: [unread_conversation_count: p.unread_conversation_count]]
+ )
+ |> Repo.update_all([])
+ end
+end
diff --git a/priv/repo/migrations/20200831115854_add_unread_index_to_conversation_participation.exs b/priv/repo/migrations/20200831115854_add_unread_index_to_conversation_participation.exs
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.AddUnreadIndexToConversationParticipation do
+ use Ecto.Migration
+
+ def change do
+ create(
+ index(:conversation_participations, [:user_id],
+ where: "read = false",
+ name: "unread_conversation_participation_count_index"
+ )
+ )
+ end
+end
diff --git a/priv/repo/migrations/20200831192323_create_backups.exs b/priv/repo/migrations/20200831192323_create_backups.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.CreateBackups do
+ use Ecto.Migration
+
+ def change do
+ create_if_not_exists table(:backups) do
+ add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
+ add(:file_name, :string, null: false)
+ add(:content_type, :string, null: false)
+ add(:processed, :boolean, null: false, default: false)
+ add(:file_size, :bigint)
+
+ timestamps()
+ end
+
+ create_if_not_exists(index(:backups, [:user_id]))
+ end
+end
diff --git a/priv/static/favicon.png b/priv/static/favicon.png
Binary files differ.
diff --git a/test/fixtures/mewmew_no_name.json b/test/fixtures/mewmew_no_name.json
@@ -0,0 +1,46 @@
+{
+ "@context" : [
+ "https://www.w3.org/ns/activitystreams",
+ "https://princess.cat/schemas/litepub-0.1.jsonld",
+ {
+ "@language" : "und"
+ }
+ ],
+ "attachment" : [],
+ "capabilities" : {
+ "acceptsChatMessages" : true
+ },
+ "discoverable" : false,
+ "endpoints" : {
+ "oauthAuthorizationEndpoint" : "https://princess.cat/oauth/authorize",
+ "oauthRegistrationEndpoint" : "https://princess.cat/api/v1/apps",
+ "oauthTokenEndpoint" : "https://princess.cat/oauth/token",
+ "sharedInbox" : "https://princess.cat/inbox",
+ "uploadMedia" : "https://princess.cat/api/ap/upload_media"
+ },
+ "followers" : "https://princess.cat/users/mewmew/followers",
+ "following" : "https://princess.cat/users/mewmew/following",
+ "icon" : {
+ "type" : "Image",
+ "url" : "https://princess.cat/media/12794fb50e86911e65be97f69196814049dcb398a2f8b58b99bb6591576e648c.png?name=blobcatpresentpink.png"
+ },
+ "id" : "https://princess.cat/users/mewmew",
+ "image" : {
+ "type" : "Image",
+ "url" : "https://princess.cat/media/05d8bf3953ab6028fc920494ffc643fbee9dcef40d7bdd06f107e19acbfbd7f9.png"
+ },
+ "inbox" : "https://princess.cat/users/mewmew/inbox",
+ "manuallyApprovesFollowers" : true,
+ "name" : " ",
+ "outbox" : "https://princess.cat/users/mewmew/outbox",
+ "preferredUsername" : "mewmew",
+ "publicKey" : {
+ "id" : "https://princess.cat/users/mewmew#main-key",
+ "owner" : "https://princess.cat/users/mewmew",
+ "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAru7VpygVef4zrFwnj0Mh\nrbO/2z2EdKN3rERtNrT8zWsLXNLQ50lfpRPnGDrd+xq7Rva4EIu0d5KJJ9n4vtY0\nuxK3On9vA2oyjLlR9O0lI3XTrHJborG3P7IPXrmNUMFpHiFHNqHp5tugUrs1gUFq\n7tmOmM92IP4Wjk8qNHFcsfnUbaPTX7sNIhteQKdi5HrTb/6lrEIe4G/FlMKRqxo3\nRNHuv6SNFQuiUKvFzjzazvjkjvBSm+aFROgdHa2tKl88StpLr7xmuY8qNFCRT6W0\nLacRp6c8ah5f03Kd+xCBVhCKvKaF1K0ERnQTBiitUh85md+Mtx/CoDoLnmpnngR3\nvQIDAQAB\n-----END PUBLIC KEY-----\n\n"
+ },
+ "summary" : "please reply to my posts as direct messages if you have many followers",
+ "tag" : [],
+ "type" : "Person",
+ "url" : "https://princess.cat/users/mewmew"
+}
diff --git a/test/pleroma/activity/ir/topics_test.exs b/test/pleroma/activity/ir/topics_test.exs
@@ -97,6 +97,20 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
refute Enum.member?(topics, "hashtag:2")
end
+
+ test "non-local action produces public:remote topic", %{activity: activity} do
+ activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
+ topics = Topics.get_activity_topics(activity)
+
+ assert Enum.member?(topics, "public:remote:lain.com")
+ end
+
+ test "local action doesn't produce public:remote topic", %{activity: activity} do
+ activity = %{activity | local: true, actor: "https://lain.com/users/lain"}
+ topics = Topics.get_activity_topics(activity)
+
+ refute Enum.member?(topics, "public:remote:lain.com")
+ end
end
describe "public visibility create events with attachments" do
@@ -128,6 +142,13 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
refute Enum.member?(topics, "public:local:media")
end
+
+ test "non-local action produces public:remote:media topic", %{activity: activity} do
+ activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
+ topics = Topics.get_activity_topics(activity)
+
+ assert Enum.member?(topics, "public:remote:media:lain.com")
+ end
end
describe "non-public visibility" do
diff --git a/test/pleroma/conversation/participation_test.exs b/test/pleroma/conversation/participation_test.exs
@@ -37,9 +37,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
[%{read: true}] = Participation.for_user(user)
[%{read: false} = participation] = Participation.for_user(other_user)
-
- assert User.get_cached_by_id(user.id).unread_conversation_count == 0
- assert User.get_cached_by_id(other_user.id).unread_conversation_count == 1
+ assert Participation.unread_count(user) == 0
+ assert Participation.unread_count(other_user) == 1
{:ok, _} =
CommonAPI.post(other_user, %{
@@ -54,8 +53,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
[%{read: false}] = Participation.for_user(user)
[%{read: true}] = Participation.for_user(other_user)
- assert User.get_cached_by_id(user.id).unread_conversation_count == 1
- assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
+ assert Participation.unread_count(user) == 1
+ assert Participation.unread_count(other_user) == 0
end
test "for a new conversation, it sets the recipents of the participation" do
@@ -264,7 +263,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
+ assert Participation.unread_count(blocker) == 4
{:ok, _user_relationship} = User.block(blocker, blocked)
@@ -272,15 +271,15 @@ defmodule Pleroma.Conversation.ParticipationTest do
assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 1
+ assert Participation.unread_count(blocker) == 1
# The conversation is not marked as read for the blocked user
assert [_, _, %{read: false}] = Participation.for_user(blocked)
- assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+ assert Participation.unread_count(blocker) == 1
# The conversation is not marked as read for the third user
assert [%{read: false}, _, _] = Participation.for_user(third_user)
- assert User.get_cached_by_id(third_user.id).unread_conversation_count == 1
+ assert Participation.unread_count(third_user) == 1
end
test "the new conversation with the blocked user is not marked as unread " do
@@ -298,7 +297,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
})
assert [%{read: true}] = Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+ assert Participation.unread_count(blocker) == 0
# When the blocked user is a recipient
{:ok, _direct2} =
@@ -308,10 +307,10 @@ defmodule Pleroma.Conversation.ParticipationTest do
})
assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+ assert Participation.unread_count(blocker) == 0
assert [%{read: false}, _] = Participation.for_user(blocked)
- assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+ assert Participation.unread_count(blocked) == 1
end
test "the conversation with the blocked user is not marked as unread on a reply" do
@@ -327,8 +326,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
{:ok, _user_relationship} = User.block(blocker, blocked)
assert [%{read: true}] = Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+ assert Participation.unread_count(blocker) == 0
assert [blocked_participation] = Participation.for_user(blocked)
# When it's a reply from the blocked user
@@ -340,8 +339,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
})
assert [%{read: true}] = Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+ assert Participation.unread_count(blocker) == 0
assert [third_user_participation] = Participation.for_user(third_user)
# When it's a reply from the third user
@@ -353,11 +352,12 @@ defmodule Pleroma.Conversation.ParticipationTest do
})
assert [%{read: true}] = Participation.for_user(blocker)
- assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+ assert Participation.unread_count(blocker) == 0
# Marked as unread for the blocked user
assert [%{read: false}] = Participation.for_user(blocked)
- assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+
+ assert Participation.unread_count(blocked) == 1
end
end
end
diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs
@@ -49,6 +49,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
test "allows public streams without authentication" do
assert {:ok, _} = start_socket("?stream=public")
assert {:ok, _} = start_socket("?stream=public:local")
+ assert {:ok, _} = start_socket("?stream=public:remote&instance=lain.com")
assert {:ok, _} = start_socket("?stream=hashtag&tag=lain")
end
diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs
@@ -0,0 +1,244 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.BackupTest do
+ use Oban.Testing, repo: Pleroma.Repo
+ use Pleroma.DataCase
+
+ import Mock
+ import Pleroma.Factory
+ import Swoosh.TestAssertions
+
+ alias Pleroma.Bookmark
+ alias Pleroma.Tests.ObanHelpers
+ alias Pleroma.User.Backup
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Workers.BackupWorker
+
+ setup do
+ clear_config([Pleroma.Upload, :uploader])
+ clear_config([Backup, :limit_days])
+ clear_config([Pleroma.Emails.Mailer, :enabled], true)
+ end
+
+ test "it requries enabled email" do
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+ user = insert(:user)
+ assert {:error, "Backups require enabled email"} == Backup.create(user)
+ end
+
+ test "it requries user's email" do
+ user = insert(:user, %{email: nil})
+ assert {:error, "Email is required"} == Backup.create(user)
+ end
+
+ test "it creates a backup record and an Oban job" do
+ %{id: user_id} = user = insert(:user)
+ assert {:ok, %Oban.Job{args: args}} = Backup.create(user)
+ assert_enqueued(worker: BackupWorker, args: args)
+
+ backup = Backup.get(args["backup_id"])
+ assert %Backup{user_id: ^user_id, processed: false, file_size: 0} = backup
+ end
+
+ test "it return an error if the export limit is over" do
+ %{id: user_id} = user = insert(:user)
+ limit_days = Pleroma.Config.get([Backup, :limit_days])
+ assert {:ok, %Oban.Job{args: args}} = Backup.create(user)
+ backup = Backup.get(args["backup_id"])
+ assert %Backup{user_id: ^user_id, processed: false, file_size: 0} = backup
+
+ assert Backup.create(user) == {:error, "Last export was less than #{limit_days} days ago"}
+ end
+
+ test "it process a backup record" do
+ Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
+ %{id: user_id} = user = insert(:user)
+
+ assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user)
+ assert {:ok, backup} = perform_job(BackupWorker, args)
+ assert backup.file_size > 0
+ assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id} = backup
+
+ delete_job_args = %{"op" => "delete", "backup_id" => backup_id}
+
+ assert_enqueued(worker: BackupWorker, args: delete_job_args)
+ assert {:ok, backup} = perform_job(BackupWorker, delete_job_args)
+ refute Backup.get(backup_id)
+
+ email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup)
+
+ assert_email_sent(
+ to: {user.name, user.email},
+ html_body: email.html_body
+ )
+ end
+
+ test "it removes outdated backups after creating a fresh one" do
+ Pleroma.Config.put([Backup, :limit_days], -1)
+ Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
+ user = insert(:user)
+
+ assert {:ok, job1} = Backup.create(user)
+
+ assert {:ok, %Backup{}} = ObanHelpers.perform(job1)
+ assert {:ok, job2} = Backup.create(user)
+ assert Pleroma.Repo.aggregate(Backup, :count) == 2
+ assert {:ok, backup2} = ObanHelpers.perform(job2)
+
+ ObanHelpers.perform_all()
+
+ assert [^backup2] = Pleroma.Repo.all(Backup)
+ end
+
+ test "it creates a zip archive with user data" do
+ user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"})
+
+ {:ok, %{object: %{data: %{"id" => id1}}} = status1} =
+ CommonAPI.post(user, %{status: "status1"})
+
+ {:ok, %{object: %{data: %{"id" => id2}}} = status2} =
+ CommonAPI.post(user, %{status: "status2"})
+
+ {:ok, %{object: %{data: %{"id" => id3}}} = status3} =
+ CommonAPI.post(user, %{status: "status3"})
+
+ CommonAPI.favorite(user, status1.id)
+ CommonAPI.favorite(user, status2.id)
+
+ Bookmark.create(user.id, status2.id)
+ Bookmark.create(user.id, status3.id)
+
+ assert {:ok, backup} = user |> Backup.new() |> Repo.insert()
+ assert {:ok, path} = Backup.export(backup)
+ assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory])
+ assert {:ok, {'actor.json', json}} = :zip.zip_get('actor.json', zipfile)
+
+ assert %{
+ "@context" => [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ %{"@language" => "und"}
+ ],
+ "bookmarks" => "bookmarks.json",
+ "followers" => "http://cofe.io/users/cofe/followers",
+ "following" => "http://cofe.io/users/cofe/following",
+ "id" => "http://cofe.io/users/cofe",
+ "inbox" => "http://cofe.io/users/cofe/inbox",
+ "likes" => "likes.json",
+ "name" => "Cofe",
+ "outbox" => "http://cofe.io/users/cofe/outbox",
+ "preferredUsername" => "cofe",
+ "publicKey" => %{
+ "id" => "http://cofe.io/users/cofe#main-key",
+ "owner" => "http://cofe.io/users/cofe"
+ },
+ "type" => "Person",
+ "url" => "http://cofe.io/users/cofe"
+ } = Jason.decode!(json)
+
+ assert {:ok, {'outbox.json', json}} = :zip.zip_get('outbox.json', zipfile)
+
+ assert %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => "outbox.json",
+ "orderedItems" => [
+ %{
+ "object" => %{
+ "actor" => "http://cofe.io/users/cofe",
+ "content" => "status1",
+ "type" => "Note"
+ },
+ "type" => "Create"
+ },
+ %{
+ "object" => %{
+ "actor" => "http://cofe.io/users/cofe",
+ "content" => "status2"
+ }
+ },
+ %{
+ "actor" => "http://cofe.io/users/cofe",
+ "object" => %{
+ "content" => "status3"
+ }
+ }
+ ],
+ "totalItems" => 3,
+ "type" => "OrderedCollection"
+ } = Jason.decode!(json)
+
+ assert {:ok, {'likes.json', json}} = :zip.zip_get('likes.json', zipfile)
+
+ assert %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => "likes.json",
+ "orderedItems" => [^id1, ^id2],
+ "totalItems" => 2,
+ "type" => "OrderedCollection"
+ } = Jason.decode!(json)
+
+ assert {:ok, {'bookmarks.json', json}} = :zip.zip_get('bookmarks.json', zipfile)
+
+ assert %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => "bookmarks.json",
+ "orderedItems" => [^id2, ^id3],
+ "totalItems" => 2,
+ "type" => "OrderedCollection"
+ } = Jason.decode!(json)
+
+ :zip.zip_close(zipfile)
+ File.rm!(path)
+ end
+
+ describe "it uploads and deletes a backup archive" do
+ setup do
+ clear_config(Pleroma.Uploaders.S3,
+ bucket: "test_bucket",
+ public_endpoint: "https://s3.amazonaws.com"
+ )
+
+ clear_config([Pleroma.Upload, :uploader])
+
+ user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"})
+
+ {:ok, status1} = CommonAPI.post(user, %{status: "status1"})
+ {:ok, status2} = CommonAPI.post(user, %{status: "status2"})
+ {:ok, status3} = CommonAPI.post(user, %{status: "status3"})
+ CommonAPI.favorite(user, status1.id)
+ CommonAPI.favorite(user, status2.id)
+ Bookmark.create(user.id, status2.id)
+ Bookmark.create(user.id, status3.id)
+
+ assert {:ok, backup} = user |> Backup.new() |> Repo.insert()
+ assert {:ok, path} = Backup.export(backup)
+
+ [path: path, backup: backup]
+ end
+
+ test "S3", %{path: path, backup: backup} do
+ Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.S3)
+
+ with_mock ExAws,
+ request: fn
+ %{http_method: :put} -> {:ok, :ok}
+ %{http_method: :delete} -> {:ok, %{status_code: 204}}
+ end do
+ assert {:ok, %Pleroma.Upload{}} = Backup.upload(backup, path)
+ assert {:ok, _backup} = Backup.delete(backup)
+ end
+
+ with_mock ExAws, request: fn %{http_method: :delete} -> {:ok, %{status_code: 204}} end do
+ end
+ end
+
+ test "Local", %{path: path, backup: backup} do
+ Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
+
+ assert {:ok, %Pleroma.Upload{}} = Backup.upload(backup, path)
+ assert {:ok, _backup} = Backup.delete(backup)
+ end
+ end
+end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
@@ -388,6 +388,7 @@ defmodule Pleroma.UserTest do
}
setup do: clear_config([:instance, :autofollowed_nicknames])
+ setup do: clear_config([:instance, :autofollowing_nicknames])
setup do: clear_config([:welcome])
setup do: clear_config([:instance, :account_activation_required])
@@ -408,6 +409,23 @@ defmodule Pleroma.UserTest do
refute User.following?(registered_user, remote_user)
end
+ test "it adds automatic followers for new registered accounts" do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ Pleroma.Config.put([:instance, :autofollowing_nicknames], [
+ user1.nickname,
+ user2.nickname
+ ])
+
+ cng = User.register_changeset(%User{}, @full_user_data)
+
+ {:ok, registered_user} = User.register(cng)
+
+ assert User.following?(user1, registered_user)
+ assert User.following?(user2, registered_user)
+ end
+
test "it sends a welcome message if it is set" do
welcome_user = insert(:user)
Pleroma.Config.put([:welcome, :direct_message, :enabled], true)
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -156,21 +156,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response == "Not found"
end
-
- test "it requires authentication if instance is NOT federating", %{
- conn: conn
- } do
- user = insert(:user)
-
- conn =
- put_req_header(
- conn,
- "accept",
- "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
- )
-
- ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
- end
end
describe "mastodon compatibility routes" do
@@ -338,18 +323,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "Not found" == json_response(conn2, :not_found)
end
-
- test "it requires authentication if instance is NOT federating", %{
- conn: conn
- } do
- user = insert(:user)
- note = insert(:note)
- uuid = String.split(note.data["id"], "/") |> List.last()
-
- conn = put_req_header(conn, "accept", "application/activity+json")
-
- ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
- end
end
describe "/activities/:uuid" do
@@ -421,18 +394,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "Not found" == json_response(conn2, :not_found)
end
-
- test "it requires authentication if instance is NOT federating", %{
- conn: conn
- } do
- user = insert(:user)
- activity = insert(:note_activity)
- uuid = String.split(activity.data["id"], "/") |> List.last()
-
- conn = put_req_header(conn, "accept", "application/activity+json")
-
- ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
- end
end
describe "/inbox" do
@@ -893,15 +854,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response(conn, 200) =~ announce_activity.data["object"]
end
-
- test "it requires authentication if instance is NOT federating", %{
- conn: conn
- } do
- user = insert(:user)
- conn = put_req_header(conn, "accept", "application/activity+json")
-
- ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
- end
end
describe "POST /users/:nickname/outbox (C2S)" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -2300,4 +2300,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert length(activities) == 2
end
end
+
+ test "allow fetching of accounts with an empty string name field" do
+ Tesla.Mock.mock(fn
+ %{method: :get, url: "https://princess.cat/users/mewmew"} ->
+ file = File.read!("test/fixtures/mewmew_no_name.json")
+ %Tesla.Env{status: 200, body: file}
+ end)
+
+ {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
+ assert user.name == " "
+ end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnswerHandlingTest do
})
object = Object.normalize(activity)
+ assert object.data["repliesCount"] == nil
data =
File.read!("test/fixtures/mastodon-vote.json")
@@ -41,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AnswerHandlingTest do
assert answer_object.data["inReplyTo"] == object.data["id"]
new_object = Object.get_by_ap_id(object.data["id"])
- assert new_object.data["replies_count"] == object.data["replies_count"]
+ assert new_object.data["repliesCount"] == nil
assert Enum.any?(
new_object.data["oneOf"],
diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
@@ -7,22 +7,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Oban.Testing, repo: Pleroma.Repo
import ExUnit.CaptureLog
- import Mock
import Pleroma.Factory
import Swoosh.TestAssertions
alias Pleroma.Activity
- alias Pleroma.Config
- alias Pleroma.HTML
alias Pleroma.MFA
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
- alias Pleroma.Web
- alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.MediaProxy
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -153,300 +147,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
end
- describe "DELETE /api/pleroma/admin/users" do
- test "single user", %{admin: admin, conn: conn} do
- clear_config([:instance, :federating], true)
-
- user =
- insert(:user,
- avatar: %{"url" => [%{"href" => "https://someurl"}]},
- banner: %{"url" => [%{"href" => "https://somebanner"}]},
- bio: "Hello world!",
- name: "A guy"
- )
-
- # Create some activities to check they got deleted later
- follower = insert(:user)
- {:ok, _} = CommonAPI.post(user, %{status: "test"})
- {:ok, _, _, _} = CommonAPI.follow(user, follower)
- {:ok, _, _, _} = CommonAPI.follow(follower, user)
- user = Repo.get(User, user.id)
- assert user.note_count == 1
- assert user.follower_count == 1
- assert user.following_count == 1
- refute user.deactivated
-
- with_mock Pleroma.Web.Federator,
- publish: fn _ -> nil end,
- perform: fn _, _ -> nil end do
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
-
- ObanHelpers.perform_all()
-
- assert User.get_by_nickname(user.nickname).deactivated
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted users: @#{user.nickname}"
-
- assert json_response(conn, 200) == [user.nickname]
-
- user = Repo.get(User, user.id)
- assert user.deactivated
-
- assert user.avatar == %{}
- assert user.banner == %{}
- assert user.note_count == 0
- assert user.follower_count == 0
- assert user.following_count == 0
- assert user.bio == ""
- assert user.name == nil
-
- assert called(Pleroma.Web.Federator.publish(:_))
- end
- end
-
- test "multiple users", %{admin: admin, conn: conn} do
- user_one = insert(:user)
- user_two = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users", %{
- nicknames: [user_one.nickname, user_two.nickname]
- })
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
-
- response = json_response(conn, 200)
- assert response -- [user_one.nickname, user_two.nickname] == []
- end
- end
-
- describe "/api/pleroma/admin/users" do
- test "Create", %{conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "lain",
- "email" => "lain@example.org",
- "password" => "test"
- },
- %{
- "nickname" => "lain2",
- "email" => "lain2@example.org",
- "password" => "test"
- }
- ]
- })
-
- response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
- assert response == ["success", "success"]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
- end
-
- test "Cannot create user with existing email", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "lain",
- "email" => user.email,
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => user.email,
- "nickname" => "lain"
- },
- "error" => "email has already been taken",
- "type" => "error"
- }
- ]
- end
-
- test "Cannot create user with existing nickname", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => user.nickname,
- "email" => "someuser@plerama.social",
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => "someuser@plerama.social",
- "nickname" => user.nickname
- },
- "error" => "nickname has already been taken",
- "type" => "error"
- }
- ]
- end
-
- test "Multiple user creation works in transaction", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "newuser",
- "email" => "newuser@pleroma.social",
- "password" => "test"
- },
- %{
- "nickname" => "lain",
- "email" => user.email,
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => user.email,
- "nickname" => "lain"
- },
- "error" => "email has already been taken",
- "type" => "error"
- },
- %{
- "code" => 409,
- "data" => %{
- "email" => "newuser@pleroma.social",
- "nickname" => "newuser"
- },
- "error" => "",
- "type" => "error"
- }
- ]
-
- assert User.get_by_nickname("newuser") === nil
- end
- end
-
- describe "/api/pleroma/admin/users/:nickname" do
- test "Show", %{conn: conn} do
- user = insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-
- expected = %{
- "deactivated" => false,
- "id" => to_string(user.id),
- "local" => true,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
-
- assert expected == json_response(conn, 200)
- end
-
- test "when the user doesn't exist", %{conn: conn} do
- user = build(:user)
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-
- assert %{"error" => "Not found"} == json_response(conn, 404)
- end
- end
-
- describe "/api/pleroma/admin/users/follow" do
- test "allows to force-follow another user", %{admin: admin, conn: conn} do
- user = insert(:user)
- follower = insert(:user)
-
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/follow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
-
- user = User.get_cached_by_id(user.id)
- follower = User.get_cached_by_id(follower.id)
-
- assert User.following?(follower, user)
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
- end
- end
-
- describe "/api/pleroma/admin/users/unfollow" do
- test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
- user = insert(:user)
- follower = insert(:user)
-
- User.follow(follower, user)
-
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/unfollow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
-
- user = User.get_cached_by_id(user.id)
- follower = User.get_cached_by_id(follower.id)
-
- refute User.following?(follower, user)
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
- end
- end
-
describe "PUT /api/pleroma/admin/users/tag" do
setup %{conn: conn} do
user1 = insert(:user, %{tags: ["x"]})
@@ -643,753 +343,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
end
- describe "GET /api/pleroma/admin/users" do
- test "renders users array for the first page", %{conn: conn, admin: admin} do
- user = insert(:user, local: false, tags: ["foo", "bar"])
- user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
-
- conn = get(conn, "/api/pleroma/admin/users?page=1")
-
- users =
- [
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => false,
- "tags" => ["foo", "bar"],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => user2.deactivated,
- "id" => user2.id,
- "nickname" => user2.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false,
- "approval_pending" => true,
- "url" => user2.ap_id,
- "registration_reason" => "I'm a chill dude",
- "actor_type" => "Person"
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 3,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "pagination works correctly with service users", %{conn: conn} do
- service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
-
- insert_list(25, :user)
-
- assert %{"count" => 26, "page_size" => 10, "users" => users1} =
- conn
- |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users1) == 10
- assert service1 not in users1
-
- assert %{"count" => 26, "page_size" => 10, "users" => users2} =
- conn
- |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users2) == 10
- assert service1 not in users2
-
- assert %{"count" => 26, "page_size" => 10, "users" => users3} =
- conn
- |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users3) == 6
- assert service1 not in users3
- end
-
- test "renders empty array for the second page", %{conn: conn} do
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?page=2")
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => []
- }
- end
-
- test "regular search", %{conn: conn} do
- user = insert(:user, nickname: "bob")
-
- conn = get(conn, "/api/pleroma/admin/users?query=bo")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "search by domain", %{conn: conn} do
- user = insert(:user, nickname: "nickname@domain.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "search by full nickname", %{conn: conn} do
- user = insert(:user, nickname: "nickname@domain.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "search by display name", %{conn: conn} do
- user = insert(:user, name: "Display name")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?name=display")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "search by email", %{conn: conn} do
- user = insert(:user, email: "email@example.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "regular search with page size", %{conn: conn} do
- user = insert(:user, nickname: "aalice")
- user2 = insert(:user, nickname: "alice")
-
- conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
-
- assert json_response(conn1, 200) == %{
- "count" => 2,
- "page_size" => 1,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
-
- conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
-
- assert json_response(conn2, 200) == %{
- "count" => 2,
- "page_size" => 1,
- "users" => [
- %{
- "deactivated" => user2.deactivated,
- "id" => user2.id,
- "nickname" => user2.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user2.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "only local users" do
- admin = insert(:user, is_admin: true, nickname: "john")
- token = insert(:oauth_admin_token, user: admin)
- user = insert(:user, nickname: "bob")
-
- insert(:user, nickname: "bobb", local: false)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/users?query=bo&filters=local")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "only local users with no query", %{conn: conn, admin: old_admin} do
- admin = insert(:user, is_admin: true, nickname: "john")
- user = insert(:user, nickname: "bob")
-
- insert(:user, nickname: "bobb", local: false)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=local")
-
- users =
- [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => false,
- "id" => old_admin.id,
- "local" => true,
- "nickname" => old_admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "tags" => [],
- "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => old_admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 3,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "only unapproved users", %{conn: conn} do
- user =
- insert(:user,
- nickname: "sadboy",
- approval_pending: true,
- registration_reason: "Plz let me in!"
- )
-
- insert(:user, nickname: "happyboy", approval_pending: false)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=need_approval")
-
- users =
- [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => true,
- "url" => user.ap_id,
- "registration_reason" => "Plz let me in!",
- "actor_type" => "Person"
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "load only admins", %{conn: conn, admin: admin} do
- second_admin = insert(:user, is_admin: true)
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
-
- users =
- [
- %{
- "deactivated" => false,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => admin.local,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => false,
- "id" => second_admin.id,
- "nickname" => second_admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => second_admin.local,
- "tags" => [],
- "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => second_admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "load only moderators", %{conn: conn} do
- moderator = insert(:user, is_moderator: true)
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => false,
- "id" => moderator.id,
- "nickname" => moderator.nickname,
- "roles" => %{"admin" => false, "moderator" => true},
- "local" => moderator.local,
- "tags" => [],
- "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => moderator.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "load users with tags list", %{conn: conn} do
- user1 = insert(:user, tags: ["first"])
- user2 = insert(:user, tags: ["second"])
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
-
- users =
- [
- %{
- "deactivated" => false,
- "id" => user1.id,
- "nickname" => user1.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user1.local,
- "tags" => ["first"],
- "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user1.name || user1.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user1.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- },
- %{
- "deactivated" => false,
- "id" => user2.id,
- "nickname" => user2.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user2.local,
- "tags" => ["second"],
- "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user2.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "`active` filters out users pending approval", %{token: token} do
- insert(:user, approval_pending: true)
- %{id: user_id} = insert(:user, approval_pending: false)
- %{id: admin_id} = token.user
-
- conn =
- build_conn()
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/users?filters=active")
-
- assert %{
- "count" => 2,
- "page_size" => 50,
- "users" => [
- %{"id" => ^admin_id},
- %{"id" => ^user_id}
- ]
- } = json_response(conn, 200)
- end
-
- test "it works with multiple filters" do
- admin = insert(:user, nickname: "john", is_admin: true)
- token = insert(:oauth_admin_token, user: admin)
- user = insert(:user, nickname: "bob", local: false, deactivated: true)
-
- insert(:user, nickname: "ken", local: true, deactivated: true)
- insert(:user, nickname: "bobb", local: false, deactivated: false)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/users?filters=deactivated,external")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user.local,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
-
- test "it omits relay user", %{admin: admin, conn: conn} do
- assert %User{} = Relay.get_actor()
-
- conn = get(conn, "/api/pleroma/admin/users")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => admin.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
- ]
- }
- end
- end
-
- test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
- user_one = insert(:user, deactivated: true)
- user_two = insert(:user, deactivated: true)
-
- conn =
- patch(
- conn,
- "/api/pleroma/admin/users/activate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response(conn, 200)
- assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
- user_one = insert(:user, deactivated: false)
- user_two = insert(:user, deactivated: false)
-
- conn =
- patch(
- conn,
- "/api/pleroma/admin/users/deactivate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response(conn, 200)
- assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
- user_one = insert(:user, approval_pending: true)
- user_two = insert(:user, approval_pending: true)
-
- conn =
- patch(
- conn,
- "/api/pleroma/admin/users/approve",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response(conn, 200)
- assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
- user = insert(:user)
-
- conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
-
- assert json_response(conn, 200) ==
- %{
- "deactivated" => !user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false,
- "approval_pending" => false,
- "url" => user.ap_id,
- "registration_reason" => nil,
- "actor_type" => "Person"
- }
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user.nickname}"
- end
-
describe "PUT disable_mfa" do
test "returns 200 and disable 2fa", %{conn: conn} do
user =
@@ -1891,8 +844,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
describe "instances" do
test "GET /instances/:instance/statuses", %{conn: conn} do
- user = insert(:user, local: false, nickname: "archaeme@archae.me")
- user2 = insert(:user, local: false, nickname: "test@test.com")
+ user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
+ user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
insert_pair(:note_activity, user: user)
activity = insert(:note_activity, user: user2)
@@ -2024,6 +977,73 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
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.User.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.User.Backup)
+
+ assert "" ==
+ conn
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
+ |> json_response(200)
+
+ assert Repo.aggregate(Pleroma.User.Backup, :count) == 2
+ end
+ end
end
# Needed for testing
diff --git a/test/pleroma/web/admin_api/controllers/chat_controller_test.exs b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ChatControllerTest do
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
- alias Pleroma.Config
alias Pleroma.ModerationLog
alias Pleroma.Object
alias Pleroma.Repo
diff --git a/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do
use Pleroma.Web.ConnCase, async: true
import Pleroma.Factory
- alias Pleroma.Config
@dir "test/tmp/instance_static"
@default_instance_panel ~s(<p>Welcome to <a href="https://pleroma.social" target="_blank">Pleroma!</a></p>)
diff --git a/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
import Pleroma.Factory
- alias Pleroma.Config
alias Pleroma.Web
setup do
diff --git a/test/pleroma/web/admin_api/controllers/relay_controller_test.exs b/test/pleroma/web/admin_api/controllers/relay_controller_test.exs
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.AdminAPI.RelayControllerTest do
import Pleroma.Factory
- alias Pleroma.Config
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.User
diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
import Pleroma.Factory
alias Pleroma.Activity
- alias Pleroma.Config
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.ReportNote
@@ -38,12 +37,21 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
status_ids: [activity.id]
})
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
+ content: "this is an admin note"
+ })
+
response =
conn
|> get("/api/pleroma/admin/reports/#{report_id}")
|> json_response_and_validate_schema(:ok)
assert response["id"] == report_id
+
+ [notes] = response["notes"]
+ assert notes["content"] == "this is an admin note"
end
test "returns 404 when report id is invalid", %{conn: conn} do
diff --git a/test/pleroma/web/admin_api/controllers/status_controller_test.exs b/test/pleroma/web/admin_api/controllers/status_controller_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.AdminAPI.StatusControllerTest do
import Pleroma.Factory
alias Pleroma.Activity
- alias Pleroma.Config
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.User
diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
@@ -0,0 +1,970 @@
+# 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.UserControllerTest do
+ use Pleroma.Web.ConnCase
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import Mock
+ import Pleroma.Factory
+
+ alias Pleroma.HTML
+ alias Pleroma.ModerationLog
+ alias Pleroma.Repo
+ alias Pleroma.Tests.ObanHelpers
+ alias Pleroma.User
+ alias Pleroma.Web
+ alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MediaProxy
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ :ok
+ end
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ test "with valid `admin_token` query parameter, skips OAuth scopes check" do
+ clear_config([:admin_token], "password123")
+
+ user = insert(:user)
+
+ conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
+
+ assert json_response(conn, 200)
+ end
+
+ describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
+ setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
+
+ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
+ %{admin: admin} do
+ user = insert(:user)
+ url = "/api/pleroma/admin/users/#{user.nickname}"
+
+ good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
+ good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
+
+ bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
+ bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
+ bad_token3 = nil
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, nil)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+
+ for bad_token <- [bad_token1, bad_token2, bad_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, bad_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+ end
+
+ describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
+ setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
+
+ test "GET /api/pleroma/admin/users/:nickname requires " <>
+ "read:accounts or admin:read:accounts or broader scope",
+ %{admin: admin} do
+ user = insert(:user)
+ url = "/api/pleroma/admin/users/#{user.nickname}"
+
+ good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
+ good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
+ good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
+ good_token5 = insert(:oauth_token, user: admin, scopes: ["read"])
+
+ good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5]
+
+ bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"])
+ bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
+ bad_token3 = nil
+
+ for good_token <- good_tokens do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
+ for good_token <- good_tokens do
+ conn =
+ build_conn()
+ |> assign(:user, nil)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+
+ for bad_token <- [bad_token1, bad_token2, bad_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, bad_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/users" do
+ test "single user", %{admin: admin, conn: conn} do
+ clear_config([:instance, :federating], true)
+
+ user =
+ insert(:user,
+ avatar: %{"url" => [%{"href" => "https://someurl"}]},
+ banner: %{"url" => [%{"href" => "https://somebanner"}]},
+ bio: "Hello world!",
+ name: "A guy"
+ )
+
+ # Create some activities to check they got deleted later
+ follower = insert(:user)
+ {:ok, _} = CommonAPI.post(user, %{status: "test"})
+ {:ok, _, _, _} = CommonAPI.follow(user, follower)
+ {:ok, _, _, _} = CommonAPI.follow(follower, user)
+ user = Repo.get(User, user.id)
+ assert user.note_count == 1
+ assert user.follower_count == 1
+ assert user.following_count == 1
+ refute user.deactivated
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _ -> nil end,
+ perform: fn _, _ -> nil end do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
+
+ ObanHelpers.perform_all()
+
+ assert User.get_by_nickname(user.nickname).deactivated
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted users: @#{user.nickname}"
+
+ assert json_response(conn, 200) == [user.nickname]
+
+ user = Repo.get(User, user.id)
+ assert user.deactivated
+
+ assert user.avatar == %{}
+ assert user.banner == %{}
+ assert user.note_count == 0
+ assert user.follower_count == 0
+ assert user.following_count == 0
+ assert user.bio == ""
+ assert user.name == nil
+
+ assert called(Pleroma.Web.Federator.publish(:_))
+ end
+ end
+
+ test "multiple users", %{admin: admin, conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
+
+ response = json_response(conn, 200)
+ assert response -- [user_one.nickname, user_two.nickname] == []
+ end
+ end
+
+ describe "/api/pleroma/admin/users" do
+ test "Create", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "lain",
+ "email" => "lain@example.org",
+ "password" => "test"
+ },
+ %{
+ "nickname" => "lain2",
+ "email" => "lain2@example.org",
+ "password" => "test"
+ }
+ ]
+ })
+
+ response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
+ assert response == ["success", "success"]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
+ end
+
+ test "Cannot create user with existing email", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "lain",
+ "email" => user.email,
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => user.email,
+ "nickname" => "lain"
+ },
+ "error" => "email has already been taken",
+ "type" => "error"
+ }
+ ]
+ end
+
+ test "Cannot create user with existing nickname", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => user.nickname,
+ "email" => "someuser@plerama.social",
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => "someuser@plerama.social",
+ "nickname" => user.nickname
+ },
+ "error" => "nickname has already been taken",
+ "type" => "error"
+ }
+ ]
+ end
+
+ test "Multiple user creation works in transaction", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "newuser",
+ "email" => "newuser@pleroma.social",
+ "password" => "test"
+ },
+ %{
+ "nickname" => "lain",
+ "email" => user.email,
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => user.email,
+ "nickname" => "lain"
+ },
+ "error" => "email has already been taken",
+ "type" => "error"
+ },
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => "newuser@pleroma.social",
+ "nickname" => "newuser"
+ },
+ "error" => "",
+ "type" => "error"
+ }
+ ]
+
+ assert User.get_by_nickname("newuser") === nil
+ end
+ end
+
+ describe "/api/pleroma/admin/users/:nickname" do
+ test "Show", %{conn: conn} do
+ user = insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
+
+ assert user_response(user) == json_response(conn, 200)
+ end
+
+ test "when the user doesn't exist", %{conn: conn} do
+ user = build(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
+
+ assert %{"error" => "Not found"} == json_response(conn, 404)
+ end
+ end
+
+ describe "/api/pleroma/admin/users/follow" do
+ test "allows to force-follow another user", %{admin: admin, conn: conn} do
+ user = insert(:user)
+ follower = insert(:user)
+
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/follow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_cached_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
+
+ assert User.following?(follower, user)
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
+ end
+ end
+
+ describe "/api/pleroma/admin/users/unfollow" do
+ test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
+ user = insert(:user)
+ follower = insert(:user)
+
+ User.follow(follower, user)
+
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/unfollow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_cached_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
+
+ refute User.following?(follower, user)
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/users" do
+ test "renders users array for the first page", %{conn: conn, admin: admin} do
+ user = insert(:user, local: false, tags: ["foo", "bar"])
+ user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
+
+ conn = get(conn, "/api/pleroma/admin/users?page=1")
+
+ users =
+ [
+ user_response(
+ admin,
+ %{"roles" => %{"admin" => true, "moderator" => false}}
+ ),
+ user_response(user, %{"local" => false, "tags" => ["foo", "bar"]}),
+ user_response(
+ user2,
+ %{
+ "local" => true,
+ "approval_pending" => true,
+ "registration_reason" => "I'm a chill dude",
+ "actor_type" => "Person"
+ }
+ )
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 3,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "pagination works correctly with service users", %{conn: conn} do
+ service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
+
+ insert_list(25, :user)
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users1} =
+ conn
+ |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users1) == 10
+ assert service1 not in users1
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users2} =
+ conn
+ |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users2) == 10
+ assert service1 not in users2
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users3} =
+ conn
+ |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users3) == 6
+ assert service1 not in users3
+ end
+
+ test "renders empty array for the second page", %{conn: conn} do
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => []
+ }
+ end
+
+ test "regular search", %{conn: conn} do
+ user = insert(:user, nickname: "bob")
+
+ conn = get(conn, "/api/pleroma/admin/users?query=bo")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user, %{"local" => true})]
+ }
+ end
+
+ test "search by domain", %{conn: conn} do
+ user = insert(:user, nickname: "nickname@domain.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "search by full nickname", %{conn: conn} do
+ user = insert(:user, nickname: "nickname@domain.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "search by display name", %{conn: conn} do
+ user = insert(:user, name: "Display name")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?name=display")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "search by email", %{conn: conn} do
+ user = insert(:user, email: "email@example.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "regular search with page size", %{conn: conn} do
+ user = insert(:user, nickname: "aalice")
+ user2 = insert(:user, nickname: "alice")
+
+ conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
+
+ assert json_response(conn1, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [user_response(user)]
+ }
+
+ conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
+
+ assert json_response(conn2, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [user_response(user2)]
+ }
+ end
+
+ test "only local users" do
+ admin = insert(:user, is_admin: true, nickname: "john")
+ token = insert(:oauth_admin_token, user: admin)
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/users?query=bo&filters=local")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "only local users with no query", %{conn: conn, admin: old_admin} do
+ admin = insert(:user, is_admin: true, nickname: "john")
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=local")
+
+ users =
+ [
+ user_response(user),
+ user_response(admin, %{
+ "roles" => %{"admin" => true, "moderator" => false}
+ }),
+ user_response(old_admin, %{
+ "deactivated" => false,
+ "roles" => %{"admin" => true, "moderator" => false}
+ })
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 3,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "only unconfirmed users", %{conn: conn} do
+ sad_user = insert(:user, nickname: "sadboy", confirmation_pending: true)
+ old_user = insert(:user, nickname: "oldboy", confirmation_pending: true)
+
+ insert(:user, nickname: "happyboy", approval_pending: false)
+ insert(:user, confirmation_pending: false)
+
+ result =
+ conn
+ |> get("/api/pleroma/admin/users?filters=unconfirmed")
+ |> json_response(200)
+
+ users =
+ Enum.map([old_user, sad_user], fn user ->
+ user_response(user, %{
+ "confirmation_pending" => true,
+ "approval_pending" => false
+ })
+ end)
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert result == %{"count" => 2, "page_size" => 50, "users" => users}
+ end
+
+ test "only unapproved users", %{conn: conn} do
+ user =
+ insert(:user,
+ nickname: "sadboy",
+ approval_pending: true,
+ registration_reason: "Plz let me in!"
+ )
+
+ insert(:user, nickname: "happyboy", approval_pending: false)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=need_approval")
+
+ users = [
+ user_response(
+ user,
+ %{"approval_pending" => true, "registration_reason" => "Plz let me in!"}
+ )
+ ]
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "load only admins", %{conn: conn, admin: admin} do
+ second_admin = insert(:user, is_admin: true)
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
+
+ users =
+ [
+ user_response(admin, %{
+ "deactivated" => false,
+ "roles" => %{"admin" => true, "moderator" => false}
+ }),
+ user_response(second_admin, %{
+ "deactivated" => false,
+ "roles" => %{"admin" => true, "moderator" => false}
+ })
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "load only moderators", %{conn: conn} do
+ moderator = insert(:user, is_moderator: true)
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ user_response(moderator, %{
+ "deactivated" => false,
+ "roles" => %{"admin" => false, "moderator" => true}
+ })
+ ]
+ }
+ end
+
+ test "load users with actor_type is Person", %{admin: admin, conn: conn} do
+ insert(:user, actor_type: "Service")
+ insert(:user, actor_type: "Application")
+
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> get(user_path(conn, :list), %{actor_types: ["Person"]})
+ |> json_response(200)
+
+ users =
+ [
+ user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}),
+ user_response(user1),
+ user_response(user2)
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert response == %{"count" => 3, "page_size" => 50, "users" => users}
+ end
+
+ test "load users with actor_type is Person and Service", %{admin: admin, conn: conn} do
+ user_service = insert(:user, actor_type: "Service")
+ insert(:user, actor_type: "Application")
+
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ response =
+ conn
+ |> get(user_path(conn, :list), %{actor_types: ["Person", "Service"]})
+ |> json_response(200)
+
+ users =
+ [
+ user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}}),
+ user_response(user1),
+ user_response(user2),
+ user_response(user_service, %{"actor_type" => "Service"})
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert response == %{"count" => 4, "page_size" => 50, "users" => users}
+ end
+
+ test "load users with actor_type is Service", %{conn: conn} do
+ user_service = insert(:user, actor_type: "Service")
+ insert(:user, actor_type: "Application")
+ insert(:user)
+ insert(:user)
+
+ response =
+ conn
+ |> get(user_path(conn, :list), %{actor_types: ["Service"]})
+ |> json_response(200)
+
+ users = [user_response(user_service, %{"actor_type" => "Service"})]
+
+ assert response == %{"count" => 1, "page_size" => 50, "users" => users}
+ end
+
+ test "load users with tags list", %{conn: conn} do
+ user1 = insert(:user, tags: ["first"])
+ user2 = insert(:user, tags: ["second"])
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
+
+ users =
+ [
+ user_response(user1, %{"tags" => ["first"]}),
+ user_response(user2, %{"tags" => ["second"]})
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "`active` filters out users pending approval", %{token: token} do
+ insert(:user, approval_pending: true)
+ %{id: user_id} = insert(:user, approval_pending: false)
+ %{id: admin_id} = token.user
+
+ conn =
+ build_conn()
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/users?filters=active")
+
+ assert %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => [
+ %{"id" => ^admin_id},
+ %{"id" => ^user_id}
+ ]
+ } = json_response(conn, 200)
+ end
+
+ test "it works with multiple filters" do
+ admin = insert(:user, nickname: "john", is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+ user = insert(:user, nickname: "bob", local: false, deactivated: true)
+
+ insert(:user, nickname: "ken", local: true, deactivated: true)
+ insert(:user, nickname: "bobb", local: false, deactivated: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/users?filters=deactivated,external")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [user_response(user)]
+ }
+ end
+
+ test "it omits relay user", %{admin: admin, conn: conn} do
+ assert %User{} = Relay.get_actor()
+
+ conn = get(conn, "/api/pleroma/admin/users")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}})
+ ]
+ }
+ end
+ end
+
+ test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
+ user_one = insert(:user, deactivated: true)
+ user_two = insert(:user, deactivated: true)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/activate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
+ user_one = insert(:user, deactivated: false)
+ user_two = insert(:user, deactivated: false)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/deactivate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
+ user_one = insert(:user, approval_pending: true)
+ user_two = insert(:user, approval_pending: true)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/approve",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
+ user = insert(:user)
+
+ conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+
+ assert json_response(conn, 200) ==
+ user_response(
+ user,
+ %{"deactivated" => !user.deactivated}
+ )
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user.nickname}"
+ end
+
+ defp user_response(user, attrs \\ %{}) do
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => user.local,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false,
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil,
+ "actor_type" => "Person"
+ }
+ |> Map.merge(attrs)
+ end
+end
diff --git a/test/pleroma/web/admin_api/search_test.exs b/test/pleroma/web/admin_api/search_test.exs
@@ -143,6 +143,20 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
assert user2 in users
end
+ test "it returns users by actor_types" do
+ user_service = insert(:user, actor_type: "Service")
+ user_application = insert(:user, actor_type: "Application")
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, [^user_service], 1} = Search.user(%{actor_types: ["Service"]})
+ {:ok, [^user_application], 1} = Search.user(%{actor_types: ["Application"]})
+ {:ok, [^user1, ^user2], 2} = Search.user(%{actor_types: ["Person"]})
+
+ {:ok, [^user_service, ^user1, ^user2], 3} =
+ Search.user(%{actor_types: ["Person", "Service"]})
+ end
+
test "it returns user by display name" do
user = insert(:user, name: "Display name")
insert(:user)
@@ -178,6 +192,17 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
assert count == 1
end
+ test "it returns unconfirmed user" do
+ unconfirmed = insert(:user, confirmation_pending: true)
+ insert(:user)
+ insert(:user)
+
+ {:ok, _results, total} = Search.user()
+ {:ok, [^unconfirmed], count} = Search.user(%{unconfirmed: true})
+ assert total == 3
+ assert count == 1
+ end
+
test "it returns non-discoverable users" do
insert(:user)
insert(:user, is_discoverable: false)
diff --git a/test/pleroma/web/endpoint/metrics_exporter_test.exs b/test/pleroma/web/endpoint/metrics_exporter_test.exs
@@ -0,0 +1,68 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Endpoint.MetricsExporterTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Web.Endpoint.MetricsExporter
+
+ defp config do
+ Application.get_env(:prometheus, MetricsExporter)
+ end
+
+ describe "with default config" do
+ test "does NOT expose app metrics", %{conn: conn} do
+ conn
+ |> get(config()[:path])
+ |> json_response(404)
+ end
+ end
+
+ describe "when enabled" do
+ setup do
+ initial_config = config()
+ on_exit(fn -> Application.put_env(:prometheus, MetricsExporter, initial_config) end)
+
+ Application.put_env(
+ :prometheus,
+ MetricsExporter,
+ Keyword.put(initial_config, :enabled, true)
+ )
+ end
+
+ test "serves app metrics", %{conn: conn} do
+ conn = get(conn, config()[:path])
+ assert response = response(conn, 200)
+
+ for metric <- [
+ "http_requests_total",
+ "http_request_duration_microseconds",
+ "phoenix_controller_call_duration",
+ "telemetry_scrape_duration",
+ "erlang_vm_memory_atom_bytes_total"
+ ] do
+ assert response =~ ~r/#{metric}/
+ end
+ end
+
+ test "when IP whitelist configured, " <>
+ "serves app metrics only if client IP is whitelisted",
+ %{conn: conn} do
+ Application.put_env(
+ :prometheus,
+ MetricsExporter,
+ Keyword.put(config(), :ip_whitelist, ["127.127.127.127", {1, 1, 1, 1}, '255.255.255.255'])
+ )
+
+ conn
+ |> get(config()[:path])
+ |> json_response(404)
+
+ conn
+ |> Map.put(:remote_ip, {127, 127, 127, 127})
+ |> get(config()[:path])
+ |> response(200)
+ end
+ end
+end
diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
import Pleroma.Factory
import SweetXml
+ alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Feed.FeedView
@@ -15,7 +16,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
setup do: clear_config([:feed])
test "gets a feed (ATOM)", %{conn: conn} do
- Pleroma.Config.put(
+ Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
@@ -82,7 +83,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
end
test "gets a feed (RSS)", %{conn: conn} do
- Pleroma.Config.put(
+ Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
@@ -157,7 +158,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
response =
conn
|> put_req_header("accept", "application/rss+xml")
- |> get(tag_feed_path(conn, :feed, "pleromaart"))
+ |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
|> response(200)
xml = parse(response)
@@ -183,14 +184,12 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
end
describe "private instance" do
- setup do: clear_config([:instance, :public])
+ setup do: clear_config([:instance, :public], false)
test "returns 404 for tags feed", %{conn: conn} do
- Config.put([:instance, :public], false)
-
conn
|> put_req_header("accept", "application/rss+xml")
- |> get(tag_feed_path(conn, :feed, "pleromaart"))
+ |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
|> response(404)
end
end
diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
- setup do: clear_config([:instance, :federating], true)
+ setup do: clear_config([:static_fe, :enabled], false)
describe "feed" do
setup do: clear_config([:feed])
@@ -192,6 +192,16 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
|> get(user_feed_path(conn, :feed, user.nickname))
|> response(404)
end
+
+ test "does not require authentication on non-federating instances", %{conn: conn} do
+ clear_config([:instance, :federating], false)
+ user = insert(:user)
+
+ conn
+ |> put_req_header("accept", "application/rss+xml")
+ |> get("/users/#{user.nickname}/feed.rss")
+ |> response(200)
+ end
end
# Note: see ActivityPubControllerTest for JSON format tests
diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -1509,28 +1509,103 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
test "getting a list of mutes" do
%{user: user, conn: conn} = oauth_access(["read:mutes"])
- other_user = insert(:user)
+ %{id: id1} = other_user1 = insert(:user)
+ %{id: id2} = other_user2 = insert(:user)
+ %{id: id3} = other_user3 = insert(:user)
+
+ {:ok, _user_relationships} = User.mute(user, other_user1)
+ {:ok, _user_relationships} = User.mute(user, other_user2)
+ {:ok, _user_relationships} = User.mute(user, other_user3)
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes")
+ |> json_response_and_validate_schema(200)
+
+ assert [id1, id2, id3] == Enum.map(result, & &1["id"])
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes?limit=1")
+ |> json_response_and_validate_schema(200)
- {:ok, _user_relationships} = User.mute(user, other_user)
+ assert [%{"id" => ^id1}] = result
- conn = get(conn, "/api/v1/mutes")
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes?since_id=#{id1}")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes?since_id=#{id1}&max_id=#{id3}")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"id" => ^id2}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/mutes?since_id=#{id1}&limit=1")
+ |> json_response_and_validate_schema(200)
- other_user_id = to_string(other_user.id)
- assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
+ assert [%{"id" => ^id2}] = result
end
test "getting a list of blocks" do
%{user: user, conn: conn} = oauth_access(["read:blocks"])
- other_user = insert(:user)
+ %{id: id1} = other_user1 = insert(:user)
+ %{id: id2} = other_user2 = insert(:user)
+ %{id: id3} = other_user3 = insert(:user)
- {:ok, _user_relationship} = User.block(user, other_user)
+ {:ok, _user_relationship} = User.block(user, other_user1)
+ {:ok, _user_relationship} = User.block(user, other_user3)
+ {:ok, _user_relationship} = User.block(user, other_user2)
- conn =
+ result =
conn
|> assign(:user, user)
|> get("/api/v1/blocks")
+ |> json_response_and_validate_schema(200)
+
+ assert [id1, id2, id3] == Enum.map(result, & &1["id"])
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/blocks?limit=1")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"id" => ^id1}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/blocks?since_id=#{id1}")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"id" => ^id2}, %{"id" => ^id3}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/blocks?since_id=#{id1}&max_id=#{id3}")
+ |> json_response_and_validate_schema(200)
+
+ assert [%{"id" => ^id2}] = result
+
+ result =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/blocks?since_id=#{id1}&limit=1")
+ |> json_response_and_validate_schema(200)
- other_user_id = to_string(other_user.id)
- assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
+ assert [%{"id" => ^id2}] = result
end
end
diff --git a/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
use Pleroma.Web.ConnCase
+ alias Pleroma.Conversation.Participation
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@@ -28,10 +29,10 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
user_three: user_three,
conn: conn
} do
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+ assert Participation.unread_count(user_two) == 0
{:ok, direct} = create_direct_message(user_one, [user_two, user_three])
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
+ assert Participation.unread_count(user_two) == 1
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
@@ -54,12 +55,33 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
account_ids = Enum.map(res_accounts, & &1["id"])
assert length(res_accounts) == 2
+ assert user_one.id not in account_ids
assert user_two.id in account_ids
assert user_three.id in account_ids
assert is_binary(res_id)
assert unread == false
assert res_last_status["id"] == direct.id
- assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
+ assert res_last_status["account"]["id"] == user_one.id
+ assert Participation.unread_count(user_one) == 0
+ end
+
+ test "includes the user if the user is the only participant", %{
+ user: user_one,
+ conn: conn
+ } do
+ {:ok, _direct} = create_direct_message(user_one, [])
+
+ res_conn = get(conn, "/api/v1/conversations")
+
+ assert response = json_response_and_validate_schema(res_conn, 200)
+
+ assert [
+ %{
+ "accounts" => [account]
+ }
+ ] = response
+
+ assert user_one.id == account["id"]
end
test "observes limit params", %{
@@ -134,8 +156,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
user_two = insert(:user)
{:ok, direct} = create_direct_message(user_one, [user_two])
- assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
+ assert Participation.unread_count(user_one) == 0
+ assert Participation.unread_count(user_two) == 1
user_two_conn =
build_conn()
@@ -155,8 +177,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
|> post("/api/v1/conversations/#{direct_conversation_id}/read")
|> json_response_and_validate_schema(200)
- assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+ assert Participation.unread_count(user_one) == 0
+ assert Participation.unread_count(user_two) == 0
# The conversation is marked as unread on reply
{:ok, _} =
@@ -171,8 +193,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
|> get("/api/v1/conversations")
|> json_response_and_validate_schema(200)
- assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+ assert Participation.unread_count(user_one) == 1
+ assert Participation.unread_count(user_two) == 0
# A reply doesn't increment the user's unread_conversation_count if the conversation is unread
{:ok, _} =
@@ -182,8 +204,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
in_reply_to_status_id: direct.id
})
- assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
- assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+ assert Participation.unread_count(user_one) == 1
+ assert Participation.unread_count(user_two) == 0
end
test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do
diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
import Pleroma.Factory
import Tesla.Mock
- alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@@ -166,6 +165,18 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
activities = json_response_and_validate_schema(res_conn, 200)
[%{"id" => ^activity_id}] = activities
end
+
+ test "can be filtered by instance", %{conn: conn} do
+ user = insert(:user, ap_id: "https://lain.com/users/lain")
+ insert(:note_activity, local: false)
+ insert(:note_activity, local: false)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "test"})
+
+ conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
+
+ assert length(json_response_and_validate_schema(conn, :ok)) == 1
+ end
end
defp local_and_remote_activities do
diff --git a/test/pleroma/web/mastodon_api/views/conversation_view_test.exs b/test/pleroma/web/mastodon_api/views/conversation_view_test.exs
@@ -36,9 +36,11 @@ defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do
assert conversation.id == participation.id |> to_string()
assert conversation.last_status.id == activity.id
+ assert conversation.last_status.account.id == user.id
assert [account] = conversation.accounts
assert account.id == other_user.id
+
assert conversation.last_status.pleroma.direct_conversation_id == participation.id
end
end
diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs
@@ -44,7 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do
],
voted: false,
votes_count: 0,
- voters_count: nil
+ voters_count: 0
}
result = PollView.render("show.json", %{object: object})
diff --git a/test/pleroma/web/o_status/o_status_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
import Pleroma.Factory
- alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -21,7 +20,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
:ok
end
- setup do: clear_config([:instance, :federating], true)
+ setup do: clear_config([:static_fe, :enabled], false)
describe "Mastodon compatibility routes" do
setup %{conn: conn} do
@@ -215,15 +214,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 404)
end
- test "it requires authentication if instance is NOT federating", %{
+ test "does not require authentication on non-federating instances", %{
conn: conn
} do
- user = insert(:user)
+ clear_config([:instance, :federating], false)
note_activity = insert(:note_activity)
- conn = put_req_header(conn, "accept", "text/html")
-
- ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user)
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/notice/#{note_activity.id}")
+ |> response(200)
end
end
@@ -325,14 +325,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> response(404)
end
- test "it requires authentication if instance is NOT federating", %{
+ test "does not require authentication on non-federating instances", %{
conn: conn,
note_activity: note_activity
} do
- user = insert(:user)
- conn = put_req_header(conn, "accept", "text/html")
+ clear_config([:instance, :federating], false)
- ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user)
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/notice/#{note_activity.id}/embed_player")
+ |> response(200)
end
end
end
diff --git a/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs
@@ -0,0 +1,85 @@
+# 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.BackupControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.User.Backup
+ alias Pleroma.Web.PleromaAPI.BackupView
+
+ setup do
+ clear_config([Pleroma.Upload, :uploader])
+ clear_config([Backup, :limit_days])
+ oauth_access(["read:accounts"])
+ end
+
+ test "GET /api/v1/pleroma/backups", %{user: user, conn: conn} do
+ assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id}}} = Backup.create(user)
+
+ backup = Backup.get(backup_id)
+
+ response =
+ conn
+ |> get("/api/v1/pleroma/backups")
+ |> json_response_and_validate_schema(:ok)
+
+ assert [
+ %{
+ "content_type" => "application/zip",
+ "url" => url,
+ "file_size" => 0,
+ "processed" => false,
+ "inserted_at" => _
+ }
+ ] = response
+
+ assert url == BackupView.download_url(backup)
+
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert [
+ %{
+ "url" => ^url,
+ "processed" => true
+ }
+ ] =
+ conn
+ |> get("/api/v1/pleroma/backups")
+ |> json_response_and_validate_schema(:ok)
+ end
+
+ test "POST /api/v1/pleroma/backups", %{user: _user, conn: conn} do
+ assert [
+ %{
+ "content_type" => "application/zip",
+ "url" => url,
+ "file_size" => 0,
+ "processed" => false,
+ "inserted_at" => _
+ }
+ ] =
+ conn
+ |> post("/api/v1/pleroma/backups")
+ |> json_response_and_validate_schema(:ok)
+
+ Pleroma.Tests.ObanHelpers.perform_all()
+
+ assert [
+ %{
+ "url" => ^url,
+ "processed" => true
+ }
+ ] =
+ conn
+ |> get("/api/v1/pleroma/backups")
+ |> json_response_and_validate_schema(:ok)
+
+ days = Pleroma.Config.get([Backup, :limit_days])
+
+ assert %{"error" => "Last export was less than #{days} days ago"} ==
+ conn
+ |> post("/api/v1/pleroma/backups")
+ |> json_response_and_validate_schema(400)
+ end
+end
diff --git a/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs
@@ -82,11 +82,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
result =
conn
|> put_req_header("content-type", "application/json")
+ |> put_req_header("idempotency-key", "123")
|> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "Hallo!!"})
|> json_response_and_validate_schema(200)
assert result["content"] == "Hallo!!"
assert result["chat_id"] == chat.id |> to_string()
+ assert result["idempotency_key"] == "123"
end
test "it fails if there is no content", %{conn: conn, user: user} do
@@ -341,6 +343,35 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
assert length(result) == 0
end
+ test "it does not return chats with users you muted", %{conn: conn, user: user} do
+ recipient = insert(:user)
+
+ {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 1
+
+ User.mute(user, recipient)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 0
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats?with_muted=true")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 1
+ end
+
test "it returns all chats", %{conn: conn, user: user} do
Enum.each(1..30, fn _ ->
recipient = insert(:user)
diff --git a/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == false
assert Participation.get(participation1.id).read == false
- assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
+ assert Participation.unread_count(other_user) == 2
[%{"unread" => false}, %{"unread" => false}] =
conn
@@ -131,6 +131,6 @@ defmodule Pleroma.Web.PleromaAPI.ConversationControllerTest do
[participation2, participation1] = Participation.for_user(other_user)
assert Participation.get(participation2.id).read == true
assert Participation.get(participation1.id).read == true
- assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
+ assert Participation.unread_count(other_user) == 0
end
end
diff --git a/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/instances_controller_test.exs
@@ -0,0 +1,38 @@
+# 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.InstancesControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Instances
+
+ setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
+
+ setup do
+ constant = "http://consistently-unreachable.name/"
+ eventual = "http://eventually-unreachable.com/path"
+
+ {:ok, %Pleroma.Instances.Instance{unreachable_since: constant_unreachable}} =
+ Instances.set_consistently_unreachable(constant)
+
+ _eventual_unrechable = Instances.set_unreachable(eventual)
+
+ %{constant_unreachable: constant_unreachable, constant: constant}
+ end
+
+ test "GET /api/v1/pleroma/federation_status", %{
+ conn: conn,
+ constant_unreachable: constant_unreachable,
+ constant: constant
+ } do
+ constant_host = URI.parse(constant).host
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> get("/api/v1/pleroma/federation_status")
+ |> json_response_and_validate_schema(200) == %{
+ "unreachable" => %{constant_host => to_string(constant_unreachable)}
+ }
+ end
+end
diff --git a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
- alias Pleroma.Config
alias Pleroma.Tests.ObanHelpers
import Pleroma.Factory
diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
@@ -25,7 +25,9 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
}
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
- {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
+
+ {:ok, activity} =
+ CommonAPI.post_chat_message(user, recipient, "kippis :firefox:", idempotency_key: "123")
chat = Chat.get(user.id, recipient.ap_id)
@@ -42,6 +44,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
assert chat_message[:created_at]
assert chat_message[:unread] == false
assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
+ assert chat_message[:idempotency_key] == "123"
clear_config([:rich_media, :enabled], true)
diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
use Pleroma.Web.ConnCase
+ import Mock
@dir "test/tmp/instance_static"
@@ -53,4 +54,24 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
index = get(conn, "/pleroma/admin/")
assert html_response(index, 200) == "from frontend plug"
end
+
+ test "exclude invalid path", %{conn: conn} do
+ name = "pleroma-fe"
+ ref = "dist"
+ clear_config([:media_proxy, :enabled], true)
+ clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+ clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+
+ File.mkdir_p!("#{path}/proxy/rr/ss")
+ File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image")
+
+ url =
+ Pleroma.Web.MediaProxy.encode_url("https://pbs.twimg.com/media/Ek7w8WPVcAApOvN.jpg:large")
+
+ with_mock Pleroma.ReverseProxy,
+ call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
+ assert %Plug.Conn{status: :success} = get(conn, url)
+ end
+ end
end
diff --git a/test/pleroma/web/plugs/http_security_plug_test.exs b/test/pleroma/web/plugs/http_security_plug_test.exs
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Config
alias Plug.Conn
describe "http security enabled" do
diff --git a/test/pleroma/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs
@@ -6,14 +6,12 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Activity
- alias Pleroma.Config
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup_all do: clear_config([:static_fe, :enabled], true)
- setup do: clear_config([:instance, :federating], true)
setup %{conn: conn} do
conn = put_req_header(conn, "accept", "text/html")
@@ -74,8 +72,27 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
refute html =~ ">test29<"
end
- test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
- ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user)
+ test "does not require authentication on non-federating instances", %{
+ conn: conn,
+ user: user
+ } do
+ clear_config([:instance, :federating], false)
+
+ conn = get(conn, "/users/#{user.nickname}")
+
+ assert html_response(conn, 200) =~ user.nickname
+ end
+
+ test "returns 404 for local user with `restrict_unauthenticated/profiles/local` setting", %{
+ conn: conn
+ } do
+ clear_config([:restrict_unauthenticated, :profiles, :local], true)
+
+ local_user = insert(:user, local: true)
+
+ conn
+ |> get("/users/#{local_user.nickname}")
+ |> html_response(404)
end
end
@@ -187,10 +204,28 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
assert html_response(conn, 302) =~ "redirected"
end
- test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do
+ test "does not require authentication on non-federating instances", %{
+ conn: conn,
+ user: user
+ } do
+ clear_config([:instance, :federating], false)
+
+ {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
+
+ conn = get(conn, "/notice/#{activity.id}")
+
+ assert html_response(conn, 200) =~ "testing a thing!"
+ end
+
+ test "returns 404 for local public activity with `restrict_unauthenticated/activities/local` setting",
+ %{conn: conn, user: user} do
+ clear_config([:restrict_unauthenticated, :activities, :local], true)
+
{:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
- ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user)
+ conn
+ |> get("/notice/#{activity.id}")
+ |> html_response(404)
end
end
end
diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs
@@ -29,6 +29,14 @@ defmodule Pleroma.Web.StreamerTest do
assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
end
+ test "allows instance streams" do
+ assert {:ok, "public:remote:lain.com"} =
+ Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
+
+ assert {:ok, "public:remote:media:lain.com"} =
+ Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"})
+ end
+
test "allows hashtag streams" do
assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"})
end
@@ -255,7 +263,9 @@ defmodule Pleroma.Web.StreamerTest do
} do
other_user = insert(:user)
- {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno")
+ {:ok, create_activity} =
+ CommonAPI.post_chat_message(other_user, user, "hey cirno", idempotency_key: "123")
+
object = Object.normalize(create_activity, false)
chat = Chat.get(user.id, other_user.ap_id)
cm_ref = MessageReference.for_chat_and_object(chat, object)
diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
alias Pleroma.User
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
@@ -112,28 +112,6 @@ defmodule Pleroma.Web.ConnCase do
defp json_response_and_validate_schema(conn, _status) do
flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
end
-
- defp ensure_federating_or_authenticated(conn, url, user) do
- initial_setting = Config.get([:instance, :federating])
- on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
-
- Config.put([:instance, :federating], false)
-
- conn
- |> get(url)
- |> response(403)
-
- conn
- |> assign(:user, user)
- |> get(url)
- |> response(200)
-
- Config.put([:instance, :federating], true)
-
- conn
- |> get(url)
- |> response(200)
- end
end
end
diff --git a/test/support/oban_helpers.ex b/test/support/oban_helpers.ex
@@ -7,6 +7,8 @@ defmodule Pleroma.Tests.ObanHelpers do
Oban test helpers.
"""
+ require Ecto.Query
+
alias Pleroma.Repo
def wipe_all do
@@ -15,6 +17,7 @@ defmodule Pleroma.Tests.ObanHelpers do
def perform_all do
Oban.Job
+ |> Ecto.Query.where(state: "available")
|> Repo.all()
|> perform()
end