commit: 1170dfdd49d9ab0d3a1788db253e88e1bbeb67e1
parent 31487e5be48f3845d9cf57116dd5a618a733e259
Author: lain <lain@soykaf.club>
Date: Thu, 19 Dec 2024 10:15:30 +0000
Merge branch 'release/2.8.0' into 'stable'
Release/2.8.0
See merge request pleroma/pleroma!4295
Diffstat:
217 files changed, 4475 insertions(+), 1980 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -63,3 +63,6 @@ pleroma.iml
archive-*
.gitlab-ci-local
+
+# Test files should be named *.exs
+test/pleroma/**/*.ex
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
@@ -1,14 +1,15 @@
-image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-25
+image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.14.5-otp-25
variables: &global_variables
# Only used for the release
- ELIXIR_VER: 1.13.4
+ ELIXIR_VER: 1.14.5
POSTGRES_DB: pleroma_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DB_HOST: postgres
DB_PORT: "5432"
MIX_ENV: test
+ GIT_STRATEGY: fetch
workflow:
rules:
@@ -70,7 +71,7 @@ check-changelog:
tags:
- amd64
-build-1.13.4-otp-25:
+build-1.14.5-otp-25:
extends:
- .build_changes_policy
- .using-ci-base
@@ -118,7 +119,7 @@ benchmark:
- mix ecto.migrate
- mix pleroma.load_testing
-unit-testing-1.13.4-otp-25:
+unit-testing-1.14.5-otp-25:
extends:
- .build_changes_policy
- .using-ci-base
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -4,6 +4,65 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## 2.8.0
+
+### Changed
+- Metadata: Do not include .atom feed links for remote accounts
+- Bumped `fast_html` to v2.3.0, which notably allows to use system-installed lexbor with passing `WITH_SYSTEM_LEXBOR=1` environment variable at build-time
+- Dedupe upload filter now uses a three-level sharding directory structure
+- Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe`
+- Restrict incoming activities from unknown actors to a subset that does not imply a previous relationship and early rejection of unrecognized activity types.
+- Elixir 1.14 and Erlang/OTP 23 is now the minimum supported release
+- Support `id` param in `GET /api/v1/statuses`
+- LDAP authentication has been refactored to operate as a GenServer process which will maintain an active connection to the LDAP server.
+- Fix 'Setting a marker should mark notifications as read'
+- Adjust more Oban workers to enforce unique job constraints.
+- Oban updated to 2.18.3
+- Publisher behavior improvement when snoozing Oban jobs due to Gun connection pool contention.
+- Poll results refreshing is handled asynchronously and will not attempt to keep fetching updates to a closed poll.
+- Tuning for release builds to lower CPU usage.
+- Rich Media preview fetching will skip making an HTTP HEAD request to check a URL for allowed content type and length if the Tesla adapter is Gun or Finch
+- Fix nonexisting user will not generate metadata for search engine opt-out
+- Update Oban to 2.18
+- Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues.
+
+### Added
+- Add metadata provider for ActivityPub alternate links
+- Added support for argon2 passwords and their conversion for migration from Akkoma fork to upstream.
+- Respect :restrict_unauthenticated for hashtag rss/atom feeds
+- LDAP configuration now permits overriding the CA root certificate file for TLS validation.
+- LDAP now supports users changing their passwords
+- Include list id in StatusView
+- Added MRF.FODirectReply which changes replies to followers-only posts to be direct.
+- Add `id_filter` to MRF to filter URLs and their domain prior to fetching
+- Added MRF.QuietReply which prevents replies to public posts from being published to the timelines
+- Add `group_key` to notifications
+- Allow providing avatar/header descriptions
+- Added RemoteReportPolicy from Rebased for handling bogus federated reports
+- scrubbers/default: Allow "mention hashtag" classes used by Mastodon
+- Added dependencies for Swoosh's Mua mail adapter
+- Include session scopes in TokenView
+
+### Fixed
+- Verify a local Update sent through AP C2S so users can only update their own objects
+- Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them.
+- Fix incoming Block activities being rejected
+- STARTTLS certificate and hostname verification for LDAP authentication
+- LDAPS connections (implicit TLS) are now supported.
+- Fix /api/v2/media returning the wrong status code (202) for media processed synchronously
+- Miscellaneous fixes for Meilisearch support
+- Fix pleroma_ctl mix task calls sometimes not being found
+- Add a rate limiter to the OAuth App creation endpoint and ensure registered apps are assigned to users.
+- ReceiverWorker will cancel processing jobs instead of retrying if the user cannot be fetched due to 403, 404, or 410 errors or if the account is disabled locally.
+- Address case where instance reachability status couldn't be updated
+- Remote Fetcher Worker recognizes more permanent failure errors
+- StreamerView: Do not leak follows count if hidden
+- Imports of blocks, mutes, and follows would retry repeatedly due to incorrect error handling and all work executed in a single job
+- Make vapid_config return empty array, fixing preloading for instances without push notifications configured
+
+### Removed
+- Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0)
+
## 2.7.1
### Changed
diff --git a/Dockerfile b/Dockerfile
@@ -1,7 +1,8 @@
+# https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir
-ARG ELIXIR_VER=1.13.4
-ARG ERLANG_VER=24.3.4.15
-ARG ALPINE_VER=3.17.5
+ARG ELIXIR_VER=1.14.5
+ARG ERLANG_VER=25.3.2.14
+ARG ALPINE_VER=3.17.9
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
diff --git a/ci/elixir-1.12/Dockerfile b/ci/elixir-1.12/Dockerfile
@@ -1,8 +0,0 @@
-FROM elixir:1.12.3
-
-# Single RUN statement, otherwise intermediate images are created
-# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
-RUN apt-get update &&\
- apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
- mix local.hex --force &&\
- mix local.rebar --force
diff --git a/ci/elixir-1.12/build_and_push.sh b/ci/elixir-1.12/build_and_push.sh
@@ -1 +0,0 @@
-docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.12 --push .
diff --git a/ci/elixir-1.13.4-otp-25/Dockerfile b/ci/elixir-1.13.4-otp-25/Dockerfile
@@ -1,8 +0,0 @@
-FROM elixir:1.13.4-otp-25
-
-# Single RUN statement, otherwise intermediate images are created
-# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
-RUN apt-get update &&\
- apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
- mix local.hex --force &&\
- mix local.rebar --force
diff --git a/ci/elixir-1.13.4-otp-25/build_and_push.sh b/ci/elixir-1.13.4-otp-25/build_and_push.sh
@@ -1 +0,0 @@
-docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-25 --push .
diff --git a/ci/elixir-1.14.5-otp-25/Dockerfile b/ci/elixir-1.14.5-otp-25/Dockerfile
@@ -0,0 +1,8 @@
+FROM elixir:1.14.5-otp-25
+
+# Single RUN statement, otherwise intermediate images are created
+# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
+RUN apt-get update &&\
+ apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
+ mix local.hex --force &&\
+ mix local.rebar --force
diff --git a/ci/elixir-1.14.5-otp-25/build_and_push.sh b/ci/elixir-1.14.5-otp-25/build_and_push.sh
@@ -0,0 +1 @@
+docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.14.5-otp-25 --push .
diff --git a/config/config.exs b/config/config.exs
@@ -344,7 +344,7 @@ config :pleroma, :manifest,
icons: [
%{
src: "/static/logo.svg",
- sizes: "144x144",
+ sizes: "512x512",
purpose: "any",
type: "image/svg+xml"
}
@@ -434,6 +434,11 @@ config :pleroma, :mrf_follow_bot, follower_nickname: nil
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
+config :pleroma, :mrf_remote_report,
+ reject_all: false,
+ reject_anonymous: true,
+ reject_empty_message: true
+
config :pleroma, :mrf_force_mention,
mention_parent: true,
mention_quoted: true
@@ -597,14 +602,8 @@ config :pleroma, Oban,
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
crontab: [
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
- {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
- ]
-
-config :pleroma, :workers,
- retries: [
- federator_incoming: 5,
- federator_outgoing: 5,
- search_indexing: 2
+ {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
+ {"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
]
config :pleroma, Pleroma.Formatter,
@@ -618,14 +617,17 @@ config :pleroma, Pleroma.Formatter,
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
- host: System.get_env("LDAP_HOST") || "localhost",
- port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
+ host: System.get_env("LDAP_HOST", "localhost"),
+ port: String.to_integer(System.get_env("LDAP_PORT", "389")),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
- base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
- uid: System.get_env("LDAP_UID") || "cn"
+ base: System.get_env("LDAP_BASE", "dc=example,dc=com"),
+ uid: System.get_env("LDAP_UID", "cn"),
+ # defaults to CAStore's Mozilla roots
+ cacertfile: System.get_env("LDAP_CACERTFILE", nil),
+ mail: System.get_env("LDAP_MAIL", "mail")
oauth_consumer_strategies =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
@@ -718,6 +720,7 @@ config :pleroma, :rate_limit,
timeline: {500, 3},
search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25},
+ oauth_app_creation: {900_000, 5},
relations_actions: {10_000, 10},
relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15},
diff --git a/config/description.exs b/config/description.exs
@@ -2015,23 +2015,6 @@ config :pleroma, :config_description, [
},
%{
group: :pleroma,
- key: :workers,
- type: :group,
- description: "Includes custom worker options not interpretable directly by `Oban`",
- children: [
- %{
- key: :retries,
- type: {:keyword, :integer},
- description: "Max retry attempts for failed jobs, per `Oban` queue",
- suggestions: [
- federator_incoming: 5,
- federator_outgoing: 5
- ]
- }
- ]
- },
- %{
- group: :pleroma,
key: Pleroma.Web.Metadata,
type: :group,
description: "Metadata-related settings",
@@ -2258,15 +2241,9 @@ config :pleroma, :config_description, [
label: "SSL options",
type: :keyword,
description: "Additional SSL options",
- suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
+ suggestions: [verify: :verify_peer],
children: [
%{
- key: :cacertfile,
- type: :string,
- description: "Path to file with PEM encoded cacerts",
- suggestions: ["path/to/file/with/PEM/cacerts"]
- },
- %{
key: :verify,
type: :atom,
description: "Type of cert verification",
@@ -2285,15 +2262,9 @@ config :pleroma, :config_description, [
label: "TLS options",
type: :keyword,
description: "Additional TLS options",
- suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
+ suggestions: [verify: :verify_peer],
children: [
%{
- key: :cacertfile,
- type: :string,
- description: "Path to file with PEM encoded cacerts",
- suggestions: ["path/to/file/with/PEM/cacerts"]
- },
- %{
key: :verify,
type: :atom,
description: "Type of cert verification",
@@ -2309,11 +2280,25 @@ config :pleroma, :config_description, [
},
%{
key: :uid,
- label: "UID",
+ label: "UID Attribute",
type: :string,
description:
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
suggestions: ["cn"]
+ },
+ %{
+ key: :cacertfile,
+ label: "CACertfile",
+ type: :string,
+ description: "Path to CA certificate file"
+ },
+ %{
+ key: :mail,
+ label: "Mail Attribute",
+ type: :string,
+ description:
+ "LDAP attribute name to use as the email address when automatically registering the user on first login",
+ suggestions: ["mail"]
}
]
},
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
@@ -742,6 +742,21 @@ config :pleroma, Pleroma.Emails.Mailer,
auth: :always
```
+An example for Mua adapter:
+
+```elixir
+config :pleroma, Pleroma.Emails.Mailer,
+ enabled: true,
+ adapter: Swoosh.Adapters.Mua,
+ relay: "mail.example.com",
+ port: 465,
+ auth: [
+ username: "YOUR_USERNAME@domain.tld",
+ password: "YOUR_SMTP_PASSWORD"
+ ],
+ protocol: :ssl
+```
+
### :email_notifications
Email notifications settings.
@@ -968,12 +983,13 @@ Pleroma account will be created with the same name as the LDAP user name.
* `enabled`: enables LDAP authentication
* `host`: LDAP server hostname
* `port`: LDAP port, e.g. 389 or 636
-* `ssl`: true to use SSL, usually implies the port 636
+* `ssl`: true to use implicit SSL/TLS, usually port 636
* `sslopts`: additional SSL options
-* `tls`: true to start TLS, usually implies the port 389
+* `tls`: true to use explicit TLS (STARTTLS), usually port 389
* `tlsopts`: additional TLS options
* `base`: LDAP base, e.g. "dc=example,dc=com"
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
+* `cacertfile`: Path to alternate CA root certificates file
Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
OpenLDAP server the value may be `uid: "uid"`.
diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md
@@ -433,7 +433,7 @@ Response:
* On success: URL of the unfollowed relay
```json
-{"https://example.com/relay"}
+"https://example.com/relay"
```
## `POST /api/v1/pleroma/admin/users/invite_token`
@@ -1193,20 +1193,23 @@ Loads json generated from `config/descriptions.exs`.
- Response:
```json
-[
- {
- "id": 1234,
- "data": {
- "actor": {
- "id": 1,
- "nickname": "lain"
+{
+ "items": [
+ {
+ "id": 1234,
+ "data": {
+ "actor": {
+ "id": 1,
+ "nickname": "lain"
+ },
+ "action": "relay_follow"
},
- "action": "relay_follow"
- },
- "time": 1502812026, // timestamp
- "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message
- }
-]
+ "time": 1502812026, // timestamp
+ "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message
+ }
+ ],
+ "total": 1
+}
```
## `POST /api/v1/pleroma/admin/reload_emoji`
diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
@@ -42,6 +42,7 @@ Has these additional fields under the `pleroma` object:
- `quotes_count`: the count of status quotes.
- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen.
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
+- `list_id`: the ID of the list the post is addressed to (if any, only returned to author).
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
@@ -103,7 +104,7 @@ Has these additional fields under the `pleroma` object:
- `background_image`: nullable URL string, background image of the user
- `tags`: Lists an array of tags for the user
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
-- `is_moderator`: boolean, nullable, true if user is a moderator
+- `is_moderator`: boolean, nullable, true if user is a moderator
- `is_admin`: boolean, nullable, true if user is an admin
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
@@ -120,6 +121,8 @@ Has these additional fields under the `pleroma` object:
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
- `favicon`: nullable URL string, Favicon image of the user's instance
+- `avatar_description`: string, image description for user avatar, defaults to empty string
+- `header_description`: string, image description for user banner, defaults to empty string
### Source
@@ -255,6 +258,8 @@ Additional parameters can be added to the JSON body/Form data:
- `actor_type` - the type of this account.
- `accepts_chat_messages` - if false, this account will reject all chat messages.
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
+- `avatar_description` - image description for user avatar
+- `header_description` - image description for user banner
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
@@ -510,12 +515,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat
- `GET /api/v1/trends`: Returns an empty array, `[]`
-### Identity proofs
-
-*Added in Mastodon 2.8.0*
-
-- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
-
### Featured tags
*Added in Mastodon 3.0.0*
diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md
@@ -145,6 +145,9 @@ See [Admin-API](admin_api.md)
## `/api/v1/pleroma/accounts/:id/subscribe`
### Subscribe to receive notifications for all statuses posted by a user
+
+Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead.
+
* Method `POST`
* Authentication: required
* Params:
@@ -171,6 +174,9 @@ See [Admin-API](admin_api.md)
## `/api/v1/pleroma/accounts/:id/unsubscribe`
### Unsubscribe to stop receiving notifications from user statuses
+
+Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead.
+
* Method `POST`
* Authentication: required
* Params:
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
@@ -69,12 +69,18 @@ cd /opt/pleroma
sudo -Hu pleroma mix deps.get
```
-* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
+* Generate the configuration:
+
+```shell
+sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
+```
+
+* During this process:
* Answer with `yes` if it asks you to install `rebar3`.
* This may take some time, because parts of pleroma get compiled first.
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
-* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
+* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for production instances, `dev.secret.exs` for development instances):
```shell
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
@@ -14,7 +14,7 @@ Note: This article is potentially outdated because at this time we may not have
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
- `postgresql-contrib` 11.0以上 (同上)
-- Elixir 1.13 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
+- Elixir 1.14 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
- `erlang-dev`
- `erlang-nox`
- `git`
diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md
@@ -31,7 +31,7 @@ Setup the required services to automatically start at boot, using `sysrc(8)`.
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
```shell
-# pkg install imagemagick ffmpeg p5-Image-ExifTool
+# pkg install imagemagick ffmpeg p5-Image-ExifTool vips
```
## Configuring Pleroma
diff --git a/docs/installation/generic_dependencies.include b/docs/installation/generic_dependencies.include
@@ -1,8 +1,8 @@
## Required dependencies
* PostgreSQL >=11.0
-* Elixir >=1.13.0 <1.17
-* Erlang OTP >=22.2.0 (supported: <27)
+* Elixir >=1.14.0 <1.17
+* Erlang OTP >=23.0.0 (supported: <27)
* git
* file / libmagic
* gcc or clang
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
@@ -12,7 +12,7 @@ For any additional information regarding commands and configuration files mentio
To install them, run the following command (with doas or as root):
```
-pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick
+pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick libvips
```
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md
@@ -18,7 +18,7 @@ Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
Asenna tarvittava ohjelmisto:
-`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick`
+`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick libvips`
#### Optional software
diff --git a/installation/openldap/pw_self_service.ldif b/installation/openldap/pw_self_service.ldif
@@ -0,0 +1,7 @@
+dn: olcDatabase={1}mdb,cn=config
+changetype: modify
+add: olcAccess
+olcAccess: {1}to attrs=userPassword
+ by self write
+ by anonymous auth
+ by * none
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
@@ -295,10 +295,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|> DateTime.from_naive!("Etc/UTC")
|> Timex.shift(days: days)
- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
- activity_id: activity.id,
- expires_at: expires_at
- })
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: activity.id
+ },
+ scheduled_at: expires_at
+ )
end)
end)
|> Stream.run()
diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex
@@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
import Ecto.Query
import Pleroma.Search.Meilisearch,
- only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete: 1]
+ only: [meili_put: 2, meili_get: 1, meili_delete: 1]
def run(["index"]) do
start_pleroma()
@@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
end
{:ok, _} =
- meili_post(
+ meili_put(
"/indexes/objects/settings/ranking-rules",
[
"published:desc",
@@ -42,7 +42,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
)
{:ok, _} =
- meili_post(
+ meili_put(
"/indexes/objects/settings/searchable-attributes",
[
"content"
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
@@ -94,6 +94,7 @@ defmodule Pleroma.Application do
children =
[
Pleroma.PromEx,
+ Pleroma.LDAP,
Pleroma.Repo,
Config.TransferTask,
Pleroma.Emoji,
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
@@ -22,7 +22,8 @@ defmodule Pleroma.Config.TransferTask do
{:pleroma, :markup},
{:pleroma, :streamer},
{:pleroma, :pools},
- {:pleroma, :connections_pool}
+ {:pleroma, :connections_pool},
+ {:pleroma, :ldap}
]
defp reboot_time_subkeys,
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
@@ -85,6 +85,41 @@ defmodule Pleroma.Constants do
]
)
+ const(activity_types,
+ do: [
+ "Block",
+ "Create",
+ "Update",
+ "Delete",
+ "Follow",
+ "Accept",
+ "Reject",
+ "Add",
+ "Remove",
+ "Like",
+ "Announce",
+ "Undo",
+ "Flag",
+ "EmojiReact"
+ ]
+ )
+
+ const(allowed_activity_types_from_strangers,
+ do: [
+ "Block",
+ "Create",
+ "Flag",
+ "Follow",
+ "Like",
+ "EmojiReact",
+ "Announce"
+ ]
+ )
+
+ const(object_types,
+ do: ~w[Event Question Answer Audio Video Image Article Note Page ChatMessage]
+ )
+
# basic regex, just there to weed out potential mistakes
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
const(mime_regex,
diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex
@@ -25,7 +25,8 @@ defmodule Pleroma.Emails.Mailer do
|> :erlang.term_to_binary()
|> Base.encode64()
- MailerWorker.enqueue("email", %{"encoded_email" => encoded_email, "config" => config})
+ MailerWorker.new(%{"op" => "email", "encoded_email" => encoded_email, "config" => config})
+ |> Oban.insert()
end
@doc "callback to perform send email from queue"
diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex
@@ -133,10 +133,13 @@ defmodule Pleroma.Filter do
defp maybe_add_expires_at(changeset, _), do: changeset
defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do
- Pleroma.Workers.PurgeExpiredFilter.enqueue(%{
- filter_id: filter.id,
- expires_at: DateTime.from_naive!(expires_at, "Etc/UTC")
- })
+ Pleroma.Workers.PurgeExpiredFilter.new(
+ %{
+ filter_id: filter.id
+ },
+ scheduled_at: DateTime.from_naive!(expires_at, "Etc/UTC")
+ )
+ |> Oban.insert()
end
defp maybe_add_expiration_job(_), do: {:ok, nil}
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
@@ -74,11 +74,14 @@ defmodule Pleroma.Frontend do
new_file_path = Path.join(dest, path)
- new_file_path
+ path
|> Path.dirname()
+ |> then(&Path.join(dest, &1))
|> File.mkdir_p!()
- File.write!(new_file_path, data)
+ if not File.dir?(new_file_path) do
+ File.write!(new_file_path, data)
+ end
end)
end
end
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
@@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
case adapter() do
Tesla.Adapter.Gun -> AdapterHelper.Gun
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
+ {Tesla.Adapter.Finch, _} -> AdapterHelper.Finch
_ -> AdapterHelper.Default
end
end
@@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do
host_charlist
end
end
+
+ @spec can_stream? :: bool()
+ def can_stream? do
+ case Application.get_env(:tesla, :adapter) do
+ Tesla.Adapter.Gun -> true
+ {Tesla.Adapter.Finch, _} -> true
+ _ -> false
+ end
+ end
end
diff --git a/lib/pleroma/http/adapter_helper/finch.ex b/lib/pleroma/http/adapter_helper/finch.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.Finch do
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ alias Pleroma.Config
+ alias Pleroma.HTTP.AdapterHelper
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(incoming_opts \\ [], %URI{} = _uri) do
+ proxy =
+ [:http, :proxy_url]
+ |> Config.get()
+ |> AdapterHelper.format_proxy()
+
+ config_opts = Config.get([:http, :adapter], [])
+
+ config_opts
+ |> Keyword.merge(incoming_opts)
+ |> AdapterHelper.maybe_add_proxy(proxy)
+ |> maybe_stream()
+ end
+
+ # Finch uses [response: :stream]
+ defp maybe_stream(opts) do
+ case Keyword.pop(opts, :stream, nil) do
+ {true, opts} -> Keyword.put(opts, :response, :stream)
+ {_, opts} -> opts
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
@@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|> AdapterHelper.maybe_add_proxy(proxy)
|> Keyword.merge(incoming_opts)
|> put_timeout()
+ |> maybe_stream()
end
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
@@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
Keyword.put(opts, :timeout, recv_timeout)
end
+ # Gun uses [body_as: :stream]
+ defp maybe_stream(opts) do
+ case Keyword.pop(opts, :stream, nil) do
+ {true, opts} -> Keyword.put(opts, :body_as, :stream)
+ {_, opts} -> opts
+ end
+ end
+
@spec pool_timeout(pool()) :: non_neg_integer()
def pool_timeout(pool) do
default = Config.get([:pools, :default, :recv_timeout], 5_000)
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
@@ -297,7 +297,8 @@ defmodule Pleroma.Instances.Instance do
all of those users' activities and notifications.
"""
def delete_users_and_activities(host) when is_binary(host) do
- DeleteWorker.enqueue("delete_instance", %{"host" => host})
+ DeleteWorker.new(%{"op" => "delete_instance", "host" => host})
+ |> Oban.insert()
end
def perform(:delete_instance, host) when is_binary(host) do
diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex
@@ -0,0 +1,271 @@
+defmodule Pleroma.LDAP do
+ use GenServer
+
+ require Logger
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ import Pleroma.Web.Auth.Helpers, only: [fetch_user: 1]
+
+ @connection_timeout 2_000
+ @search_timeout 2_000
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ def bind_user(name, password) do
+ GenServer.call(__MODULE__, {:bind_user, name, password})
+ end
+
+ def change_password(name, password, new_password) do
+ GenServer.call(__MODULE__, {:change_password, name, password, new_password})
+ end
+
+ @impl true
+ def init(state) do
+ case {Config.get(Pleroma.Web.Auth.Authenticator), Config.get([:ldap, :enabled])} do
+ {Pleroma.Web.Auth.LDAPAuthenticator, true} ->
+ {:ok, state, {:continue, :connect}}
+
+ {Pleroma.Web.Auth.LDAPAuthenticator, false} ->
+ Logger.error(
+ "LDAP Authenticator enabled but :pleroma, :ldap is not enabled. Auth will not work."
+ )
+
+ {:ok, state}
+
+ {_, true} ->
+ Logger.warning(
+ ":pleroma, :ldap is enabled but Pleroma.Web.Authenticator is not set to the LDAPAuthenticator. LDAP will not be used."
+ )
+
+ {:ok, state}
+
+ _ ->
+ {:ok, state}
+ end
+ end
+
+ @impl true
+ def handle_continue(:connect, _state), do: do_handle_connect()
+
+ @impl true
+ def handle_info(:connect, _state), do: do_handle_connect()
+
+ def handle_info({:bind_after_reconnect, name, password, from}, state) do
+ result = do_bind_user(state[:handle], name, password)
+
+ GenServer.reply(from, result)
+
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_call({:bind_user, name, password}, from, state) do
+ case do_bind_user(state[:handle], name, password) do
+ :needs_reconnect ->
+ Process.send(self(), {:bind_after_reconnect, name, password, from}, [])
+ {:noreply, state, {:continue, :connect}}
+
+ result ->
+ {:reply, result, state, :hibernate}
+ end
+ end
+
+ def handle_call({:change_password, name, password, new_password}, _from, state) do
+ result = change_password(state[:handle], name, password, new_password)
+
+ {:reply, result, state, :hibernate}
+ end
+
+ @impl true
+ def terminate(_, state) do
+ handle = Keyword.get(state, :handle)
+
+ if not is_nil(handle) do
+ :eldap.close(handle)
+ end
+
+ :ok
+ end
+
+ defp do_handle_connect do
+ state =
+ case connect() do
+ {:ok, handle} ->
+ :eldap.controlling_process(handle, self())
+ Process.link(handle)
+ [handle: handle]
+
+ _ ->
+ Logger.error("Failed to connect to LDAP. Retrying in 5000ms")
+ Process.send_after(self(), :connect, 5_000)
+ []
+ end
+
+ {:noreply, state}
+ end
+
+ defp connect do
+ ldap = Config.get(:ldap, [])
+ host = Keyword.get(ldap, :host, "localhost")
+ port = Keyword.get(ldap, :port, 389)
+ ssl = Keyword.get(ldap, :ssl, false)
+ tls = Keyword.get(ldap, :tls, false)
+ cacertfile = Keyword.get(ldap, :cacertfile) || CAStore.file_path()
+
+ if ssl, do: Application.ensure_all_started(:ssl)
+
+ default_secure_opts = [
+ verify: :verify_peer,
+ cacerts: decode_certfile(cacertfile),
+ customize_hostname_check: [
+ fqdn_fun: fn _ -> to_charlist(host) end
+ ]
+ ]
+
+ sslopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :sslopts, []))
+ tlsopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :tlsopts, []))
+
+ default_options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}]
+
+ # :sslopts can only be included in :eldap.open/2 when {ssl: true}
+ # or the connection will fail
+ options =
+ if ssl do
+ default_options ++ [{:sslopts, sslopts}]
+ else
+ default_options
+ end
+
+ case :eldap.open([to_charlist(host)], options) do
+ {:ok, handle} ->
+ try do
+ cond do
+ tls ->
+ case :eldap.start_tls(
+ handle,
+ tlsopts,
+ @connection_timeout
+ ) do
+ :ok ->
+ {:ok, handle}
+
+ error ->
+ Logger.error("Could not start TLS: #{inspect(error)}")
+ :eldap.close(handle)
+ end
+
+ true ->
+ {:ok, handle}
+ end
+ after
+ :ok
+ end
+
+ {:error, error} ->
+ Logger.error("Could not open LDAP connection: #{inspect(error)}")
+ {:error, {:ldap_connection_error, error}}
+ end
+ end
+
+ defp do_bind_user(handle, name, password) do
+ dn = make_dn(name)
+
+ case :eldap.simple_bind(handle, dn, password) do
+ :ok ->
+ case fetch_user(name) do
+ %User{} = user ->
+ user
+
+ _ ->
+ register_user(handle, ldap_base(), ldap_uid(), name)
+ end
+
+ # eldap does not inform us of socket closure
+ # until it is used
+ {:error, {:gen_tcp_error, :closed}} ->
+ :eldap.close(handle)
+ :needs_reconnect
+
+ {:error, error} = e ->
+ Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
+ e
+ end
+ end
+
+ defp register_user(handle, base, uid, name) do
+ case :eldap.search(handle, [
+ {:base, to_charlist(base)},
+ {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
+ {:scope, :eldap.wholeSubtree()},
+ {:timeout, @search_timeout}
+ ]) do
+ # The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
+ # https://github.com/erlang/otp/pull/5538
+ {:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
+ try_register(name, attributes)
+
+ {:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
+ try_register(name, attributes)
+
+ error ->
+ Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
+ {:error, {:ldap_search_error, error}}
+ end
+ end
+
+ defp try_register(name, attributes) do
+ mail_attribute = Config.get([:ldap, :mail])
+
+ params = %{
+ name: name,
+ nickname: name,
+ password: nil
+ }
+
+ params =
+ case List.keyfind(attributes, to_charlist(mail_attribute), 0) do
+ {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
+ _ -> params
+ end
+
+ changeset = User.register_changeset_ldap(%User{}, params)
+
+ case User.register(changeset) do
+ {:ok, user} -> user
+ error -> error
+ end
+ end
+
+ defp change_password(handle, name, password, new_password) do
+ dn = make_dn(name)
+
+ with :ok <- :eldap.simple_bind(handle, dn, password) do
+ :eldap.modify_password(handle, dn, to_charlist(new_password), to_charlist(password))
+ end
+ end
+
+ defp decode_certfile(file) do
+ with {:ok, data} <- File.read(file) do
+ data
+ |> :public_key.pem_decode()
+ |> Enum.map(fn {_, b, _} -> b end)
+ else
+ _ ->
+ Logger.error("Unable to read certfile: #{file}")
+ []
+ end
+ end
+
+ defp ldap_uid, do: to_charlist(Config.get([:ldap, :uid], "cn"))
+ defp ldap_base, do: to_charlist(Config.get([:ldap, :base]))
+
+ defp make_dn(name) do
+ uid = ldap_uid()
+ base = ldap_base()
+ ~c"#{uid}=#{name},#{base}"
+ end
+end
diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex
@@ -20,15 +20,13 @@ defmodule Pleroma.Maps do
end
def filter_empty_values(data) do
- # TODO: Change to Map.filter in Elixir 1.13+
data
- |> Enum.filter(fn
+ |> Map.filter(fn
{_k, nil} -> false
{_k, ""} -> false
{_k, []} -> false
{_k, %{} = v} -> Map.keys(v) != []
{_k, _v} -> true
end)
- |> Map.new()
end
end
diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex
@@ -52,11 +52,14 @@ defmodule Pleroma.MFA.Token do
@spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def create(user, authorization \\ nil) do
with {:ok, token} <- do_create(user, authorization) do
- Pleroma.Workers.PurgeExpiredToken.enqueue(%{
- token_id: token.id,
- valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
- mod: __MODULE__
- })
+ Pleroma.Workers.PurgeExpiredToken.new(
+ %{
+ token_id: token.id,
+ mod: __MODULE__
+ },
+ scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC")
+ )
+ |> Oban.insert()
{:ok, token}
end
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
@@ -99,27 +99,6 @@ defmodule Pleroma.Object do
def get_by_id(nil), do: nil
def get_by_id(id), do: Repo.get(Object, id)
- @spec get_by_id_and_maybe_refetch(integer(), list()) :: Object.t() | nil
- def get_by_id_and_maybe_refetch(id, opts \\ []) do
- with %Object{updated_at: updated_at} = object <- get_by_id(id) do
- if opts[:interval] &&
- NaiveDateTime.diff(NaiveDateTime.utc_now(), updated_at) > opts[:interval] do
- case Fetcher.refetch_object(object) do
- {:ok, %Object{} = object} ->
- object
-
- e ->
- Logger.error("Couldn't refresh #{object.data["id"]}:\n#{inspect(e)}")
- object
- end
- else
- object
- end
- else
- nil -> nil
- end
- end
-
def get_by_ap_id(nil), do: nil
def get_by_ap_id(ap_id) do
@@ -255,7 +234,8 @@ defmodule Pleroma.Object do
@spec cleanup_attachments(boolean(), Object.t()) ::
{:ok, Oban.Job.t() | nil}
def cleanup_attachments(true, %Object{} = object) do
- AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{"object" => object})
+ AttachmentsCleanupWorker.new(%{"op" => "cleanup_attachments", "object" => object})
+ |> Oban.insert()
end
def cleanup_attachments(_, _), do: {:ok, nil}
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
@@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do
end
end
+ @typep fetcher_errors ::
+ :error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier
+
# Note: will create a Create activity, which we need internally at the moment.
- @spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()}
+ @spec fetch_object_from_id(String.t(), list()) ::
+ {:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors()
def fetch_object_from_id(id, options \\ []) do
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
@@ -73,50 +77,22 @@ defmodule Pleroma.Object.Fetcher do
{:object, data, Object.normalize(activity, fetch: false)} do
{:ok, object}
else
- {:allowed_depth, false} = e ->
- log_fetch_error(id, e)
- {:error, :allowed_depth}
-
- {:containment, reason} = e ->
- log_fetch_error(id, e)
- {:error, reason}
-
- {:transmogrifier, {:error, {:reject, reason}}} = e ->
- log_fetch_error(id, e)
- {:reject, reason}
-
- {:transmogrifier, {:reject, reason}} = e ->
- log_fetch_error(id, e)
- {:reject, reason}
-
- {:transmogrifier, reason} = e ->
- log_fetch_error(id, e)
- {:error, reason}
-
- {:object, data, nil} ->
- reinject_object(%Object{}, data)
-
{:normalize, object = %Object{}} ->
{:ok, object}
{:fetch_object, %Object{} = object} ->
{:ok, object}
- {:fetch, {:error, reason}} = e ->
- log_fetch_error(id, e)
- {:error, reason}
+ {:object, data, nil} ->
+ reinject_object(%Object{}, data)
e ->
- log_fetch_error(id, e)
- {:error, e}
+ Logger.metadata(object: id)
+ Logger.error("Object rejected while fetching #{id} #{inspect(e)}")
+ e
end
end
- defp log_fetch_error(id, error) do
- Logger.metadata(object: id)
- Logger.error("Object rejected while fetching #{id} #{inspect(error)}")
- end
-
defp prepare_activity_params(data) do
%{
"type" => "Create",
@@ -169,6 +145,7 @@ defmodule Pleroma.Object.Fetcher do
Logger.debug("Fetching object #{id} via AP")
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
+ {_, true} <- {:mrf, MRF.id_filter(id)},
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
@@ -184,6 +161,9 @@ defmodule Pleroma.Object.Fetcher do
{:error, e} ->
{:error, e}
+ {:mrf, false} ->
+ {:error, {:reject, "Filtered by id"}}
+
e ->
{:error, e}
end
diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex
@@ -16,17 +16,24 @@ defmodule Pleroma.ReleaseTasks do
end
end
+ def find_module(task) do
+ module_name =
+ task
+ |> String.split(".")
+ |> Enum.map(&String.capitalize/1)
+ |> then(fn x -> [Mix, Tasks, Pleroma] ++ x end)
+ |> Module.concat()
+
+ case Code.ensure_loaded(module_name) do
+ {:module, _} -> module_name
+ _ -> nil
+ end
+ end
+
defp mix_task(task, args) do
Application.load(:pleroma)
- {:ok, modules} = :application.get_key(:pleroma, :modules)
-
- module =
- Enum.find(modules, fn module ->
- module = Module.split(module)
- match?(["Mix", "Tasks", "Pleroma" | _], module) and
- String.downcase(List.last(module)) == task
- end)
+ module = find_module(task)
if module do
module.run(args)
diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex
@@ -2,11 +2,13 @@ defmodule Pleroma.Search do
alias Pleroma.Workers.SearchIndexingWorker
def add_to_index(%Pleroma.Activity{id: activity_id}) do
- SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id})
+ SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id})
+ |> Oban.insert()
end
def remove_from_index(%Pleroma.Object{id: object_id}) do
- SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id})
+ SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id})
+ |> Oban.insert()
end
def search(query, options) do
diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex
@@ -122,6 +122,7 @@ defmodule Pleroma.Search.Meilisearch do
# Only index public or unlisted Notes
if not is_nil(object) and object.data["type"] == "Note" and
not is_nil(object.data["content"]) and
+ not is_nil(object.data["published"]) and
(Pleroma.Constants.as_public() in object.data["to"] or
Pleroma.Constants.as_public() in object.data["cc"]) and
object.data["content"] not in ["", "."] do
diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex
@@ -17,8 +17,16 @@ defmodule Pleroma.Upload.Filter.Dedupe do
|> Base.encode16(case: :lower)
filename = shasum <> "." <> extension
- {:ok, :filtered, %Upload{upload | id: shasum, path: filename}}
+
+ {:ok, :filtered, %Upload{upload | id: shasum, path: shard_path(filename)}}
end
def filter(_), do: {:ok, :noop}
+
+ @spec shard_path(String.t()) :: String.t()
+ def shard_path(
+ <<a::binary-size(2), b::binary-size(2), c::binary-size(2), _::binary>> = filename
+ ) do
+ Path.join([a, b, c, filename])
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
@@ -419,6 +419,11 @@ defmodule Pleroma.User do
end
end
+ def image_description(image, default \\ "")
+
+ def image_description(%{"name" => name}, _default), do: name
+ def image_description(_, default), do: default
+
# Should probably be renamed or removed
@spec ap_id(User.t()) :: String.t()
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
@@ -586,16 +591,26 @@ defmodule Pleroma.User do
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
+ |> validate_image_description(:avatar_description, params)
+ |> validate_image_description(:header_description, params)
|> put_fields()
|> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
- |> put_change_if_present(:avatar, &put_upload(&1, :avatar))
- |> put_change_if_present(:banner, &put_upload(&1, :banner))
+ |> put_change_if_present(
+ :avatar,
+ &put_upload(&1, :avatar, Map.get(params, :avatar_description))
+ )
+ |> put_change_if_present(
+ :banner,
+ &put_upload(&1, :banner, Map.get(params, :header_description))
+ )
|> put_change_if_present(:background, &put_upload(&1, :background))
|> put_change_if_present(
:pleroma_settings_store,
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
)
+ |> maybe_update_image_description(:avatar, Map.get(params, :avatar_description))
+ |> maybe_update_image_description(:banner, Map.get(params, :header_description))
|> validate_fields(false)
end
@@ -674,13 +689,41 @@ defmodule Pleroma.User do
end
end
- defp put_upload(value, type) do
+ defp put_upload(value, type, description \\ nil) do
with %Plug.Upload{} <- value,
- {:ok, object} <- ActivityPub.upload(value, type: type) do
+ {:ok, object} <- ActivityPub.upload(value, type: type, description: description) do
{:ok, object.data}
end
end
+ defp validate_image_description(changeset, key, params) do
+ description_limit = Config.get([:instance, :description_limit], 5_000)
+ description = Map.get(params, key)
+
+ if is_binary(description) and String.length(description) > description_limit do
+ changeset
+ |> add_error(key, "#{key} is too long")
+ else
+ changeset
+ end
+ end
+
+ defp maybe_update_image_description(changeset, image_field, description)
+ when is_binary(description) do
+ with {:image_missing, true} <- {:image_missing, not changed?(changeset, image_field)},
+ {:existing_image, %{"id" => id}} <-
+ {:existing_image, Map.get(changeset.data, image_field)},
+ {:object, %Object{} = object} <- {:object, Object.get_by_ap_id(id)},
+ {:ok, object} <- Object.update_data(object, %{"name" => description}) do
+ put_change(changeset, image_field, object.data)
+ else
+ {:description_too_long, true} -> {:error}
+ _ -> changeset
+ end
+ end
+
+ defp maybe_update_image_description(changeset, _, _), do: changeset
+
def update_as_admin_changeset(struct, params) do
struct
|> update_changeset(params)
@@ -738,7 +781,8 @@ defmodule Pleroma.User do
end
def force_password_reset_async(user) do
- BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
+ BackgroundWorker.new(%{"op" => "force_password_reset", "user_id" => user.id})
+ |> Oban.insert()
end
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@@ -1220,7 +1264,8 @@ defmodule Pleroma.User do
def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
if get_change(changeset, :raw_fields) do
- BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id})
+ BackgroundWorker.new(%{"op" => "verify_fields_links", "user_id" => user.id})
+ |> Oban.insert()
end
set_cache(user)
@@ -1591,11 +1636,11 @@ defmodule Pleroma.User do
)) ||
{:ok, nil} do
if duration > 0 do
- Pleroma.Workers.MuteExpireWorker.enqueue(
- "unmute_user",
- %{"muter_id" => muter.id, "mutee_id" => mutee.id},
+ Pleroma.Workers.MuteExpireWorker.new(
+ %{"op" => "unmute_user", "muter_id" => muter.id, "mutee_id" => mutee.id},
scheduled_at: expires_at
)
+ |> Oban.insert()
end
@cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}")
@@ -1838,7 +1883,8 @@ defmodule Pleroma.User do
defp maybe_filter_on_ap_id(query, _ap_ids), do: query
def set_activation_async(user, status \\ true) do
- BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status})
+ BackgroundWorker.new(%{"op" => "user_activation", "user_id" => user.id, "status" => status})
+ |> Oban.insert()
end
@spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@@ -1985,7 +2031,9 @@ defmodule Pleroma.User do
def delete(%User{} = user) do
# Purge the user immediately
purge(user)
- DeleteWorker.enqueue("delete_user", %{"user_id" => user.id})
+
+ DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id})
+ |> Oban.insert()
end
# *Actually* delete the user from the DB
diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex
@@ -92,9 +92,6 @@ defmodule Pleroma.User.Backup do
else
true ->
{:error, "Backup is missing id. Please insert it into the Repo first."}
-
- e ->
- {:error, e}
end
end
@@ -121,14 +118,13 @@ defmodule Pleroma.User.Backup do
end
defp permitted?(user) do
- with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)},
- days = Config.get([__MODULE__, :limit_days]),
- diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days),
- {_, true} <- {:diff, diff > days} do
- true
+ with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do
+ days = Config.get([__MODULE__, :limit_days])
+ diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days)
+
+ diff > days
else
{:last, nil} -> true
- {:diff, false} -> false
end
end
@@ -297,9 +293,6 @@ defmodule Pleroma.User.Backup do
)
acc
-
- _ ->
- acc
end
end)
diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex
@@ -5,81 +5,107 @@
defmodule Pleroma.User.Import do
use Ecto.Schema
+ alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Workers.BackgroundWorker
require Logger
- @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()}
- def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do
- Enum.map(
- identifiers,
- fn identifier ->
- with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier),
- {:ok, _} <- User.mute(user, muted_user) do
- muted_user
- else
- error -> handle_error(:mutes_import, identifier, error)
- end
- end
- )
+ @spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()}
+ def perform(:mute_import, %User{} = user, actor) do
+ with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
+ {_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},
+ {:ok, _} <- User.mute(user, muted_user) do
+ {:ok, muted_user}
+ else
+ {:existing_mute, true} -> :ok
+ error -> handle_error(:mutes_import, actor, error)
+ end
end
- def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do
- Enum.map(
- identifiers,
- fn identifier ->
- with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier),
- {:ok, _block} <- CommonAPI.block(blocked, blocker) do
- blocked
- else
- error -> handle_error(:blocks_import, identifier, error)
- end
- end
- )
+ def perform(:block_import, %User{} = user, actor) do
+ with {:ok, %User{} = blocked} <- User.get_or_fetch(actor),
+ {_, false} <- {:existing_block, User.blocks_user?(user, blocked)},
+ {:ok, _block} <- CommonAPI.block(blocked, user) do
+ {:ok, blocked}
+ else
+ {:existing_block, true} -> :ok
+ error -> handle_error(:blocks_import, actor, error)
+ end
end
- def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do
- Enum.map(
- identifiers,
- fn identifier ->
- with {:ok, %User{} = followed} <- User.get_or_fetch(identifier),
- {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed),
- {:ok, _, _, _} <- CommonAPI.follow(followed, follower) do
- followed
- else
- error -> handle_error(:follow_import, identifier, error)
- end
- end
- )
+ def perform(:follow_import, %User{} = user, actor) do
+ with {:ok, %User{} = followed} <- User.get_or_fetch(actor),
+ {_, false} <- {:existing_follow, User.following?(user, followed)},
+ {:ok, user, followed} <- User.maybe_direct_follow(user, followed),
+ {:ok, _, _, _} <- CommonAPI.follow(followed, user) do
+ {:ok, followed}
+ else
+ {:existing_follow, true} -> :ok
+ error -> handle_error(:follow_import, actor, error)
+ end
end
- def perform(_, _, _), do: :ok
-
defp handle_error(op, user_id, error) do
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
- error
+ {:error, error}
end
- def blocks_import(%User{} = blocker, [_ | _] = identifiers) do
- BackgroundWorker.enqueue(
- "blocks_import",
- %{"user_id" => blocker.id, "identifiers" => identifiers}
- )
+ def blocks_import(%User{} = user, [_ | _] = actors) do
+ jobs =
+ Repo.checkout(fn ->
+ Enum.reduce(actors, [], fn actor, acc ->
+ {:ok, job} =
+ BackgroundWorker.new(%{
+ "op" => "block_import",
+ "user_id" => user.id,
+ "actor" => actor
+ })
+ |> Oban.insert()
+
+ acc ++ [job]
+ end)
+ end)
+
+ {:ok, jobs}
end
- def follow_import(%User{} = follower, [_ | _] = identifiers) do
- BackgroundWorker.enqueue(
- "follow_import",
- %{"user_id" => follower.id, "identifiers" => identifiers}
- )
+ def follows_import(%User{} = user, [_ | _] = actors) do
+ jobs =
+ Repo.checkout(fn ->
+ Enum.reduce(actors, [], fn actor, acc ->
+ {:ok, job} =
+ BackgroundWorker.new(%{
+ "op" => "follow_import",
+ "user_id" => user.id,
+ "actor" => actor
+ })
+ |> Oban.insert()
+
+ acc ++ [job]
+ end)
+ end)
+
+ {:ok, jobs}
end
- def mutes_import(%User{} = user, [_ | _] = identifiers) do
- BackgroundWorker.enqueue(
- "mutes_import",
- %{"user_id" => user.id, "identifiers" => identifiers}
- )
+ def mutes_import(%User{} = user, [_ | _] = actors) do
+ jobs =
+ Repo.checkout(fn ->
+ Enum.reduce(actors, [], fn actor, acc ->
+ {:ok, job} =
+ BackgroundWorker.new(%{
+ "op" => "mute_import",
+ "user_id" => user.id,
+ "actor" => actor
+ })
+ |> Oban.insert()
+
+ acc ++ [job]
+ end)
+ end)
+
+ {:ok, jobs}
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -222,10 +222,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
) do
with {:ok, _job} <-
- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
- activity_id: activity.id,
- expires_at: expires_at
- }) do
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: activity.id
+ },
+ scheduled_at: expires_at
+ ) do
{:ok, activity}
end
end
@@ -446,10 +448,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
_ <- notify_and_stream(activity) do
maybe_federate(activity)
- BackgroundWorker.enqueue("move_following", %{
+ BackgroundWorker.new(%{
+ "op" => "move_following",
"origin_id" => origin.id,
"target_id" => target.id
})
+ |> Oban.insert()
{:ok, activity}
else
@@ -1538,16 +1542,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp get_actor_url(_url), do: nil
- defp normalize_image(%{"url" => url}) do
+ defp normalize_image(%{"url" => url} = data) do
%{
"type" => "Image",
"url" => [%{"href" => url}]
}
+ |> maybe_put_description(data)
end
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
defp normalize_image(_), do: nil
+ defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
+ Map.put(map, "name", description)
+ end
+
+ defp maybe_put_description(map, _), do: map
+
defp object_to_user_data(data, additional) do
fields =
data
@@ -1797,10 +1808,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# enqueue a task to fetch all pinned objects
Enum.each(pins, fn {ap_id, _} ->
if is_nil(Object.get_cached_by_ap_id(ap_id)) do
- Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
+ Pleroma.Workers.RemoteFetcherWorker.new(%{
+ "op" => "fetch_remote",
"id" => ap_id,
"depth" => 1
})
+ |> Oban.insert()
end
end)
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -311,7 +311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
post_inbox_relayed_create(conn, params)
else
conn
- |> put_status(:bad_request)
+ |> put_status(403)
|> json("Not federating")
end
end
@@ -482,7 +482,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> put_status(:forbidden)
|> json(message)
- {:error, message} ->
+ {:error, message} when is_binary(message) ->
conn
|> put_status(:bad_request)
|> json(message)
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
@@ -108,6 +108,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def filter(%{} = object), do: get_policies() |> filter(object)
+ def id_filter(policies, id) when is_binary(id) do
+ policies
+ |> Enum.filter(&function_exported?(&1, :id_filter, 1))
+ |> Enum.all?(& &1.id_filter(id))
+ end
+
+ def id_filter(id) when is_binary(id), do: get_policies() |> id_filter(id)
+
@impl true
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -63,20 +63,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
end
@impl true
- def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
+ def filter(%{"type" => "Follow", "actor" => actor_id} = activity) do
%User{} = actor = normalize_by_ap_id(actor_id)
score = determine_if_followbot(actor)
- if score < 0.8 || bot_allowed?(message, actor) do
- {:ok, message}
+ if score < 0.8 || bot_allowed?(activity, actor) do
+ {:ok, activity}
else
{:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -29,17 +29,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
defp contains_links?(_), do: false
@impl true
- def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
+ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = activity) do
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)},
{:old_user, true} <- {:old_user, old_user?(u)} do
- {:ok, message}
+ {:ok, activity}
else
{:ok, %User{local: true}} ->
- {:ok, message}
+ {:ok, activity}
{:contains_links, false} ->
- {:ok, message}
+ {:ok, activity}
{:old_user, false} ->
{:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
end
# in all other cases, pass through
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex
@@ -22,11 +22,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
end
# copied from HellthreadPolicy
- defp get_recipient_count(message) do
- recipients = (message["to"] || []) ++ (message["cc"] || [])
+ defp get_recipient_count(activity) do
+ recipients = (activity["to"] || []) ++ (activity["cc"] || [])
follower_collection =
- User.get_cached_by_ap_id(message["actor"] || message["attributedTo"]).follower_address
+ User.get_cached_by_ap_id(activity["actor"] || activity["attributedTo"]).follower_address
if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients =
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do
end
# in all other cases, pass through
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex b/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex
@@ -38,18 +38,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
@query_timeout 500
@impl true
- def filter(%{"actor" => actor} = object) do
+ def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor)
- with {:ok, object} <- check_rbl(actor_info, object) do
- {:ok, object}
+ with {:ok, activity} <- check_rbl(actor_info, activity) do
+ {:ok, activity}
else
_ -> {:reject, "[DNSRBLPolicy]"}
end
end
@impl true
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
@@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
}
end
- defp check_rbl(%{host: actor_host}, object) do
+ defp check_rbl(%{host: actor_host}, activity) do
with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()),
zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do
query =
@@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
rbl_response = rblquery(query)
if Enum.empty?(rbl_response) do
- {:ok, object}
+ {:ok, activity}
else
Task.start(fn ->
reason =
@@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do
:error
end
else
- _ -> {:ok, object}
+ _ -> {:ok, activity}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
@@ -8,9 +8,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
- def filter(object) do
- Logger.debug("REJECTING #{inspect(object)}")
- {:reject, object}
+ def filter(activity) do
+ Logger.debug("REJECTING #{inspect(activity)}")
+ {:reject, activity}
+ end
+
+ @impl true
+ def id_filter(id) do
+ Logger.debug("REJECTING #{id}")
+ false
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex
@@ -28,11 +28,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], [])
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def history_awareness, do: :manual
- @impl Pleroma.Web.ActivityPub.MRF.Policy
- def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message)
+ @impl true
+ def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = activity)
when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do
with {:ok, object} <-
Updater.do_with_history(object, fn object ->
@@ -42,13 +42,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
Updater.do_with_history(object, fn object ->
{:ok, process_remove(object, :shortcode, config_remove_shortcode())}
end),
- activity <- Map.put(message, "object", object),
+ activity <- Map.put(activity, "object", object),
activity <- maybe_delist(activity) do
{:ok, activity}
end
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do
with object <- process_remove(object, :url, config_remove_url()),
object <- process_remove(object, :shortcode, config_remove_shortcode()) do
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
end
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def filter(%{"type" => "EmojiReact"} = object) do
with {:ok, _} <-
matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do
@@ -67,9 +67,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
end
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
- def filter(message) do
- {:ok, message}
+ @impl true
+ def filter(activity) do
+ {:ok, activity}
end
defp match_string?(string, pattern) when is_binary(pattern) do
@@ -214,7 +214,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
)
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def describe do
mrf_emoji =
Pleroma.Config.get(:mrf_emoji, [])
@@ -226,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
{:ok, %{mrf_emoji: mrf_emoji}}
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def config_description do
%{
key: :mrf_emoji,
@@ -239,7 +239,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :remove_url,
type: {:list, :string},
description: """
- A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
+ A list of patterns which result in emoji whose URL matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
@@ -249,7 +249,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :remove_shortcode,
type: {:list, :string},
description: """
- A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles.
+ A list of patterns which result in emoji whose shortcode matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
@@ -259,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :federated_timeline_removal_url,
type: {:list, :string},
description: """
- A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
+ A list of patterns which result in activity with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
@@ -269,7 +269,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do
key: :federated_timeline_removal_shortcode,
type: {:list, :string},
description: """
- A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
+ A list of patterns which result in activities with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -29,19 +29,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
def filter_by_summary(_in_reply_to, child), do: child
- def filter(%{"type" => type, "object" => child_object} = object)
- when type in ["Create", "Update"] and is_map(child_object) do
+ def filter(%{"type" => type, "object" => object} = activity)
+ when type in ["Create", "Update"] and is_map(object) do
child =
- child_object["inReplyTo"]
+ object["inReplyTo"]
|> Object.normalize(fetch: false)
- |> filter_by_summary(child_object)
+ |> filter_by_summary(object)
- object = Map.put(object, "object", child)
+ activity = Map.put(activity, "object", child)
- {:ok, object}
+ {:ok, activity}
end
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex b/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.FODirectReply do
+ @moduledoc """
+ FODirectReply alters the scope of replies to activities which are Followers Only to be Direct. The purpose of this policy is to prevent broken threads for followers of the reply author because their response was to a user that they are not also following.
+ """
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Visibility
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "to" => to,
+ "object" => %{
+ "actor" => actor,
+ "type" => "Note",
+ "inReplyTo" => in_reply_to
+ }
+ } = activity
+ ) do
+ with true <- is_binary(in_reply_to),
+ %User{follower_address: followers_collection, local: true} <- User.get_by_ap_id(actor),
+ %Object{} = in_reply_to_object <- Object.get_by_ap_id(in_reply_to),
+ "private" <- Visibility.get_visibility(in_reply_to_object) do
+ direct_to = to -- [followers_collection]
+
+ updated_activity =
+ activity
+ |> Map.put("cc", [])
+ |> Map.put("to", direct_to)
+ |> Map.put("directMessage", true)
+ |> put_in(["object", "cc"], [])
+ |> put_in(["object", "to"], direct_to)
+
+ {:ok, updated_activity}
+ else
+ _ -> {:ok, activity}
+ end
+ end
+
+ @impl true
+ def filter(activity), do: {:ok, activity}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
@@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
require Logger
@impl true
- def filter(message) do
+ def filter(activity) do
with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
%User{actor_type: "Service"} = follower <-
User.get_cached_by_nickname(follower_nickname),
- %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
- try_follow(follower, message)
+ %{"type" => "Create", "object" => %{"type" => "Note"}} <- activity do
+ try_follow(follower, activity)
else
nil ->
Logger.warning(
@@ -24,17 +24,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
account does not exist, or the account is not correctly configured as a bot."
)
- {:ok, message}
+ {:ok, activity}
_ ->
- {:ok, message}
+ {:ok, activity}
end
end
- defp try_follow(follower, message) do
- to = Map.get(message, "to", [])
- cc = Map.get(message, "cc", [])
- actor = [message["actor"]]
+ defp try_follow(follower, activity) do
+ to = Map.get(activity, "to", [])
+ cc = Map.get(activity, "cc", [])
+ actor = [activity["actor"]]
Enum.concat([to, cc, actor])
|> List.flatten()
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
end
end)
- {:ok, message}
+ {:ok, activity}
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
"cc" => cc,
"actor" => actor,
"object" => object
- } = message
+ } = activity
) do
user = User.get_cached_by_ap_id(actor)
isbot = check_if_bot(user)
@@ -36,20 +36,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
|> Map.put("to", to)
|> Map.put("cc", cc)
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Map.put("object", object)
- {:ok, message}
+ {:ok, activity}
else
- {:ok, message}
+ {:ok, activity}
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
end
@impl true
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -79,18 +79,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
%{
"type" => type,
"object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
- } = object
+ } = activity
)
when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do
# image-only posts from pleroma apparently reach this MRF without the content field
- content = object["object"]["content"] || ""
+ content = activity["object"]["content"] || ""
# Get the replied-to user for sorting
- replied_to_user = get_replied_to_user(object["object"])
+ replied_to_user = get_replied_to_user(activity["object"])
mention_users =
to
- |> clean_recipients(object)
+ |> clean_recipients(activity)
|> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.reject(&is_nil/1)
|> sort_replied_user(replied_to_user)
@@ -126,11 +126,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
content
end
- {:ok, put_in(object["object"]["content"], content)}
+ {:ok, put_in(activity["object"]["content"], content)}
end
@impl true
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
alias Pleroma.Object
@moduledoc """
- Reject, TWKN-remove or Set-Sensitive messages with specific hashtags (without the leading #)
+ Reject, TWKN-remove or Set-Sensitive activities with specific hashtags (without the leading #)
Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
"""
@@ -19,40 +19,40 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
@impl true
def history_awareness, do: :manual
- defp check_reject(message, hashtags) do
+ defp check_reject(activity, hashtags) do
if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
{:reject, "[HashtagPolicy] Matches with rejected keyword"}
else
- {:ok, message}
+ {:ok, activity}
end
end
- defp check_ftl_removal(%{"to" => to} = message, hashtags) do
+ defp check_ftl_removal(%{"to" => to} = activity, hashtags) do
if Pleroma.Constants.as_public() in to and
Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
match in hashtags
end) do
to = List.delete(to, Pleroma.Constants.as_public())
- cc = [Pleroma.Constants.as_public() | message["cc"] || []]
+ cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc)
- {:ok, message}
+ {:ok, activity}
else
- {:ok, message}
+ {:ok, activity}
end
end
- defp check_ftl_removal(message, _hashtags), do: {:ok, message}
+ defp check_ftl_removal(activity, _hashtags), do: {:ok, activity}
- defp check_sensitive(message) do
+ defp check_sensitive(activity) do
{:ok, new_object} =
- Object.Updater.do_with_history(message["object"], fn object ->
+ Object.Updater.do_with_history(activity["object"], fn object ->
hashtags = Object.hashtags(%Object{data: object})
if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
@@ -62,11 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
end
end)
- {:ok, Map.put(message, "object", new_object)}
+ {:ok, Map.put(activity, "object", new_object)}
end
@impl true
- def filter(%{"type" => type, "object" => object} = message) when type in ["Create", "Update"] do
+ def filter(%{"type" => type, "object" => object} = activity)
+ when type in ["Create", "Update"] do
history_items =
with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do
items
@@ -82,23 +83,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags
if hashtags != [] do
- with {:ok, message} <- check_reject(message, hashtags),
- {:ok, message} <-
+ with {:ok, activity} <- check_reject(activity, hashtags),
+ {:ok, activity} <-
(if type == "Create" do
- check_ftl_removal(message, hashtags)
+ check_ftl_removal(activity, hashtags)
else
- {:ok, message}
+ {:ok, activity}
end),
- {:ok, message} <- check_sensitive(message) do
- {:ok, message}
+ {:ok, activity} <- check_sensitive(activity) do
+ {:ok, activity}
end
else
- {:ok, message}
+ {:ok, activity}
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
@@ -120,21 +121,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
%{
key: :reject,
type: {:list, :string},
- description: "A list of hashtags which result in message being rejected.",
+ description: "A list of hashtags which result in the activity being rejected.",
suggestions: ["foo"]
},
%{
key: :federated_timeline_removal,
type: {:list, :string},
description:
- "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).",
+ "A list of hashtags which result in the activity being removed from federated timelines (a.k.a unlisted).",
suggestions: ["foo"]
},
%{
key: :sensitive,
type: {:list, :string},
description:
- "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)",
+ "A list of hashtags which result in the activity being set as sensitive (a.k.a NSFW/R-18)",
suggestions: ["nsfw", "r18"]
}
]
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -7,54 +7,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
require Pleroma.Constants
- @moduledoc "Block messages with too much mentions (configurable)"
+ @moduledoc "Block activities with too much mentions (configurable)"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
- defp delist_message(message, threshold) when threshold > 0 do
- follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
- to = message["to"] || []
- cc = message["cc"] || []
+ defp delist_activity(activity, threshold) when threshold > 0 do
+ follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address
+ to = activity["to"] || []
+ cc = activity["cc"] || []
follower_collection? = Enum.member?(to ++ cc, follower_collection)
- message =
- case get_recipient_count(message) do
+ activity =
+ case get_recipient_count(activity) do
{:public, recipients}
when follower_collection? and recipients > threshold ->
- message
+ activity
|> Map.put("to", [follower_collection])
|> Map.put("cc", [Pleroma.Constants.as_public()])
{:public, recipients} when recipients > threshold ->
- message
+ activity
|> Map.put("to", [])
|> Map.put("cc", [Pleroma.Constants.as_public()])
_ ->
- message
+ activity
end
- {:ok, message}
+ {:ok, activity}
end
- defp delist_message(message, _threshold), do: {:ok, message}
+ defp delist_activity(activity, _threshold), do: {:ok, activity}
- defp reject_message(message, threshold) when threshold > 0 do
- with {_, recipients} <- get_recipient_count(message) do
+ defp reject_activity(activity, threshold) when threshold > 0 do
+ with {_, recipients} <- get_recipient_count(activity) do
if recipients > threshold do
{:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else
- {:ok, message}
+ {:ok, activity}
end
end
end
- defp reject_message(message, _threshold), do: {:ok, message}
+ defp reject_activity(activity, _threshold), do: {:ok, activity}
- defp get_recipient_count(message) do
- recipients = (message["to"] || []) ++ (message["cc"] || [])
- follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
+ defp get_recipient_count(activity) do
+ recipients = (activity["to"] || []) ++ (activity["cc"] || [])
+ follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address
if Enum.member?(recipients, Pleroma.Constants.as_public()) do
recipients =
@@ -73,7 +73,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
end
@impl true
- def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message)
+ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = activity)
when object_type in ~w{Note Article} do
reject_threshold =
Pleroma.Config.get(
@@ -83,16 +83,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
- with {:ok, message} <- reject_message(message, reject_threshold),
- {:ok, message} <- delist_message(message, delist_threshold) do
- {:ok, message}
+ with {:ok, activity} <- reject_activity(activity, reject_threshold),
+ {:ok, activity} <- delist_activity(activity, delist_threshold) do
+ {:ok, activity}
else
e -> e
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe,
@@ -104,13 +104,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
key: :mrf_hellthread,
related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
label: "MRF Hellthread",
- description: "Block messages with excessive user mentions",
+ description: "Block activities with excessive user mentions",
children: [
%{
key: :delist_threshold,
type: :integer,
description:
- "Number of mentioned users after which the message gets removed from timelines and" <>
+ "Number of mentioned users after which the activity gets removed from timelines and" <>
"disables notifications. Set to 0 to disable.",
suggestions: [10]
},
@@ -118,7 +118,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
key: :reject_threshold,
type: :integer,
description:
- "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
+ "Number of mentioned users after which the activity gets rejected. Set to 0 to disable.",
suggestions: [20]
}
]
diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
@@ -48,12 +48,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
end
@impl true
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def history_awareness, do: :auto
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
alias Pleroma.Web.ActivityPub.MRF.Utils
- @moduledoc "Reject or Word-Replace messages with a keyword or regex"
+ @moduledoc "Reject or Word-Replace activities with a keyword or regex"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|> Enum.join("\n")
end
- defp check_reject(%{"object" => %{} = object} = message) do
+ defp check_reject(%{"object" => %{} = object} = activity) do
with {:ok, _new_object} <-
Pleroma.Object.Updater.do_with_history(object, fn object ->
payload = object_payload(object)
@@ -35,16 +35,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end) do
{:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
- {:ok, message}
+ {:ok, activity}
end
end) do
- {:ok, message}
+ {:ok, activity}
else
e -> e
end
end
- defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = message) do
+ defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = activity) do
check_keyword = fn object ->
payload = object_payload(object)
@@ -67,24 +67,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Pleroma.Constants.as_public() in to and should_delist?.(object) do
to = List.delete(to, Pleroma.Constants.as_public())
- cc = [Pleroma.Constants.as_public() | message["cc"] || []]
+ cc = [Pleroma.Constants.as_public() | activity["cc"] || []]
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
- {:ok, message}
+ {:ok, activity}
else
- {:ok, message}
+ {:ok, activity}
end
end
- defp check_ftl_removal(message) do
- {:ok, message}
+ defp check_ftl_removal(activity) do
+ {:ok, activity}
end
- defp check_replace(%{"object" => %{} = object} = message) do
+ defp check_replace(%{"object" => %{} = object} = activity) do
replace_kw = fn object ->
["content", "name", "summary"]
|> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end)
@@ -103,18 +103,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw)
- message = Map.put(message, "object", object)
+ activity = Map.put(activity, "object", object)
- {:ok, message}
+ {:ok, activity}
end
@impl true
- def filter(%{"type" => type, "object" => %{"content" => _content}} = message)
+ def filter(%{"type" => type, "object" => %{"content" => _content}} = activity)
when type in ["Create", "Update"] do
- with {:ok, message} <- check_reject(message),
- {:ok, message} <- check_ftl_removal(message),
- {:ok, message} <- check_replace(message) do
- {:ok, message}
+ with {:ok, activity} <- check_reject(activity),
+ {:ok, activity} <- check_ftl_removal(activity),
+ {:ok, activity} <- check_replace(activity) do
+ {:ok, activity}
else
{:reject, nil} -> {:reject, "[KeywordPolicy] "}
{:reject, _} = e -> e
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
@@ -154,13 +154,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
label: "MRF Keyword",
description:
- "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
+ "Reject or Word-Replace activities 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.
+ A list of patterns which result in the activity being rejected.
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
""",
@@ -170,7 +170,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
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).
+ A list of patterns which result in the activity 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/`.
""",
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
HTTP.get(url, [], http_client_opts)
end
- defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
+ defp preload(%{"object" => %{"attachment" => attachments}} = _activity) do
Enum.each(attachments, fn
%{"url" => url} when is_list(url) ->
url
@@ -49,15 +49,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
end
@impl true
- def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = message)
+ def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = activity)
when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do
- preload(message)
+ preload(activity)
- {:ok, message}
+ {:ok, activity}
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -3,25 +3,25 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
- @moduledoc "Block messages which mention a user"
+ @moduledoc "Block activities which mention a user"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
- def filter(%{"type" => "Create"} = message) do
+ def filter(%{"type" => "Create"} = activity) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
- recipients = (message["to"] || []) ++ (message["cc"] || [])
+ recipients = (activity["to"] || []) ++ (activity["cc"] || [])
if rejected_mention =
Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else
- {:ok, message}
+ {:ok, activity}
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
key: :mrf_mention,
related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
label: "MRF Mention",
- description: "Block messages which mention a specific user",
+ description: "Block activities which mention a specific user",
children: [
%{
key: :actors,
diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
@@ -9,20 +9,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do
alias Pleroma.Web.Endpoint
@impl true
- def filter(%{"actor" => actor} = object) do
+ def filter(%{"actor" => actor} = activity) do
with true <- local?(actor),
- true <- eligible_type?(object),
- true <- note?(object),
- false <- has_attachment?(object),
- true <- only_mentions?(object) do
+ true <- eligible_type?(activity),
+ true <- note?(activity),
+ false <- has_attachment?(activity),
+ true <- only_mentions?(activity) do
{:reject, "[NoEmptyPolicy]"}
else
_ ->
- {:ok, object}
+ {:ok, activity}
end
end
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
defp local?(actor) do
if actor |> String.starts_with?("#{Endpoint.url()}") do
diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
- def filter(object) do
- {:ok, object}
+ def filter(activity) do
+ {:ok, activity}
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -13,15 +13,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
def filter(
%{
"type" => type,
- "object" => %{"content" => content, "attachment" => _} = _child_object
- } = object
+ "object" => %{"content" => content, "attachment" => _} = _object
+ } = activity
)
when type in ["Create", "Update"] and content in [".", "<p>.</p>"] do
- {:ok, put_in(object, ["object", "content"], "")}
+ {:ok, put_in(activity, ["object", "content"], "")}
end
@impl true
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -12,20 +12,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
def history_awareness, do: :auto
@impl true
- def filter(%{"type" => type, "object" => child_object} = object)
+ def filter(%{"type" => type, "object" => object} = activity)
when type in ["Create", "Update"] do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
content =
- child_object["content"]
+ object["content"]
|> HTML.filter_tags(scrub_policy)
- object = put_in(object, ["object", "content"], content)
+ activity = put_in(activity, ["object", "content"], content)
- {:ok, object}
+ {:ok, activity}
end
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex
@@ -122,52 +122,52 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
end
end
- def check_object_nsfw(%{"object" => %{} = child_object} = object) do
- case check_object_nsfw(child_object) do
- {:sfw, _} -> {:sfw, object}
- {:nsfw, _} -> {:nsfw, object}
+ def check_object_nsfw(%{"object" => %{} = object} = activity) do
+ case check_object_nsfw(object) do
+ {:sfw, _} -> {:sfw, activity}
+ {:nsfw, _} -> {:nsfw, activity}
end
end
def check_object_nsfw(object), do: {:sfw, object}
@impl true
- def filter(object) do
- with {:sfw, object} <- check_object_nsfw(object) do
- {:ok, object}
+ def filter(activity) do
+ with {:sfw, activity} <- check_object_nsfw(activity) do
+ {:ok, activity}
else
- {:nsfw, _data} -> handle_nsfw(object)
+ {:nsfw, _data} -> handle_nsfw(activity)
end
end
- defp handle_nsfw(object) do
+ defp handle_nsfw(activity) do
if Config.get([@policy, :reject]) do
- {:reject, object}
+ {:reject, activity}
else
{:ok,
- object
+ activity
|> maybe_unlist()
|> maybe_mark_sensitive()}
end
end
- defp maybe_unlist(object) do
+ defp maybe_unlist(activity) do
if Config.get([@policy, :unlist]) do
- unlist(object)
+ unlist(activity)
else
- object
+ activity
end
end
- defp maybe_mark_sensitive(object) do
+ defp maybe_mark_sensitive(activity) do
if Config.get([@policy, :mark_sensitive]) do
- mark_sensitive(object)
+ mark_sensitive(activity)
else
- object
+ activity
end
end
- def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do
+ def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = activity) do
with %User{} = user <- User.get_cached_by_ap_id(actor) do
to =
[user.follower_address | to]
@@ -179,7 +179,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
|> List.delete(user.follower_address)
|> Enum.uniq()
- object
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
else
@@ -187,14 +187,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do
end
end
- def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do
- Map.put(object, "object", mark_sensitive(child_object))
+ def mark_sensitive(%{"object" => object} = activity) when is_map(object) do
+ Map.put(activity, "object", mark_sensitive(object))
end
- def mark_sensitive(object) when is_map(object) do
- tags = (object["tag"] || []) ++ ["nsfw"]
+ def mark_sensitive(activity) when is_map(activity) do
+ tags = (activity["tag"] || []) ++ ["nsfw"]
- object
+ activity
|> Map.put("tag", tags)
|> Map.put("sensitive", true)
end
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@moduledoc "Filter activities depending on their age"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
- defp check_date(%{"object" => %{"published" => published}} = message) do
+ defp check_date(%{"object" => %{"published" => published}} = activity) do
with %DateTime{} = now <- DateTime.utc_now(),
{:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published),
max_ttl <- Config.get([:mrf_object_age, :threshold]),
{:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do
- {:ok, message}
+ {:ok, activity}
else
{:ttl, true} ->
{:reject, nil}
@@ -26,73 +26,73 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
end
end
- defp check_reject(message, actions) do
+ defp check_reject(activity, actions) do
if :reject in actions do
{:reject, "[ObjectAgePolicy]"}
else
- {:ok, message}
+ {:ok, activity}
end
end
- defp check_delist(message, actions) do
+ defp check_delist(activity, actions) do
if :delist in actions do
- with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
+ with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do
to =
- List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++
+ List.delete(activity["to"] || [], Pleroma.Constants.as_public()) ++
[user.follower_address]
cc =
- List.delete(message["cc"] || [], user.follower_address) ++
+ List.delete(activity["cc"] || [], user.follower_address) ++
[Pleroma.Constants.as_public()]
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc)
- {:ok, message}
+ {:ok, activity}
else
_e ->
{:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
- {:ok, message}
+ {:ok, activity}
end
end
- defp check_strip_followers(message, actions) do
+ defp check_strip_followers(activity, actions) do
if :strip_followers in actions do
- with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"] || [], user.follower_address)
- cc = List.delete(message["cc"] || [], user.follower_address)
+ with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do
+ to = List.delete(activity["to"] || [], user.follower_address)
+ cc = List.delete(activity["cc"] || [], user.follower_address)
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Kernel.put_in(["object", "to"], to)
|> Kernel.put_in(["object", "cc"], cc)
- {:ok, message}
+ {:ok, activity}
else
_e ->
{:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
- {:ok, message}
+ {:ok, activity}
end
end
@impl true
- def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do
+ def filter(%{"type" => "Create", "object" => %{"published" => _}} = activity) do
with actions <- Config.get([:mrf_object_age, :actions]),
- {:reject, _} <- check_date(message),
- {:ok, message} <- check_reject(message, actions),
- {:ok, message} <- check_delist(message, actions),
- {:ok, message} <- check_strip_followers(message, actions) do
- {:ok, message}
+ {:reject, _} <- check_date(activity),
+ {:ok, activity} <- check_reject(activity, actions),
+ {:ok, activity} <- check_delist(activity, actions),
+ {:ok, activity} <- check_strip_followers(activity, actions) do
+ {:ok, activity}
else
# check_date() is allowed to short-circuit the pipeline
e -> e
@@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
@@ -131,8 +131,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
type: {:list, :atom},
description:
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
- "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message; " <>
- "`:reject` rejects the message entirely",
+ "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct activity; " <>
+ "`:reject` rejects the activity entirely",
suggestions: [:delist, :strip_followers, :reject]
}
]
diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex
@@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
- @callback filter(map()) :: {:ok | :reject, map()}
+ @callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
+ @callback id_filter(String.t()) :: boolean()
@callback describe() :: {:ok | :error, map()}
@callback config_description() :: %{
optional(:children) => [map()],
@@ -13,5 +14,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.Policy do
description: String.t()
}
@callback history_awareness() :: :auto | :manual
- @optional_callbacks config_description: 0, history_awareness: 0
+ @optional_callbacks config_description: 0, history_awareness: 0, id_filter: 1
end
diff --git a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
+ @moduledoc """
+ QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread.
+ """
+ require Pleroma.Constants
+
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def history_awareness, do: :auto
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "object" => %{
+ "actor" => actor,
+ "type" => "Note",
+ "inReplyTo" => in_reply_to
+ }
+ } = activity
+ ) do
+ with true <- is_binary(in_reply_to),
+ false <- match?([], cc),
+ %User{follower_address: followers_collection, local: true} <-
+ User.get_by_ap_id(actor) do
+ updated_to =
+ to
+ |> Kernel.++([followers_collection])
+ |> Kernel.--([Pleroma.Constants.as_public()])
+
+ updated_cc = [Pleroma.Constants.as_public()]
+
+ updated_activity =
+ activity
+ |> Map.put("to", updated_to)
+ |> Map.put("cc", updated_cc)
+ |> put_in(["object", "to"], updated_to)
+ |> put_in(["object", "cc"], updated_cc)
+
+ {:ok, updated_activity}
+ else
+ _ -> {:ok, activity}
+ end
+ end
+
+ @impl true
+ def filter(activity), do: {:ok, activity}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex
@@ -10,18 +10,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do
require Pleroma.Constants
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
{:ok, Map.put(activity, "object", filter_object(object))}
end
- @impl Pleroma.Web.ActivityPub.MRF.Policy
- def filter(object), do: {:ok, object}
+ @impl true
+ def filter(activity), do: {:ok, activity}
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def describe, do: {:ok, %{}}
- @impl Pleroma.Web.ActivityPub.MRF.Policy
+ @impl true
def history_awareness, do: :auto
defp filter_object(%{"quoteUrl" => quote_url} = object) do
diff --git a/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex b/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex
@@ -0,0 +1,118 @@
+defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
+ @moduledoc "Drop remote reports if they don't contain enough information."
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ alias Pleroma.Config
+
+ @impl true
+ def filter(%{"type" => "Flag"} = object) do
+ with {_, false} <- {:local, local?(object)},
+ {:ok, _} <- maybe_reject_all(object),
+ {:ok, _} <- maybe_reject_anonymous(object),
+ {:ok, _} <- maybe_reject_third_party(object),
+ {:ok, _} <- maybe_reject_empty_message(object) do
+ {:ok, object}
+ else
+ {:local, true} -> {:ok, object}
+ {:reject, message} -> {:reject, message}
+ error -> {:reject, error}
+ end
+ end
+
+ def filter(object), do: {:ok, object}
+
+ defp maybe_reject_all(object) do
+ if Config.get([:mrf_remote_report, :reject_all]) do
+ {:reject, "[RemoteReportPolicy] Remote report"}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp maybe_reject_anonymous(%{"actor" => actor} = object) do
+ with true <- Config.get([:mrf_remote_report, :reject_anonymous]),
+ %URI{path: "/actor"} <- URI.parse(actor) do
+ {:reject, "[RemoteReportPolicy] Anonymous: #{actor}"}
+ else
+ _ -> {:ok, object}
+ end
+ end
+
+ defp maybe_reject_third_party(%{"object" => objects} = object) do
+ {_, to} =
+ case objects do
+ [head | tail] when is_binary(head) -> {tail, head}
+ s when is_binary(s) -> {[], s}
+ _ -> {[], ""}
+ end
+
+ with true <- Config.get([:mrf_remote_report, :reject_third_party]),
+ false <- String.starts_with?(to, Pleroma.Web.Endpoint.url()) do
+ {:reject, "[RemoteReportPolicy] Third-party: #{to}"}
+ else
+ _ -> {:ok, object}
+ end
+ end
+
+ defp maybe_reject_empty_message(%{"content" => content} = object)
+ when is_binary(content) and content != "" do
+ {:ok, object}
+ end
+
+ defp maybe_reject_empty_message(object) do
+ if Config.get([:mrf_remote_report, :reject_empty_message]) do
+ {:reject, ["RemoteReportPolicy] No content"]}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp local?(%{"actor" => actor}) do
+ String.starts_with?(actor, Pleroma.Web.Endpoint.url())
+ end
+
+ @impl true
+ def describe do
+ mrf_remote_report =
+ Config.get(:mrf_remote_report)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_remote_report: mrf_remote_report}}
+ end
+
+ @impl true
+ def config_description do
+ %{
+ key: :mrf_remote_report,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy",
+ label: "MRF Remote Report",
+ description: "Drop remote reports if they don't contain enough information.",
+ children: [
+ %{
+ key: :reject_all,
+ type: :boolean,
+ description: "Reject all remote reports? (this option takes precedence)",
+ suggestions: [false]
+ },
+ %{
+ key: :reject_anonymous,
+ type: :boolean,
+ description: "Reject anonymous remote reports?",
+ suggestions: [true]
+ },
+ %{
+ key: :reject_third_party,
+ type: :boolean,
+ description: "Reject reports on users from third-party instances?",
+ suggestions: [true]
+ },
+ %{
+ key: :reject_empty_message,
+ type: :boolean,
+ description: "Reject remote reports with no message?",
+ suggestions: [true]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -13,20 +13,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
require Pleroma.Constants
- defp check_accept(%{host: actor_host} = _actor_info, object) do
+ defp check_accept(%{host: actor_host} = _actor_info, activity) do
accepts =
instance_list(:accept)
|> MRF.subdomains_regex()
cond do
- accepts == [] -> {:ok, object}
- actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
- MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
+ accepts == [] -> {:ok, activity}
+ actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, activity}
+ MRF.subdomain_match?(accepts, actor_host) -> {:ok, activity}
true -> {:reject, "[SimplePolicy] host not in accept list"}
end
end
- defp check_reject(%{host: actor_host} = _actor_info, object) do
+ defp check_reject(%{host: actor_host} = _actor_info, activity) do
rejects =
instance_list(:reject)
|> MRF.subdomains_regex()
@@ -34,109 +34,109 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(rejects, actor_host) do
{:reject, "[SimplePolicy] host in reject list"}
else
- {:ok, object}
+ {:ok, activity}
end
end
defp check_media_removal(
%{host: actor_host} = _actor_info,
- %{"type" => type, "object" => %{"attachment" => child_attachment}} = object
+ %{"type" => type, "object" => %{"attachment" => object_attachment}} = activity
)
- when length(child_attachment) > 0 and type in ["Create", "Update"] do
+ when length(object_attachment) > 0 and type in ["Create", "Update"] do
media_removal =
instance_list(:media_removal)
|> MRF.subdomains_regex()
- object =
+ activity =
if MRF.subdomain_match?(media_removal, actor_host) do
- child_object = Map.delete(object["object"], "attachment")
- Map.put(object, "object", child_object)
+ object = Map.delete(activity["object"], "attachment")
+ Map.put(activity, "object", object)
else
- object
+ activity
end
- {:ok, object}
+ {:ok, activity}
end
- defp check_media_removal(_actor_info, object), do: {:ok, object}
+ defp check_media_removal(_actor_info, activity), do: {:ok, activity}
defp check_media_nsfw(
%{host: actor_host} = _actor_info,
%{
"type" => type,
- "object" => %{} = _child_object
- } = object
+ "object" => %{} = _object
+ } = activity
)
when type in ["Create", "Update"] do
media_nsfw =
instance_list(:media_nsfw)
|> MRF.subdomains_regex()
- object =
+ activity =
if MRF.subdomain_match?(media_nsfw, actor_host) do
- Kernel.put_in(object, ["object", "sensitive"], true)
+ Kernel.put_in(activity, ["object", "sensitive"], true)
else
- object
+ activity
end
- {:ok, object}
+ {:ok, activity}
end
- defp check_media_nsfw(_actor_info, object), do: {:ok, object}
+ defp check_media_nsfw(_actor_info, activity), do: {:ok, activity}
- defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
+ defp check_ftl_removal(%{host: actor_host} = _actor_info, activity) do
timeline_removal =
instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex()
- object =
+ activity =
with true <- MRF.subdomain_match?(timeline_removal, actor_host),
- user <- User.get_cached_by_ap_id(object["actor"]),
- true <- Pleroma.Constants.as_public() in object["to"] do
- to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
+ user <- User.get_cached_by_ap_id(activity["actor"]),
+ true <- Pleroma.Constants.as_public() in activity["to"] do
+ to = List.delete(activity["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
- cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
+ cc = List.delete(activity["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
- object
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
else
- _ -> object
+ _ -> activity
end
- {:ok, object}
+ {:ok, activity}
end
defp intersection(list1, list2) do
list1 -- list1 -- list2
end
- defp check_followers_only(%{host: actor_host} = _actor_info, object) do
+ defp check_followers_only(%{host: actor_host} = _actor_info, activity) do
followers_only =
instance_list(:followers_only)
|> MRF.subdomains_regex()
- object =
+ activity =
with true <- MRF.subdomain_match?(followers_only, actor_host),
- user <- User.get_cached_by_ap_id(object["actor"]) do
+ user <- User.get_cached_by_ap_id(activity["actor"]) do
# Don't use Map.get/3 intentionally, these must not be nil
- fixed_to = object["to"] || []
- fixed_cc = object["cc"] || []
+ fixed_to = activity["to"] || []
+ fixed_cc = activity["cc"] || []
to = FollowingRelationship.followers_ap_ids(user, fixed_to)
cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
- object
+ activity
|> Map.put("to", intersection([user.follower_address | to], fixed_to))
|> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
else
- _ -> object
+ _ -> activity
end
- {:ok, object}
+ {:ok, activity}
end
- defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
+ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = activity) do
report_removal =
instance_list(:report_removal)
|> MRF.subdomains_regex()
@@ -144,39 +144,39 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, "[SimplePolicy] host in report_removal list"}
else
- {:ok, object}
+ {:ok, activity}
end
end
- defp check_report_removal(_actor_info, object), do: {:ok, object}
+ defp check_report_removal(_actor_info, activity), do: {:ok, activity}
- defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
+ defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = activity) do
avatar_removal =
instance_list(:avatar_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
- {:ok, Map.delete(object, "icon")}
+ {:ok, Map.delete(activity, "icon")}
else
- {:ok, object}
+ {:ok, activity}
end
end
- defp check_avatar_removal(_actor_info, object), do: {:ok, object}
+ defp check_avatar_removal(_actor_info, activity), do: {:ok, activity}
- defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
+ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = activity) do
banner_removal =
instance_list(:banner_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
- {:ok, Map.delete(object, "image")}
+ {:ok, Map.delete(activity, "image")}
else
- {:ok, object}
+ {:ok, activity}
end
end
- defp check_banner_removal(_actor_info, object), do: {:ok, object}
+ defp check_banner_removal(_actor_info, activity), do: {:ok, activity}
defp check_object(%{"object" => object} = activity) do
with {:ok, _object} <- filter(object) do
@@ -184,7 +184,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
end
- defp check_object(object), do: {:ok, object}
+ defp check_object(activity), do: {:ok, activity}
defp instance_list(config_key) do
Config.get([:mrf_simple, config_key])
@@ -192,7 +192,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
@impl true
- def filter(%{"type" => "Delete", "actor" => actor} = object) do
+ def id_filter(id) do
+ host_info = URI.parse(id)
+
+ with {:ok, _} <- check_accept(host_info, %{}),
+ {:ok, _} <- check_reject(host_info, %{}) do
+ true
+ else
+ _ -> false
+ end
+ end
+
+ @impl true
+ def filter(%{"type" => "Delete", "actor" => actor} = activity) do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
@@ -202,54 +214,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
if MRF.subdomain_match?(reject_deletes, actor_host) do
{:reject, "[SimplePolicy] host in reject_deletes list"}
else
- {:ok, object}
+ {:ok, activity}
end
end
@impl true
- def filter(%{"actor" => actor} = object) do
+ def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor)
- with {:ok, object} <- check_accept(actor_info, object),
- {:ok, object} <- check_reject(actor_info, object),
- {:ok, object} <- check_media_removal(actor_info, object),
- {:ok, object} <- check_media_nsfw(actor_info, object),
- {:ok, object} <- check_ftl_removal(actor_info, object),
- {:ok, object} <- check_followers_only(actor_info, object),
- {:ok, object} <- check_report_removal(actor_info, object),
- {:ok, object} <- check_object(object) do
- {:ok, object}
+ with {:ok, activity} <- check_accept(actor_info, activity),
+ {:ok, activity} <- check_reject(actor_info, activity),
+ {:ok, activity} <- check_media_removal(actor_info, activity),
+ {:ok, activity} <- check_media_nsfw(actor_info, activity),
+ {:ok, activity} <- check_ftl_removal(actor_info, activity),
+ {:ok, activity} <- check_followers_only(actor_info, activity),
+ {:ok, activity} <- check_report_removal(actor_info, activity),
+ {:ok, activity} <- check_object(activity) do
+ {:ok, activity}
else
{:reject, _} = e -> e
end
end
- def filter(%{"id" => actor, "type" => obj_type} = object)
- when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
+ def filter(%{"id" => actor, "type" => actor_type} = activity)
+ when actor_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor)
- with {:ok, object} <- check_accept(actor_info, object),
- {:ok, object} <- check_reject(actor_info, object),
- {:ok, object} <- check_avatar_removal(actor_info, object),
- {:ok, object} <- check_banner_removal(actor_info, object) do
- {:ok, object}
+ with {:ok, activity} <- check_accept(actor_info, activity),
+ {:ok, activity} <- check_reject(actor_info, activity),
+ {:ok, activity} <- check_avatar_removal(actor_info, activity),
+ {:ok, activity} <- check_banner_removal(actor_info, activity) do
+ {:ok, activity}
else
{:reject, _} = e -> e
end
end
- def filter(object) when is_binary(object) do
- uri = URI.parse(object)
+ def filter(activity) when is_binary(activity) do
+ uri = URI.parse(activity)
- with {:ok, object} <- check_accept(uri, object),
- {:ok, object} <- check_reject(uri, object) do
- {:ok, object}
+ with {:ok, activity} <- check_accept(uri, activity),
+ {:ok, activity} <- check_reject(uri, activity) do
+ {:ok, activity}
else
{:reject, _} = e -> e
end
end
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex
@@ -62,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end
@impl true
- def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do
+ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = activity) do
host = URI.parse(actor).host
if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do
@@ -97,10 +97,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end
end
- {:ok, message}
+ {:ok, activity}
end
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
@spec config_description :: %{
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -20,20 +20,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
end
@impl true
- def filter(%{"actor" => actor} = message) do
+ def filter(%{"actor" => actor} = activity) do
with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug(
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
)
- MRF.filter(subchain, message)
+ MRF.filter(subchain, activity)
else
- _e -> {:ok, message}
+ _e -> {:ok, activity}
end
end
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
@@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
label: "MRF Subchain",
description:
- "This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
+ "This policy processes activities through an alternate pipeline when a given activity matches certain criteria." <>
" All criteria are configured as a map of regular expressions to lists of policy modules.",
children: [
%{
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -28,25 +28,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"mrf_tag:media-force-nsfw",
%{
"type" => type,
- "object" => %{"attachment" => child_attachment}
- } = message
+ "object" => %{"attachment" => object_attachment}
+ } = activity
)
- when length(child_attachment) > 0 and type in ["Create", "Update"] do
- {:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
+ when length(object_attachment) > 0 and type in ["Create", "Update"] do
+ {:ok, Kernel.put_in(activity, ["object", "sensitive"], true)}
end
defp process_tag(
"mrf_tag:media-strip",
%{
"type" => type,
- "object" => %{"attachment" => child_attachment} = object
- } = message
+ "object" => %{"attachment" => object_attachment} = object
+ } = activity
)
- when length(child_attachment) > 0 and type in ["Create", "Update"] do
+ when length(object_attachment) > 0 and type in ["Create", "Update"] do
object = Map.delete(object, "attachment")
- message = Map.put(message, "object", object)
+ activity = Map.put(activity, "object", object)
- {:ok, message}
+ {:ok, activity}
end
defp process_tag(
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"cc" => cc,
"actor" => actor,
"object" => object
- } = message
+ } = activity
) do
user = User.get_cached_by_ap_id(actor)
@@ -70,15 +70,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|> Map.put("to", to)
|> Map.put("cc", cc)
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Map.put("object", object)
- {:ok, message}
+ {:ok, activity}
else
- {:ok, message}
+ {:ok, activity}
end
end
@@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
"cc" => cc,
"actor" => actor,
"object" => object
- } = message
+ } = activity
) do
user = User.get_cached_by_ap_id(actor)
@@ -104,26 +104,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|> Map.put("to", to)
|> Map.put("cc", cc)
- message =
- message
+ activity =
+ activity
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Map.put("object", object)
- {:ok, message}
+ {:ok, activity}
else
- {:ok, message}
+ {:ok, activity}
end
end
defp process_tag(
"mrf_tag:disable-remote-subscription",
- %{"type" => "Follow", "actor" => actor} = message
+ %{"type" => "Follow", "actor" => actor} = activity
) do
user = User.get_cached_by_ap_id(actor)
if user.local == true do
- {:ok, message}
+ {:ok, activity}
else
{:reject,
"[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
@@ -133,14 +133,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}),
do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
- defp process_tag(_, message), do: {:ok, message}
+ defp process_tag(_, activity), do: {:ok, activity}
- def filter_message(actor, message) do
+ def filter_activity(actor, activity) do
User.get_cached_by_ap_id(actor)
|> get_tags()
- |> Enum.reduce({:ok, message}, fn
- tag, {:ok, message} ->
- process_tag(tag, message)
+ |> Enum.reduce({:ok, activity}, fn
+ tag, {:ok, activity} ->
+ process_tag(tag, activity)
_, error ->
error
@@ -148,15 +148,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
end
@impl true
- def filter(%{"object" => target_actor, "type" => "Follow"} = message),
- do: filter_message(target_actor, message)
+ def filter(%{"object" => target_actor, "type" => "Follow"} = activity),
+ do: filter_activity(target_actor, activity)
@impl true
- def filter(%{"actor" => actor, "type" => type} = message) when type in ["Create", "Update"],
- do: filter_message(actor, message)
+ def filter(%{"actor" => actor, "type" => type} = activity) when type in ["Create", "Update"],
+ do: filter_activity(actor, activity)
@impl true
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -8,18 +8,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
@moduledoc "Accept-list of users from specified instances"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
- defp filter_by_list(object, []), do: {:ok, object}
+ defp filter_by_list(activity, []), do: {:ok, activity}
- defp filter_by_list(%{"actor" => actor} = object, allow_list) do
+ defp filter_by_list(%{"actor" => actor} = activity, allow_list) do
if actor in allow_list do
- {:ok, object}
+ {:ok, activity}
else
{:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end
end
@impl true
- def filter(%{"actor" => actor} = object) do
+ def filter(%{"actor" => actor} = activity) do
actor_info = URI.parse(actor)
allow_list =
@@ -28,10 +28,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
[]
)
- filter_by_list(object, allow_list)
+ filter_by_list(activity, allow_list)
end
- def filter(object), do: {:ok, object}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe do
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -3,38 +3,38 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
- @moduledoc "Filter messages which belong to certain activity vocabularies"
+ @moduledoc "Filter activities which belong to certain activity vocabularies"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
@impl true
- def filter(%{"type" => "Undo", "object" => child_message} = message) do
- with {:ok, _} <- filter(child_message) do
- {:ok, message}
+ def filter(%{"type" => "Undo", "object" => object} = activity) do
+ with {:ok, _} <- filter(object) do
+ {:ok, activity}
else
{:reject, _} = e -> e
end
end
- def filter(%{"type" => message_type} = message) do
+ def filter(%{"type" => activity_type} = activity) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
{_, true} <-
{:accepted,
- Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
+ Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, activity_type)},
{_, false} <-
{:rejected,
- length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
- {:ok, _} <- filter(message["object"]) do
- {:ok, message}
+ length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, activity_type)},
+ {:ok, _} <- filter(activity["object"]) do
+ {:ok, activity}
else
{:reject, _} = e -> e
- {:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
- {:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
+ {:accepted, _} -> {:reject, "[VocabularyPolicy] #{activity_type} not in accept list"}
+ {:rejected, _} -> {:reject, "[VocabularyPolicy] #{activity_type} in reject list"}
end
end
- def filter(message), do: {:ok, message}
+ def filter(activity), do: {:ok, activity}
@impl true
def describe,
@@ -46,20 +46,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
key: :mrf_vocabulary,
related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
label: "MRF Vocabulary",
- description: "Filter messages which belong to certain activity vocabularies",
+ description: "Filter activities which belong to certain activity vocabularies",
children: [
%{
key: :accept,
type: {:list, :string},
description:
- "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
+ "A list of ActivityStreams terms to accept. If empty, all supported activities are accepted.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
},
%{
key: :reject,
type: {:list, :string},
description:
- "A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
+ "A list of ActivityStreams terms to reject. If empty, no activities are rejected.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
}
]
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
+ import Pleroma.Constants, only: [activity_types: 0, object_types: 0]
+
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
@@ -38,6 +40,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@impl true
def validate(object, meta)
+ # This overload works together with the InboxGuardPlug
+ # and ensures that we are not accepting any activity type
+ # that cannot pass InboxGuardPlug.
+ # If we want to support any more activity types, make sure to
+ # add it in Pleroma.Constants's activity_types or object_types,
+ # and, if applicable, allowed_activity_types_from_strangers.
+ def validate(%{"type" => type}, _meta)
+ when type not in activity_types() and type not in object_types(),
+ do: {:error, :not_allowed_object_type}
+
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
@@ -157,7 +169,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
meta = Keyword.put(meta, :object_data, object_data),
{:ok, update_activity} <-
update_activity
- |> UpdateValidator.cast_and_validate()
+ |> UpdateValidator.cast_and_validate(meta)
|> Ecto.Changeset.apply_action(:insert) do
update_activity = stringify_keys(update_activity)
{:ok, update_activity, meta}
@@ -165,7 +177,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
{:local, _} ->
with {:ok, object} <-
update_activity
- |> UpdateValidator.cast_and_validate()
+ |> UpdateValidator.cast_and_validate(meta)
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
@@ -195,9 +207,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
"Answer" -> AnswerValidator
end
+ cast_func =
+ if type == "Update" do
+ fn o -> validator.cast_and_validate(o, meta) end
+ else
+ fn o -> validator.cast_and_validate(o) end
+ end
+
with {:ok, object} <-
object
- |> validator.cast_and_validate()
+ |> cast_func.()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Object
+ alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -31,23 +33,50 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|> cast(data, __schema__(:fields))
end
- defp validate_data(cng) do
+ defp validate_data(cng, meta) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Update"])
|> validate_actor_presence()
- |> validate_updating_rights()
+ |> validate_updating_rights(meta)
end
- def cast_and_validate(data) do
+ def cast_and_validate(data, meta \\ []) do
data
|> cast_data
- |> validate_data
+ |> validate_data(meta)
end
- # For now we only support updating users, and here the rule is easy:
- # object id == actor id
- def validate_updating_rights(cng) do
+ def validate_updating_rights(cng, meta) do
+ if meta[:local] do
+ validate_updating_rights_local(cng)
+ else
+ validate_updating_rights_remote(cng)
+ end
+ end
+
+ # For local Updates, verify the actor can edit the object
+ def validate_updating_rights_local(cng) do
+ actor = get_field(cng, :actor)
+ updated_object = get_field(cng, :object)
+
+ if {:ok, actor} == ObjectValidators.ObjectID.cast(updated_object) do
+ cng
+ else
+ with %User{} = user <- User.get_cached_by_ap_id(actor),
+ {_, %Object{} = orig_object} <- {:object, Object.normalize(updated_object)},
+ :ok <- Object.authorize_access(orig_object, user) do
+ cng
+ else
+ _e ->
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+ end
+ end
+ end
+
+ # For remote Updates, verify the host is the same.
+ def validate_updating_rights_remote(cng) do
with actor = get_field(cng, :actor),
object = get_field(cng, :object),
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
defp config, do: Config.get([:pipeline, :config], Config)
- @spec common_pipeline(map(), keyword()) ::
- {:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()}
+ @type results :: {:ok, Activity.t() | Object.t(), keyword()}
+ @type errors :: {:error | :reject, any()}
+
+ # The Repo.transaction will wrap the result in an {:ok, _}
+ # and only returns an {:error, _} if the error encountered was related
+ # to the SQL transaction
+ @spec common_pipeline(map(), keyword()) :: results() | errors()
def common_pipeline(object, meta) do
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
{:ok, {:ok, activity, meta}} ->
side_effects().handle_after_transaction(meta)
{:ok, activity, meta}
- {:ok, value} ->
- value
+ {:ok, {:error, _} = error} ->
+ error
+
+ {:ok, {:reject, _} = error} ->
+ error
{:error, e} ->
{:error, e}
-
- {:reject, e} ->
- {:reject, e}
end
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Publisher.Prepared
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Workers.PublisherWorker
@@ -30,11 +31,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
@spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}}
def enqueue_one(%{} = params, worker_args \\ []) do
- PublisherWorker.enqueue(
- "publish_one",
- %{"params" => params},
+ PublisherWorker.new(
+ %{"op" => "publish_one", "params" => params},
worker_args
)
+ |> Oban.insert()
end
@doc """
@@ -76,14 +77,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end
@doc """
- Publish a single message to a peer. Takes a struct with the following
- parameters set:
-
+ Prepare an activity for publishing from an Oban job
* `inbox`: the inbox to publish to
* `activity_id`: the internal activity id
* `cc`: the cc recipients relevant to this inbox (optional)
"""
- def publish_one(%{inbox: inbox, activity_id: activity_id} = params) do
+ @spec prepare_one(map()) :: Prepared.t()
+ def prepare_one(%{inbox: inbox, activity_id: activity_id} = params) do
activity = Activity.get_by_id_with_user_actor(activity_id)
actor = activity.user_actor
@@ -93,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
- cc = Map.get(params, :cc)
+ cc = Map.get(params, :cc, [])
json =
data
@@ -113,27 +113,54 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
date: date
})
+ %Prepared{
+ activity_id: activity_id,
+ json: json,
+ date: date,
+ signature: signature,
+ digest: digest,
+ inbox: inbox,
+ unreachable_since: params[:unreachable_since]
+ }
+ end
+
+ @doc """
+ Publish a single message to a peer. Takes a struct with the following
+ parameters set:
+ * `activity_id`: the activity id
+ * `json`: the json payload
+ * `date`: the signed date from Pleroma.Signature.signed_date()
+ * `signature`: the signature from Pleroma.Signature.sign/2
+ * `digest`: base64 encoded the hash of the json payload prefixed with "SHA-256="
+ * `inbox`: the inbox URI of this delivery
+ * `unreachable_since`: timestamp the instance was marked unreachable
+
+ """
+ def publish_one(%Prepared{} = p) do
with {:ok, %{status: code}} = result when code in 200..299 <-
HTTP.post(
- inbox,
- json,
+ p.inbox,
+ p.json,
[
{"Content-Type", "application/activity+json"},
- {"Date", date},
- {"signature", signature},
- {"digest", digest}
+ {"Date", p.date},
+ {"signature", p.signature},
+ {"digest", p.digest}
]
) do
- if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
- Instances.set_reachable(inbox)
+ if not is_nil(p.unreachable_since) do
+ Instances.set_reachable(p.inbox)
end
result
else
{_post_result, %{status: code} = response} = e ->
- unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
- Logger.metadata(activity: activity_id, inbox: inbox, status: code)
- Logger.error("Publisher failed to inbox #{inbox} with status #{code}")
+ if is_nil(p.unreachable_since) do
+ Instances.set_unreachable(p.inbox)
+ end
+
+ Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
+ Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
case response do
%{status: 400} -> {:cancel, :bad_request}
@@ -143,18 +170,27 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
_ -> {:error, e}
end
+ {:error, {:already_started, _}} ->
+ Logger.debug("Publisher snoozing worker job due worker :already_started race condition")
+ connection_pool_snooze()
+
{:error, :pool_full} ->
Logger.debug("Publisher snoozing worker job due to full connection pool")
- {:snooze, 30}
+ connection_pool_snooze()
e ->
- unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
- Logger.metadata(activity: activity_id, inbox: inbox)
- Logger.error("Publisher failed to inbox #{inbox} #{inspect(e)}")
+ if is_nil(p.unreachable_since) do
+ Instances.set_unreachable(p.inbox)
+ end
+
+ Logger.metadata(activity: p.activity_id, inbox: p.inbox)
+ Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
{:error, e}
end
end
+ defp connection_pool_snooze, do: {:snooze, 3}
+
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
diff --git a/lib/pleroma/web/activity_pub/publisher/prepared.ex b/lib/pleroma/web/activity_pub/publisher/prepared.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Publisher.Prepared do
+ @type t :: %__MODULE__{}
+ defstruct [:activity_id, :json, :date, :signature, :digest, :inbox, :unreachable_since]
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -223,10 +223,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
object.data["replies"] != nil do
for reply_id <- object.data["replies"] do
- Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
+ Pleroma.Workers.RemoteFetcherWorker.new(%{
+ "op" => "fetch_remote",
"id" => reply_id,
"depth" => reply_depth
})
+ |> Oban.insert()
end
end
@@ -410,10 +412,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, expires_at} =
Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at])
- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
- activity_id: meta[:activity_id],
- expires_at: expires_at
- })
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: meta[:activity_id]
+ },
+ scheduled_at: expires_at
+ )
end
{:ok, object, meta}
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -129,8 +129,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"vcard:bday" => birthday,
"webfinger" => "acct:#{User.full_nickname(user)}"
}
- |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
- |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
+ |> Map.merge(
+ maybe_make_image(
+ &User.avatar_url/2,
+ User.image_description(user.avatar, nil),
+ "icon",
+ user
+ )
+ )
+ |> Map.merge(
+ maybe_make_image(
+ &User.banner_url/2,
+ User.image_description(user.banner, nil),
+ "image",
+ user
+ )
+ )
|> Map.merge(Utils.make_json_ld_header())
end
@@ -305,16 +319,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end
end
- defp maybe_make_image(func, key, user) do
+ defp maybe_make_image(func, description, key, user) do
if image = func.(user, no_default: true) do
%{
- key => %{
- "type" => "Image",
- "url" => image
- }
+ key =>
+ %{
+ "type" => "Image",
+ "url" => image
+ }
+ |> maybe_put_description(description)
}
else
%{}
end
end
+
+ defp maybe_put_description(map, description) when is_binary(description) do
+ Map.put(map, "name", description)
+ end
+
+ defp maybe_put_description(map, _description), do: map
end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -498,22 +498,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
- def identity_proofs_operation do
- %Operation{
- tags: ["Retrieve account information"],
- summary: "Identity proofs",
- operationId: "AccountController.identity_proofs",
- # Validators complains about unused path params otherwise
- parameters: [
- %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
- ],
- description: "Not implemented",
- responses: %{
- 200 => empty_array_response()
- }
- }
- end
-
def familiar_followers_operation do
%Operation{
tags: ["Retrieve account information"],
@@ -829,6 +813,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
allOf: [BooleanLike],
nullable: true,
description: "User's birthday will be visible"
+ },
+ avatar_description: %Schema{
+ type: :string,
+ nullable: true,
+ description: "Avatar image description."
+ },
+ header_description: %Schema{
+ type: :string,
+ nullable: true,
+ description: "Header image description."
}
},
example: %{
diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
security: [%{"oAuth" => ["write:media"]}],
requestBody: Helpers.request_body("Parameters", create_request()),
responses: %{
- 202 => Operation.response("Media", "application/json", Attachment),
+ 200 => Operation.response("Media", "application/json", Attachment),
400 => Operation.response("Media", "application/json", ApiError),
422 => Operation.response("Media", "application/json", ApiError),
500 => Operation.response("Media", "application/json", ApiError)
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -158,6 +158,10 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
type: :object,
properties: %{
id: %Schema{type: :string},
+ group_key: %Schema{
+ type: :string,
+ description: "Group key shared by similar notifications"
+ },
type: notification_type(),
created_at: %Schema{type: :string, format: :"date-time"},
account: %Schema{
@@ -180,6 +184,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
},
example: %{
"id" => "34975861",
+ "group-key" => "ungrouped-34975861",
"type" => "mention",
"created_at" => "2019-11-23T07:49:02.064Z",
"account" => Account.schema().example,
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -85,9 +85,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def subscribe_operation do
%Operation{
+ deprecated: true,
tags: ["Account actions"],
summary: "Subscribe",
- description: "Receive notifications for all statuses posted by the account.",
+ description:
+ "Receive notifications for all statuses posted by the account. Deprecated, use `notify: true` in follow operation instead.",
operationId: "PleromaAPI.AccountController.subscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
@@ -100,9 +102,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
def unsubscribe_operation do
%Operation{
+ deprecated: true,
tags: ["Account actions"],
summary: "Unsubscribe",
- description: "Stop receiving notifications for all statuses posted by the account.",
+ description:
+ "Stop receiving notifications for all statuses posted by the account. Deprecated, use `notify: false` in follow operation instead.",
operationId: "PleromaAPI.AccountController.unsubscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -31,12 +31,18 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
- :ids,
+ :id,
:query,
%Schema{type: :array, items: FlakeID},
"Array of status IDs"
),
Operation.parameter(
+ :ids,
+ :query,
+ %Schema{type: :array, items: FlakeID},
+ "Deprecated, use `id` instead"
+ ),
+ Operation.parameter(
:with_muted,
:query,
BooleanLike.schema(),
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -111,7 +111,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
format: :uri,
nullable: true,
description: "Favicon image of the user's instance"
- }
+ },
+ avatar_description: %Schema{type: :string},
+ header_description: %Schema{type: :string}
}
},
source: %Schema{
@@ -152,6 +154,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
example: %{
"acct" => "foobar",
"avatar" => "https://mypleroma.com/images/avi.png",
+ "avatar_description" => "",
"avatar_static" => "https://mypleroma.com/images/avi.png",
"bot" => false,
"created_at" => "2020-03-24T13:05:58.000Z",
@@ -162,6 +165,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"followers_count" => 0,
"following_count" => 1,
"header" => "https://mypleroma.com/images/banner.png",
+ "header_description" => "",
"header_static" => "https://mypleroma.com/images/banner.png",
"id" => "9tKi3esbG7OQgZ2920",
"locked" => false,
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -249,6 +249,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
nullable: true,
description:
"A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
+ },
+ list_id: %Schema{
+ type: :integer,
+ nullable: true,
+ description:
+ "The ID of the list the post is addressed to (if any, only returned to author)"
}
}
},
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
@@ -10,4 +10,9 @@ defmodule Pleroma.Web.Auth.Authenticator do
@callback handle_error(Plug.Conn.t(), any()) :: any()
@callback auth_template() :: String.t() | nil
@callback oauth_consumer_template() :: String.t() | nil
+
+ @callback change_password(Pleroma.User.t(), String.t(), String.t(), String.t()) ::
+ {:ok, Pleroma.User.t()} | {:error, term()}
+
+ @optional_callbacks change_password: 4
end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -3,18 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
+ alias Pleroma.LDAP
alias Pleroma.User
- require Logger
-
- import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
+ import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1]
@behaviour Pleroma.Web.Auth.Authenticator
@base Pleroma.Web.Auth.PleromaAuthenticator
- @connection_timeout 10_000
- @search_timeout 10_000
-
defdelegate get_registration(conn), to: @base
defdelegate create_from_registration(conn, registration), to: @base
defdelegate handle_error(conn, error), to: @base
@@ -24,7 +20,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
def get_user(%Plug.Conn{} = conn) do
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
{:ok, {name, password}} <- fetch_credentials(conn),
- %User{} = user <- ldap_user(name, password) do
+ %User{} = user <- LDAP.bind_user(name, password) do
{:ok, user}
else
{:ldap, _} ->
@@ -35,106 +31,12 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
end
end
- defp ldap_user(name, password) do
- ldap = Pleroma.Config.get(:ldap, [])
- host = Keyword.get(ldap, :host, "localhost")
- port = Keyword.get(ldap, :port, 389)
- ssl = Keyword.get(ldap, :ssl, false)
- sslopts = Keyword.get(ldap, :sslopts, [])
-
- options =
- [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
- if sslopts != [], do: [{:sslopts, sslopts}], else: []
-
- case :eldap.open([to_charlist(host)], options) do
- {:ok, connection} ->
- try do
- if Keyword.get(ldap, :tls, false) do
- :application.ensure_all_started(:ssl)
-
- case :eldap.start_tls(
- connection,
- Keyword.get(ldap, :tlsopts, []),
- @connection_timeout
- ) do
- :ok ->
- :ok
-
- error ->
- Logger.error("Could not start TLS: #{inspect(error)}")
- end
- end
-
- bind_user(connection, ldap, name, password)
- after
- :eldap.close(connection)
- end
-
- {:error, error} ->
- Logger.error("Could not open LDAP connection: #{inspect(error)}")
- {:error, {:ldap_connection_error, error}}
- end
- end
-
- defp bind_user(connection, ldap, name, password) do
- uid = Keyword.get(ldap, :uid, "cn")
- base = Keyword.get(ldap, :base)
-
- case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
- :ok ->
- case fetch_user(name) do
- %User{} = user ->
- user
-
- _ ->
- register_user(connection, base, uid, name)
- end
-
- error ->
- Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
- {:error, {:ldap_bind_error, error}}
- end
- end
-
- defp register_user(connection, base, uid, name) do
- case :eldap.search(connection, [
- {:base, to_charlist(base)},
- {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
- {:scope, :eldap.wholeSubtree()},
- {:timeout, @search_timeout}
- ]) do
- # The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
- # https://github.com/erlang/otp/pull/5538
- {:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
- try_register(name, attributes)
-
- {:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
- try_register(name, attributes)
-
- error ->
- Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
- {:error, {:ldap_search_error, error}}
+ def change_password(user, password, new_password, new_password) do
+ case LDAP.change_password(user.nickname, password, new_password) do
+ :ok -> {:ok, user}
+ e -> e
end
end
- defp try_register(name, attributes) do
- params = %{
- name: name,
- nickname: name,
- password: nil
- }
-
- params =
- case List.keyfind(attributes, ~c"mail", 0) do
- {_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
- _ -> params
- end
-
- changeset = User.register_changeset_ldap(%User{}, params)
-
- case User.register(changeset) do
- {:ok, user} -> user
- error -> error
- end
- end
+ def change_password(_, _, _, _), do: {:error, :password_confirmation}
end
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.AuthenticationPlug
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
@@ -101,4 +102,23 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
def auth_template, do: nil
def oauth_consumer_template, do: nil
+
+ @doc "Changes Pleroma.User password in the database"
+ def change_password(user, password, new_password, new_password) do
+ case CommonAPI.Utils.confirm_current_password(user, password) do
+ {:ok, user} ->
+ with {:ok, _user} <-
+ User.reset_password(user, %{
+ password: new_password,
+ password_confirmation: new_password
+ }) do
+ {:ok, user}
+ end
+
+ error ->
+ error
+ end
+ end
+
+ def change_password(_, _, _, _), do: {:error, :password_confirmation}
end
diff --git a/lib/pleroma/web/auth/wrapper_authenticator.ex b/lib/pleroma/web/auth/wrapper_authenticator.ex
@@ -39,4 +39,8 @@ defmodule Pleroma.Web.Auth.WrapperAuthenticator do
implementation().oauth_consumer_template() ||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
end
+
+ @impl true
+ def change_password(user, password, new_password, new_password_confirmation),
+ do: implementation().change_password(user, password, new_password, new_password_confirmation)
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do
require Pleroma.Constants
require Logger
- @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
def block(blocked, blocker) do
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do
end
@spec post_chat_message(User.t(), User.t(), String.t(), list()) ::
- {:ok, Activity.t()} | {:error, any()}
+ {:ok, Activity.t()} | Pipeline.errors()
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
:ok <- validate_chat_attachment_attribution(maybe_attachment, user),
@@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do
)} do
{:ok, activity}
else
- {:common_pipeline, {:reject, _} = e} -> e
+ {:common_pipeline, e} -> e
e -> e
end
end
@@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec unblock(User.t(), User.t()) ::
+ {:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking}
def unblock(blocked, blocker) do
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
@@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do
end
@spec follow(User.t(), User.t()) ::
- {:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected}
+ {:ok, User.t(), User.t(), Activity.t() | Object.t()}
+ | {:error, :rejected}
+ | Pipeline.errors()
def follow(followed, follower) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@@ -130,7 +133,7 @@ defmodule Pleroma.Web.CommonAPI do
if activity.data["state"] == "reject" do
{:error, :rejected}
else
- {:ok, follower, followed, activity}
+ {:ok, followed, follower, activity}
end
end
end
@@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()}
+ @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors()
def accept_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
@@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil
+ @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
@@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec delete(String.t(), User.t()) ::
+ {:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()}
def delete(activity_id, user) do
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
@@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found}
def repeat(id, user, params \\ %{}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
object = %Object{} <- Object.normalize(activity, fetch: false),
@@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()}
def unrepeat(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
@@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec favorite(String.t(), User.t()) ::
+ {:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()}
def favorite(id, %User{} = user) do
case favorite_helper(user, id) do
{:ok, _} = res ->
@@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec unfavorite(String.t(), User.t()) ::
+ {:ok, Activity.t()} | {:error, :not_found | String.t()}
def unfavorite(id, user) do
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id(id)},
@@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
end
@spec react_with_emoji(String.t(), User.t(), String.t()) ::
- {:ok, Activity.t()} | {:error, any()}
+ {:ok, Activity.t()} | {:error, String.t()}
def react_with_emoji(id, user, emoji) do
with %Activity{} = activity <- Activity.get_by_id(id),
object <- Object.normalize(activity, fetch: false),
@@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do
end
@spec unreact_with_emoji(String.t(), User.t(), String.t()) ::
- {:ok, Activity.t()} | {:error, any()}
+ {:ok, Activity.t()} | {:error, String.t()}
def unreact_with_emoji(id, user, emoji) do
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)},
@@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()}
+ @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors()
def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do
with :ok <- validate_not_author(object, user),
:ok <- validate_existing_votes(user, object),
@@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil}
def update(orig_activity, %User{} = user, changes) do
with orig_object <- Object.normalize(orig_activity),
{:ok, new_object} <- make_update_data(user, orig_object, changes),
@@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
+ @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
def pin(id, %User{} = user) do
with %Activity{} = activity <- create_activity_by_id(id),
true <- activity_belongs_to_actor(activity, user.ap_id),
@@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
+ @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
def unpin(id, user) do
with %Activity{} = activity <- create_activity_by_id(id),
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
@@ -552,18 +558,18 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+ @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()}
def add_mute(activity, user, params \\ %{}) do
expires_in = Map.get(params, :expires_in, 0)
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
if expires_in > 0 do
- Pleroma.Workers.MuteExpireWorker.enqueue(
- "unmute_conversation",
- %{"user_id" => user.id, "activity_id" => activity.id},
+ Pleroma.Workers.MuteExpireWorker.new(
+ %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id},
schedule_in: expires_in
)
+ |> Oban.insert()
end
{:ok, activity}
diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
redirector_with_meta(conn, %{user: user})
else
nil ->
- redirector(conn, params)
+ redirector_with_meta(conn, Map.delete(params, "maybe_nickname_or_id"))
end
end
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
@@ -35,22 +35,30 @@ defmodule Pleroma.Web.Federator do
end
# Client API
- def incoming_ap_doc(%{params: _params, req_headers: _req_headers} = args) do
- job_args = Enum.into(args, %{}, fn {k, v} -> {Atom.to_string(k), v} end)
-
- ReceiverWorker.enqueue(
- "incoming_ap_doc",
- Map.put(job_args, "timeout", :timer.seconds(20)),
+ def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
+ ReceiverWorker.new(
+ %{
+ "op" => "incoming_ap_doc",
+ "req_headers" => req_headers,
+ "params" => params,
+ "timeout" => :timer.seconds(20)
+ },
priority: 2
)
+ |> Oban.insert()
end
def incoming_ap_doc(%{"type" => "Delete"} = params) do
- ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3, queue: :slow)
+ ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params},
+ priority: 3,
+ queue: :slow
+ )
+ |> Oban.insert()
end
def incoming_ap_doc(params) do
- ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params})
+ ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params})
+ |> Oban.insert()
end
@impl true
@@ -60,9 +68,10 @@ defmodule Pleroma.Web.Federator do
@impl true
def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do
- PublisherWorker.enqueue("publish", %{"activity_id" => activity.id},
+ PublisherWorker.new(%{"op" => "publish", "activity_id" => activity.id},
priority: publish_priority(type)
)
+ |> Oban.insert()
end
defp publish_priority("Delete"), do: 3
@@ -71,7 +80,10 @@ defmodule Pleroma.Web.Federator do
# Job Worker Callbacks
@spec perform(atom(), any()) :: {:ok, any()} | {:error, any()}
- def perform(:publish_one, params), do: Publisher.publish_one(params)
+ def perform(:publish_one, params) do
+ Publisher.prepare_one(params)
+ |> Publisher.publish_one()
+ end
def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
@@ -90,7 +102,8 @@ defmodule Pleroma.Web.Federator do
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
- with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
+ with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
+ {:user_active, true} <- {:user_active, match?(true, user.is_active)},
nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
@@ -109,11 +122,6 @@ defmodule Pleroma.Web.Federator do
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
{:error, e}
- {:error, {:validate_object, _}} = e ->
- Logger.error("Incoming AP doc validation error: #{inspect(e)}")
- Logger.debug(Jason.encode!(params, pretty: true))
- e
-
e ->
# Just drop those for now
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.Feed.FeedView
def feed(conn, params) do
- if Config.get!([:instance, :public]) do
+ if not Config.restrict_unauthenticated_access?(:timelines, :local) do
render_feed(conn, params)
else
render_error(conn, :not_found, "Not found")
@@ -18,10 +18,12 @@ defmodule Pleroma.Web.Feed.TagController do
end
defp render_feed(conn, %{"tag" => raw_tag} = params) do
+ local_only = Config.restrict_unauthenticated_access?(:timelines, :federated)
+
{format, tag} = parse_tag(raw_tag)
activities =
- %{type: ["Create"], tag: tag}
+ %{type: ["Create"], tag: tag, local_only: local_only}
|> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
|> ActivityPub.fetch_public_activities()
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
@@ -15,11 +15,11 @@ defmodule Pleroma.Web.Feed.UserController do
action_fallback(:errors)
- def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
+ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname} = params) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
else
- _ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
+ _ -> Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, params)
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -22,7 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
- alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -51,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]}
- when action in [:verify_credentials, :endorsements, :identity_proofs]
+ when action in [:verify_credentials, :endorsements]
)
plug(
@@ -233,6 +232,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|> Maps.put_if_present(:birthday, params[:birthday])
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
+ |> Maps.put_if_present(:avatar_description, params[:avatar_description])
+ |> Maps.put_if_present(:header_description, params[:header_description])
# What happens here:
#
@@ -278,6 +279,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Name is too long")
+ {:error, %Ecto.Changeset{errors: [{:avatar_description, {_, _}} | _]}} ->
+ render_error(conn, :request_entity_too_large, "Avatar description is too long")
+
+ {:error, %Ecto.Changeset{errors: [{:header_description, {_, _}} | _]}} ->
+ render_error(conn, :request_entity_too_large, "Banner description is too long")
+
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
@@ -660,7 +667,4 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
defp get_familiar_followers(user, current_user) do
User.get_familiar_followers(user, current_user)
end
-
- @doc "GET /api/v1/identity_proofs"
- def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create)
+
plug(:skip_auth when action in [:create, :verify_credentials])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller
+
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@@ -30,9 +31,16 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do
params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
- with {:ok, result} <- Pleroma.Marker.upsert(user, params),
+ with {:ok, _} <- mark_notifications_read(user, params),
+ {:ok, result} <- Pleroma.Marker.upsert(user, params),
markers <- Map.values(result) do
render(conn, "markers.json", %{markers: markers})
end
end
+
+ defp mark_notifications_read(user, %{"notifications" => %{last_read_id: last_read_id}}) do
+ Pleroma.Notification.set_read_up_to(user, last_read_id)
+ end
+
+ defp mark_notifications_read(_, _), do: {:ok, :noop}
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -53,9 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
) do
attachment_data = Map.put(object.data, "id", object.id)
- conn
- |> put_status(202)
- |> render("attachment.json", %{attachment: attachment_data})
+ render(conn, "attachment.json", %{attachment: attachment_data})
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Workers.PollWorker
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -27,12 +28,16 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+ @poll_refresh_interval 120
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
- with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+ with %Object{} = object <- Object.get_by_id(id),
+ %Activity{} = activity <-
+ Activity.get_create_by_object_ap_id_with_object(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
+ maybe_refresh_poll(activity)
+
try_render(conn, "show.json", %{object: object, for: user})
else
error when is_nil(error) or error == false ->
@@ -70,4 +75,13 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
end
end)
end
+
+ defp maybe_refresh_poll(%Activity{object: %Object{} = object} = activity) do
+ with false <- activity.local,
+ {:ok, end_time} <- NaiveDateTime.from_iso8601(object.data["closed"]),
+ {_, :lt} <- {:closed_compare, NaiveDateTime.compare(object.updated_at, end_time)} do
+ PollWorker.new(%{"op" => "refresh", "activity_id" => activity.id})
+ |> Oban.insert(unique: [period: @poll_refresh_interval])
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -111,10 +111,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
`ids` query param is required
"""
def index(
- %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids} = params}}} =
+ %{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} =
conn,
_
) do
+ ids = Map.get(params, :id, Map.get(params, :ids))
limit = 100
activities =
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -18,10 +18,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
if not User.following?(follower, followed) do
CommonAPI.follow(followed, follower)
else
- {:ok, follower, followed, nil}
+ {:ok, followed, follower, nil}
end
- with {:ok, follower, _followed, _} <- result do
+ with {:ok, _followed, follower, _} <- result do
options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result)
set_subscription(options[:notify], result)
@@ -29,19 +29,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
end
end
- defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do
+ defp set_reblogs_visibility(false, {:ok, followed, follower, _}) do
CommonAPI.hide_reblogs(followed, follower)
end
- defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
+ defp set_reblogs_visibility(_, {:ok, followed, follower, _}) do
CommonAPI.show_reblogs(followed, follower)
end
- defp set_subscription(true, {:ok, follower, followed, _}) do
+ defp set_subscription(true, {:ok, followed, follower, _}) do
User.subscribe(follower, followed)
end
- defp set_subscription(false, {:ok, follower, followed, _}) do
+ defp set_subscription(false, {:ok, followed, follower, _}) do
User.unsubscribe(follower, followed)
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -219,8 +219,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
avatar = User.avatar_url(user) |> MediaProxy.url()
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
+ avatar_description = User.image_description(user.avatar)
header = User.banner_url(user) |> MediaProxy.url()
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
+ header_description = User.image_description(user.banner)
following_count =
if !user.hide_follows_count or !user.hide_follows or self,
@@ -321,7 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages,
- favicon: favicon
+ favicon: favicon,
+ avatar_description: avatar_description,
+ header_description: header_description
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -95,6 +95,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
response = %{
id: to_string(notification.id),
+ group_key: "ungrouped-" <> to_string(notification.id),
type: notification.type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: account,
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -465,7 +465,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
parent_visible: visible_for_user?(reply_to, opts[:for]),
pinned_at: pinned_at,
quotes_count: object.data["quotesCount"] || 0,
- bookmark_folder: bookmark_folder
+ bookmark_folder: bookmark_folder,
+ list_id: get_list_id(object, client_posted_this_activity)
}
}
end
@@ -803,19 +804,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp build_application(_), do: nil
- # Workaround for Elixir issue #10771
- # Avoid applying URI.merge unless necessary
- # TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
- # when Elixir 1.12 is the minimum supported version
- @spec build_image_url(struct() | nil, struct()) :: String.t() | nil
- defp build_image_url(
- %URI{scheme: image_scheme, host: image_host} = image_url_data,
- %URI{} = _page_url_data
- )
- when not is_nil(image_scheme) and not is_nil(image_host) do
- image_url_data |> to_string
- end
-
+ @spec build_image_url(URI.t(), URI.t()) :: String.t()
defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
URI.merge(page_url_data, image_url_data) |> to_string
end
@@ -847,4 +836,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
nil
end
end
+
+ defp get_list_id(object, client_posted_this_activity) do
+ with true <- client_posted_this_activity,
+ %{data: %{"listMessage" => list_ap_id}} when is_binary(list_ap_id) <- object,
+ %{id: list_id} <- Pleroma.List.get_by_ap_id(list_ap_id) do
+ list_id
+ else
+ _ -> nil
+ end
+ end
end
diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.Metadata do
def build_tags(params) do
providers = [
+ Pleroma.Web.Metadata.Providers.ActivityPub,
Pleroma.Web.Metadata.Providers.RelMe,
Pleroma.Web.Metadata.Providers.RestrictIndexing
| activated_providers()
diff --git a/lib/pleroma/web/metadata/providers/activity_pub.ex b/lib/pleroma/web/metadata/providers/activity_pub.ex
@@ -0,0 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Providers.ActivityPub do
+ alias Pleroma.Web.Metadata.Providers.Provider
+
+ @behaviour Provider
+
+ @impl Provider
+ def build_tags(%{object: %{data: %{"id" => object_id}}}) do
+ [{:link, [rel: "alternate", type: "application/activity+json", href: object_id], []}]
+ end
+
+ @impl Provider
+ def build_tags(%{user: user}) do
+ [{:link, [rel: "alternate", type: "application/activity+json", href: user.ap_id], []}]
+ end
+
+ @impl Provider
+ def build_tags(_), do: []
+end
diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do
@behaviour Provider
@impl Provider
- def build_tags(%{user: user}) do
+ def build_tags(%{user: %{local: true} = user}) do
[
{:link,
[
@@ -20,4 +20,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do
], []}
]
end
+
+ @impl Provider
+ def build_tags(_), do: []
end
diff --git a/lib/pleroma/web/metadata/providers/open_graph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -67,6 +67,9 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
end
end
+ @impl Provider
+ def build_tags(_), do: []
+
defp build_attachments(%{data: %{"attachment" => attachments}}) do
Enum.reduce(attachments, [], fn attachment, acc ->
rendered_tags =
diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex
@@ -20,6 +20,9 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do
end)
end
+ @impl Provider
+ def build_tags(_), do: []
+
defp append_fields_tag(bio, fields) do
fields
|> Enum.reduce(bio, fn %{"value" => v}, res -> res <> v end)
diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -44,6 +44,9 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end
end
+ @impl Provider
+ def build_tags(_), do: []
+
defp title_tag(user) do
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}
end
diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.App do
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.OAuth.Token
@type t :: %__MODULE__{}
@@ -155,4 +156,29 @@ defmodule Pleroma.Web.OAuth.App do
Map.put(acc, key, error)
end)
end
+
+ @spec maybe_update_owner(Token.t()) :: :ok
+ def maybe_update_owner(%Token{app_id: app_id, user_id: user_id}) when not is_nil(user_id) do
+ __MODULE__.update(app_id, %{user_id: user_id})
+
+ :ok
+ end
+
+ def maybe_update_owner(_), do: :ok
+
+ @spec remove_orphans(pos_integer()) :: :ok
+ def remove_orphans(limit \\ 100) do
+ fifteen_mins_ago = DateTime.add(DateTime.utc_now(), -900, :second)
+
+ Repo.transaction(fn ->
+ from(a in __MODULE__,
+ where: is_nil(a.user_id) and a.inserted_at < ^fifteen_mins_ago,
+ limit: ^limit
+ )
+ |> Repo.all()
+ |> Enum.each(&Repo.delete(&1))
+ end)
+
+ :ok
+ end
end
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -318,6 +318,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
+ App.maybe_update_owner(token)
+
conn
|> AuthHelper.put_session_token(token.token)
|> json(OAuthView.render("token.json", view_params))
diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex
@@ -100,11 +100,10 @@ defmodule Pleroma.Web.OAuth.Token do
def create(%App{} = app, %User{} = user, attrs \\ %{}) do
with {:ok, token} <- do_create(app, user, attrs) do
if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
- Pleroma.Workers.PurgeExpiredToken.enqueue(%{
- token_id: token.id,
- valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
- mod: __MODULE__
- })
+ Pleroma.Workers.PurgeExpiredToken.new(%{token_id: token.id, mod: __MODULE__},
+ scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC")
+ )
+ |> Oban.insert()
end
{:ok, token}
diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
@@ -38,8 +38,8 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
|> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@")))
|> Enum.reject(&(&1 == ""))
- User.Import.follow_import(follower, identifiers)
- json(conn, "job started")
+ User.Import.follows_import(follower, identifiers)
+ json(conn, "jobs started")
end
def blocks(
@@ -55,7 +55,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
defp do_block(%{assigns: %{user: blocker}} = conn, list) do
User.Import.blocks_import(blocker, prepare_user_identifiers(list))
- json(conn, "job started")
+ json(conn, "jobs started")
end
def mutes(
@@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
defp do_mute(%{assigns: %{user: user}} = conn, list) do
User.Import.mutes_import(user, prepare_user_identifiers(list))
- json(conn, "job started")
+ json(conn, "jobs started")
end
defp prepare_user_identifiers(list) do
diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
@@ -47,6 +47,11 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
end
+ def checkpw(password, "$argon2" <> _ = password_hash) do
+ # Handle argon2 passwords for Akkoma migration
+ Argon2.verify_pass(password, password_hash)
+ end
+
def checkpw(_password, _password_hash) do
Logger.error("Password hash not recognized")
false
@@ -56,6 +61,10 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
do_update_password(user, password)
end
+ def maybe_update_password(%User{password_hash: "$argon2" <> _} = user, password) do
+ do_update_password(user, password)
+ end
+
def maybe_update_password(user, _), do: {:ok, user}
defp do_update_password(user, password) do
diff --git a/lib/pleroma/web/plugs/inbox_guard_plug.ex b/lib/pleroma/web/plugs/inbox_guard_plug.ex
@@ -0,0 +1,89 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.InboxGuardPlug do
+ import Plug.Conn
+ import Pleroma.Constants, only: [activity_types: 0, allowed_activity_types_from_strangers: 0]
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
+ with {_, true} <- {:federating, Config.get!([:instance, :federating])} do
+ conn
+ |> filter_activity_types()
+ else
+ {:federating, false} ->
+ conn
+ |> json(403, "Not federating")
+ |> halt()
+ end
+ end
+
+ def call(conn, _opts) do
+ with {_, true} <- {:federating, Config.get!([:instance, :federating])},
+ conn = filter_activity_types(conn),
+ {:known, true} <- {:known, known_actor?(conn)} do
+ conn
+ else
+ {:federating, false} ->
+ conn
+ |> json(403, "Not federating")
+ |> halt()
+
+ {:known, false} ->
+ conn
+ |> filter_from_strangers()
+ end
+ end
+
+ # Early rejection of unrecognized types
+ defp filter_activity_types(%{body_params: %{"type" => type}} = conn) do
+ with true <- type in activity_types() do
+ conn
+ else
+ _ ->
+ conn
+ |> json(400, "Invalid activity type")
+ |> halt()
+ end
+ end
+
+ # If signature failed but we know this actor we should
+ # accept it as we may only need to refetch their public key
+ # during processing
+ defp known_actor?(%{body_params: data}) do
+ case Pleroma.Object.Containment.get_actor(data) |> User.get_cached_by_ap_id() do
+ %User{} -> true
+ _ -> false
+ end
+ end
+
+ # Only permit a subset of activity types from strangers
+ # or else it will add actors you've never interacted with
+ # to the database
+ defp filter_from_strangers(%{body_params: %{"type" => type}} = conn) do
+ with true <- type in allowed_activity_types_from_strangers() do
+ conn
+ else
+ _ ->
+ conn
+ |> json(400, "Invalid activity type for an unknown actor")
+ |> halt()
+ end
+ end
+
+ defp json(conn, status, resp) do
+ json_resp = Jason.encode!(resp)
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> resp(status, json_resp)
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/push.ex b/lib/pleroma/web/push.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.Push do
end
def vapid_config do
- Application.get_env(:web_push_encryption, :vapid_details, nil)
+ Application.get_env(:web_push_encryption, :vapid_details, [])
end
def enabled, do: match?([subject: _, public_key: _, private_key: _], vapid_config())
@@ -28,6 +28,7 @@ defmodule Pleroma.Web.Push do
@spec send(Pleroma.Notification.t()) ::
{:ok, Oban.Job.t()} | {:error, Oban.Job.changeset() | term()}
def send(notification) do
- WebPusherWorker.enqueue("web_push", %{"notification_id" => notification.id})
+ WebPusherWorker.new(%{"op" => "web_push", "notification_id" => notification.id})
+ |> Oban.insert()
end
end
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
@@ -11,16 +11,39 @@ defmodule Pleroma.Web.RichMedia.Helpers do
@spec rich_media_get(String.t()) :: {:ok, String.t()} | get_errors()
def rich_media_get(url) do
- headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
+ case Pleroma.HTTP.AdapterHelper.can_stream?() do
+ true -> stream(url)
+ false -> head_first(url)
+ end
+ |> handle_result(url)
+ end
+
+ defp stream(url) do
+ with {_, {:ok, %Tesla.Env{status: 200, body: stream_body, headers: headers}}} <-
+ {:get, Pleroma.HTTP.get(url, req_headers(), http_options())},
+ {_, :ok} <- {:content_type, check_content_type(headers)},
+ {_, :ok} <- {:content_length, check_content_length(headers)},
+ {:read_stream, {:ok, body}} <- {:read_stream, read_stream(stream_body)} do
+ {:ok, body}
+ end
+ end
+ defp head_first(url) do
with {_, {:ok, %Tesla.Env{status: 200, headers: headers}}} <-
- {:head, Pleroma.HTTP.head(url, headers, http_options())},
+ {:head, Pleroma.HTTP.head(url, req_headers(), http_options())},
{_, :ok} <- {:content_type, check_content_type(headers)},
{_, :ok} <- {:content_length, check_content_length(headers)},
{_, {:ok, %Tesla.Env{status: 200, body: body}}} <-
- {:get, Pleroma.HTTP.get(url, headers, http_options())} do
+ {:get, Pleroma.HTTP.get(url, req_headers(), http_options())} do
{:ok, body}
- else
+ end
+ end
+
+ defp handle_result(result, url) do
+ case result do
+ {:ok, body} ->
+ {:ok, body}
+
{:head, _} ->
Logger.debug("Rich media error for #{url}: HTTP HEAD failed")
{:error, :head}
@@ -29,8 +52,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do
Logger.debug("Rich media error for #{url}: content-type is #{type}")
{:error, :content_type}
- {:content_length, {_, length}} ->
- Logger.debug("Rich media error for #{url}: content-length is #{length}")
+ {:content_length, :error} ->
+ Logger.debug("Rich media error for #{url}: content-length exceeded")
+ {:error, :body_too_large}
+
+ {:read_stream, :error} ->
+ Logger.debug("Rich media error for #{url}: content-length exceeded")
{:error, :body_too_large}
{:get, _} ->
@@ -59,7 +86,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
{_, maybe_content_length} ->
case Integer.parse(maybe_content_length) do
{content_length, ""} when content_length <= max_body -> :ok
- {_, ""} -> {:error, maybe_content_length}
+ {_, ""} -> :error
_ -> :ok
end
@@ -68,13 +95,37 @@ defmodule Pleroma.Web.RichMedia.Helpers do
end
end
- defp http_options do
- timeout = Config.get!([:rich_media, :timeout])
+ defp read_stream(stream) do
+ max_body = Keyword.get(http_options(), :max_body)
+
+ try do
+ result =
+ Stream.transform(stream, 0, fn chunk, total_bytes ->
+ new_total = total_bytes + byte_size(chunk)
+
+ if new_total > max_body do
+ raise("Exceeds max body limit of #{max_body}")
+ else
+ {[chunk], new_total}
+ end
+ end)
+ |> Enum.into(<<>>)
+ {:ok, result}
+ rescue
+ _ -> :error
+ end
+ end
+
+ defp http_options do
[
pool: :rich_media,
max_body: Config.get([:rich_media, :max_body], 5_000_000),
- tesla_middleware: [{Tesla.Middleware.Timeout, timeout: timeout}]
+ stream: true
]
end
+
+ defp req_headers do
+ [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
@@ -217,6 +217,10 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
end
+ pipeline :inbox_guard do
+ plug(Pleroma.Web.Plugs.InboxGuardPlug)
+ end
+
pipeline :static_fe do
plug(Pleroma.Web.Plugs.StaticFEPlug)
end
@@ -648,7 +652,6 @@ defmodule Pleroma.Web.Router do
get("/accounts/relationships", AccountController, :relationships)
get("/accounts/familiar_followers", AccountController, :familiar_followers)
get("/accounts/:id/lists", AccountController, :lists)
- get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/endorsements", AccountController, :endorsements)
get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes)
@@ -921,7 +924,7 @@ defmodule Pleroma.Web.Router do
end
scope "/", Pleroma.Web.ActivityPub do
- pipe_through(:activitypub)
+ pipe_through([:activitypub, :inbox_guard])
post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Healthcheck
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
@@ -195,19 +196,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
%{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn,
_
) do
- case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
- {:ok, user} ->
- with {:ok, _user} <-
- User.reset_password(user, %{
- password: body_params.new_password,
- password_confirmation: body_params.new_password_confirmation
- }) do
- json(conn, %{status: "success"})
- else
- {:error, changeset} ->
- {_, {error, _}} = Enum.at(changeset.errors, 0)
- json(conn, %{error: "New password #{error}."})
- end
+ with {:ok, %User{}} <-
+ Authenticator.change_password(
+ user,
+ body_params.password,
+ body_params.new_password,
+ body_params.new_password_confirmation
+ ) do
+ json(conn, %{status: "success"})
+ else
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {_, {error, _}} = Enum.at(changeset.errors, 0)
+ json(conn, %{error: "New password #{error}."})
+
+ {:error, :password_confirmation} ->
+ json(conn, %{error: "New password does not match confirmation."})
{:error, msg} ->
json(conn, %{error: msg})
diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/twitter_api/views/token_view.ex
@@ -15,7 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.TokenView do
%{
id: token_entry.id,
valid_until: token_entry.valid_until,
- app_name: token_entry.app.client_name
+ app_name: token_entry.app.client_name,
+ scopes: token_entry.scopes
}
end
end
diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex
@@ -109,7 +109,25 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!()
end
- def render("follow_relationships_update.json", item, topic) do
+ def render(
+ "follow_relationships_update.json",
+ %{follower: follower, following: following} = item,
+ topic
+ ) do
+ following_follower_count =
+ if Enum.any?([following.hide_followers_count, following.hide_followers]) do
+ 0
+ else
+ following.follower_count
+ end
+
+ following_following_count =
+ if Enum.any?([following.hide_follows_count, following.hide_follows]) do
+ 0
+ else
+ following.following_count
+ end
+
%{
stream: render("stream.json", %{topic: topic}),
event: "pleroma:follow_relationships_update",
@@ -117,14 +135,14 @@ defmodule Pleroma.Web.StreamerView do
%{
state: item.state,
follower: %{
- id: item.follower.id,
- follower_count: item.follower.follower_count,
- following_count: item.follower.following_count
+ id: follower.id,
+ follower_count: follower.follower_count,
+ following_count: follower.following_count
},
following: %{
- id: item.following.id,
- follower_count: item.following.follower_count,
- following_count: item.following.following_count
+ id: following.id,
+ follower_count: following_follower_count,
+ following_count: following_following_count
}
}
|> Jason.encode!()
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -8,9 +8,9 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
alias Pleroma.Object
alias Pleroma.Repo
- use Pleroma.Workers.WorkerHelper, queue: "slow"
+ use Oban.Worker, queue: :slow
- @impl Oban.Worker
+ @impl true
def perform(%Job{
args: %{
"op" => "cleanup_attachments",
@@ -31,7 +31,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(900)
defp do_clean({object_ids, attachment_urls}) do
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Workers.BackgroundWorker do
alias Pleroma.User
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "user_activation", "user_id" => user_id, "status" => status}}) do
user = User.get_cached_by_id(user_id)
@@ -19,10 +19,10 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:force_password_reset, user)
end
- def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}})
- when op in ["blocks_import", "follow_import", "mutes_import"] do
+ def perform(%Job{args: %{"op" => op, "user_id" => user_id, "actor" => actor}})
+ when op in ["block_import", "follow_import", "mute_import"] do
user = User.get_cached_by_id(user_id)
- {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)}
+ User.Import.perform(String.to_existing_atom(op), user, actor)
end
def perform(%Job{
@@ -39,6 +39,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:verify_fields_links, user)
end
- @impl Oban.Worker
- def timeout(_job), do: :timer.seconds(15)
+ @impl true
+ def timeout(_job), do: :timer.seconds(900)
end
diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Workers.BackupWorker do
alias Pleroma.Config.Getting, as: Config
alias Pleroma.User.Backup
- @impl Oban.Worker
+ @impl true
def perform(%Job{
args: %{"op" => "process", "backup_id" => backup_id}
}) do
@@ -32,7 +32,7 @@ defmodule Pleroma.Workers.BackupWorker do
end
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: Config.get([Backup, :timeout], :timer.minutes(30))
defp has_email?(user) do
diff --git a/lib/pleroma/workers/cron/app_cleanup_worker.ex b/lib/pleroma/workers/cron/app_cleanup_worker.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.AppCleanupWorker do
+ @moduledoc """
+ Cleans up registered apps that were never associated with a user.
+ """
+
+ use Oban.Worker, queue: "background"
+
+ alias Pleroma.Web.OAuth.App
+
+ @impl true
+ def perform(_job) do
+ App.remove_orphans()
+ end
+
+ @impl true
+ def timeout(_job), do: :timer.seconds(30)
+end
diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex
@@ -18,7 +18,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
require Logger
- @impl Oban.Worker
+ @impl true
def perform(_job) do
config = Config.get([:email_notifications, :digest])
@@ -59,6 +59,6 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
User.touch_last_digest_emailed_at(user)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -9,9 +9,9 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
import Ecto.Query
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background
- @impl Oban.Worker
+ @impl true
def perform(_job) do
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
@@ -61,6 +61,6 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
:ok
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/delete_worker.ex b/lib/pleroma/workers/delete_worker.ex
@@ -6,10 +6,9 @@ defmodule Pleroma.Workers.DeleteWorker do
alias Pleroma.Instances.Instance
alias Pleroma.User
- use Pleroma.Workers.WorkerHelper, queue: "slow"
-
- @impl Oban.Worker
+ use Oban.Worker, queue: :slow
+ @impl true
def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
User.perform(:delete, user)
@@ -19,6 +18,6 @@ defmodule Pleroma.Workers.DeleteWorker do
Instance.perform(:delete_instance, host)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(900)
end
diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MailerWorker do
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
encoded_email
|> Base.decode64!()
@@ -13,6 +13,6 @@ defmodule Pleroma.Workers.MailerWorker do
|> Pleroma.Emails.Mailer.deliver(config)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.MuteExpireWorker do
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
Pleroma.User.unmute(muter_id, mutee_id)
:ok
@@ -18,6 +18,6 @@ defmodule Pleroma.Workers.MuteExpireWorker do
:ok
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex
@@ -6,32 +6,51 @@ defmodule Pleroma.Workers.PollWorker do
@moduledoc """
Generates notifications when a poll ends.
"""
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
+ alias Pleroma.Object.Fetcher
- @impl Oban.Worker
+ @stream_out_impl Pleroma.Config.get(
+ [__MODULE__, :stream_out],
+ Pleroma.Web.ActivityPub.ActivityPub
+ )
+
+ @impl true
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
- with %Activity{} = activity <- find_poll_activity(activity_id),
+ with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)},
{:ok, notifications} <- Notification.create_poll_notifications(activity) do
+ unless activity.local do
+ # Schedule a final refresh
+ __MODULE__.new(%{"op" => "refresh", "activity_id" => activity_id})
+ |> Oban.insert()
+ end
+
Notification.stream(notifications)
else
- {:error, :poll_activity_not_found} = e -> {:cancel, e}
+ {:activity, nil} -> {:cancel, :poll_activity_not_found}
e -> {:error, e}
end
end
- @impl Oban.Worker
- def timeout(_job), do: :timer.seconds(5)
+ def perform(%Job{args: %{"op" => "refresh", "activity_id" => activity_id}}) do
+ with {_, %Activity{object: object}} <-
+ {:activity, Activity.get_by_id_with_object(activity_id)},
+ {_, {:ok, _object}} <- {:refetch, Fetcher.refetch_object(object)} do
+ stream_update(activity_id)
- defp find_poll_activity(activity_id) do
- with nil <- Activity.get_by_id(activity_id) do
- {:error, :poll_activity_not_found}
+ :ok
+ else
+ {:activity, nil} -> {:cancel, :poll_activity_not_found}
+ {:refetch, _} = e -> {:cancel, e}
end
end
+ @impl true
+ def timeout(_job), do: :timer.seconds(5)
+
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
Object.normalize(activity),
@@ -49,4 +68,10 @@ defmodule Pleroma.Workers.PollWorker do
end
def schedule_poll_end(activity), do: {:error, activity}
+
+ defp stream_update(activity_id) do
+ Activity.get_by_id(activity_id)
+ |> Activity.normalize()
+ |> @stream_out_impl.stream_out()
+ end
end
diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex
@@ -6,13 +6,9 @@ defmodule Pleroma.Workers.PublisherWorker do
alias Pleroma.Activity
alias Pleroma.Web.Federator
- use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
+ use Oban.Worker, queue: :federator_outgoing, max_attempts: 5
- def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
- Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5)
- end
-
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
Federator.perform(:publish, activity)
@@ -23,6 +19,18 @@ defmodule Pleroma.Workers.PublisherWorker do
Federator.perform(:publish_one, params)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(10)
+
+ @base_backoff 15
+ @pow 5
+ @impl true
+ def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
+ backoff =
+ :math.pow(attempt, @pow) +
+ @base_backoff +
+ :rand.uniform(2 * @base_backoff) * attempt
+
+ trunc(backoff)
+ end
end
diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex
@@ -13,16 +13,13 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do
alias Pleroma.Activity
- @spec enqueue(map()) ::
+ @spec enqueue(map(), list()) ::
{:ok, Oban.Job.t()}
| {:error, :expired_activities_disabled}
| {:error, :expiration_too_close}
- def enqueue(args) do
+ def enqueue(params, worker_args) do
with true <- enabled?() do
- {scheduled_at, args} = Map.pop(args, :expires_at)
-
- args
- |> new(scheduled_at: scheduled_at)
+ new(params, worker_args)
|> Oban.insert()
end
end
@@ -35,7 +32,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do
end
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
defp enabled? do
diff --git a/lib/pleroma/workers/purge_expired_filter.ex b/lib/pleroma/workers/purge_expired_filter.ex
@@ -31,7 +31,7 @@ defmodule Pleroma.Workers.PurgeExpiredFilter do
|> Repo.delete()
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
@spec get_expiration(pos_integer()) :: Job.t() | nil
diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex
@@ -9,16 +9,6 @@ defmodule Pleroma.Workers.PurgeExpiredToken do
use Oban.Worker, queue: :background, max_attempts: 1
- @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) ::
- {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
- def enqueue(args) do
- {scheduled_at, args} = Map.pop(args, :valid_until)
-
- args
- |> __MODULE__.new(scheduled_at: scheduled_at)
- |> Oban.insert()
- end
-
@impl true
def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do
module
@@ -27,6 +17,6 @@ defmodule Pleroma.Workers.PurgeExpiredToken do
|> Pleroma.Repo.delete()
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Workers.ReceiverWorker do
alias Pleroma.User
alias Pleroma.Web.Federator
- use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
+ use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
- @impl Oban.Worker
+ @impl true
def perform(%Job{
args: %{
@@ -33,7 +33,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
query_string: query_string
}
- with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
+ with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
@@ -51,22 +51,34 @@ defmodule Pleroma.Workers.ReceiverWorker do
end
end
- @impl Oban.Worker
+ @impl true
def timeout(%_{args: %{"timeout" => timeout}}), do: timeout
def timeout(_job), do: :timer.seconds(5)
+ defp process_errors({:error, {:error, _} = error}), do: process_errors(error)
+
defp process_errors(errors) do
case errors do
- {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
+ # User fetch failures
+ {:error, :not_found} = reason -> {:cancel, reason}
+ {:error, :forbidden} = reason -> {:cancel, reason}
+ # Inactive user
+ {:error, {:user_active, false} = reason} -> {:cancel, reason}
+ # Validator will error and return a changeset error
+ # e.g., duplicate activities or if the object was deleted
+ {:error, {:validate, {:error, _changeset} = reason}} -> {:cancel, reason}
+ # Duplicate detection during Normalization
{:error, :already_present} -> {:cancel, :already_present}
- {:error, {:validate_object, _} = reason} -> {:cancel, reason}
- {:error, {:error, {:validate, {:error, _changeset} = reason}}} -> {:cancel, reason}
+ # MRFs will return a reject
{:error, {:reject, _} = reason} -> {:cancel, reason}
+ # HTTP Sigs
{:signature, false} -> {:cancel, :invalid_signature}
- {:error, "Object has been deleted"} = reason -> {:cancel, reason}
+ # Origin / URL validation failed somewhere possibly due to spoofing
+ {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
+ # Unclear if this can be reached
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
- {:error, :not_found} = reason -> {:cancel, reason}
+ # Catchall
{:error, _} = e -> e
e -> {:error, e}
end
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -5,31 +5,40 @@
defmodule Pleroma.Workers.RemoteFetcherWorker do
alias Pleroma.Object.Fetcher
- use Pleroma.Workers.WorkerHelper, queue: "background"
+ use Oban.Worker, queue: :background, unique: [period: :infinity]
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
{:ok, _object} ->
:ok
- {:reject, reason} ->
+ {:allowed_depth, false} ->
+ {:cancel, :allowed_depth}
+
+ {:containment, reason} ->
{:cancel, reason}
- {:error, :forbidden} ->
- {:cancel, :forbidden}
+ {:transmogrifier, reason} ->
+ {:cancel, reason}
- {:error, :not_found} ->
- {:cancel, :not_found}
+ {:fetch, {:error, :forbidden = reason}} ->
+ {:cancel, reason}
- {:error, :allowed_depth} ->
- {:cancel, :allowed_depth}
+ {:fetch, {:error, :not_found = reason}} ->
+ {:cancel, reason}
+
+ {:fetch, {:error, {:content_type, _}} = reason} ->
+ {:cancel, reason}
+
+ {:fetch, {:error, reason}} ->
+ {:error, reason}
{:error, _} = e ->
e
end
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(15)
end
diff --git a/lib/pleroma/workers/rich_media_worker.ex b/lib/pleroma/workers/rich_media_worker.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Workers.RichMediaWorker do
alias Pleroma.Web.RichMedia.Backfill
alias Pleroma.Web.RichMedia.Card
- use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300]
+ use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: :infinity]
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do
Card.delete(url)
end
@@ -33,7 +33,7 @@ defmodule Pleroma.Workers.RichMediaWorker do
# a slow/infinite data stream and insert a negative cache entry for the URL
# We pad it by 2 seconds to be certain a slow connection is detected and we
# can inject a negative cache entry for the URL
- @impl Oban.Worker
+ @impl true
def timeout(_job) do
Config.get!([:rich_media, :timeout]) + :timer.seconds(2)
end
diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
The worker to post scheduled activity.
"""
- use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
+ use Oban.Worker, queue: :federator_outgoing, max_attempts: 5
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
@@ -15,7 +15,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
require Logger
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"activity_id" => activity_id}}) do
with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
%User{} = user <- find_user(scheduled_activity.user_id) do
@@ -37,7 +37,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
end
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
defp find_scheduled_activity(id) do
diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex
@@ -1,7 +1,7 @@
defmodule Pleroma.Workers.SearchIndexingWorker do
- use Pleroma.Workers.WorkerHelper, queue: "search_indexing"
+ use Oban.Worker, queue: :search_indexing, max_attempts: 2
- @impl Oban.Worker
+ @impl true
alias Pleroma.Config.Getting, as: Config
@@ -21,6 +21,6 @@ defmodule Pleroma.Workers.SearchIndexingWorker do
search_module.remove_from_index(object)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.UserRefreshWorker do
- use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: 300]
+ use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity]
alias Pleroma.User
@@ -12,6 +12,6 @@ defmodule Pleroma.Workers.UserRefreshWorker do
User.fetch_by_ap_id(ap_id)
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(15)
end
diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Workers.WebPusherWorker do
alias Pleroma.Repo
alias Pleroma.Web.Push.Impl
- use Pleroma.Workers.WorkerHelper, queue: "web_push"
+ use Oban.Worker, queue: :web_push, unique: [period: :infinity]
- @impl Oban.Worker
+ @impl true
def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do
notification =
Notification
@@ -20,6 +20,6 @@ defmodule Pleroma.Workers.WebPusherWorker do
|> Enum.each(&Impl.deliver(&1))
end
- @impl Oban.Worker
+ @impl true
def timeout(_job), do: :timer.seconds(5)
end
diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex
@@ -1,48 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.WorkerHelper do
- alias Pleroma.Config
- alias Pleroma.Workers.WorkerHelper
-
- def worker_args(queue) do
- case Config.get([:workers, :retries, queue]) do
- nil -> []
- max_attempts -> [max_attempts: max_attempts]
- end
- end
-
- def sidekiq_backoff(attempt, pow \\ 4, base_backoff \\ 15) do
- backoff =
- :math.pow(attempt, pow) +
- base_backoff +
- :rand.uniform(2 * base_backoff) * attempt
-
- trunc(backoff)
- end
-
- defmacro __using__(opts) do
- caller_module = __CALLER__.module
- queue = Keyword.fetch!(opts, :queue)
-
- quote do
- # Note: `max_attempts` is intended to be overridden in `new/2` call
- use Oban.Worker,
- queue: unquote(queue),
- max_attempts: 1
-
- alias Oban.Job
-
- def enqueue(op, params, worker_args \\ []) do
- params = Map.merge(%{"op" => op}, params)
- queue_atom = String.to_atom(unquote(queue))
- worker_args = worker_args ++ WorkerHelper.worker_args(queue_atom)
-
- unquote(caller_module)
- |> apply(:new, [params, worker_args])
- |> Oban.insert()
- end
- end
- end
-end
diff --git a/mix.exs b/mix.exs
@@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("2.7.1"),
- elixir: "~> 1.13",
+ version: version("2.8.0"),
+ elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(), prune_code_paths: false],
@@ -145,7 +145,7 @@ defmodule Pleroma.Mixfile do
{:telemetry_poller, "~> 1.0"},
{:tzdata, "~> 1.0.3"},
{:plug_cowboy, "~> 2.5"},
- {:oban, "~> 2.17.9"},
+ {:oban, "~> 2.18.0"},
{:gettext, "~> 0.20"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
@@ -154,12 +154,12 @@ defmodule Pleroma.Mixfile do
{:calendar, "~> 1.0"},
{:cachex, "~> 3.2"},
{:tesla, "~> 1.11"},
- {:castore, "~> 0.1"},
+ {:castore, "~> 1.0"},
{:cowlib, "~> 2.9", override: true},
{:gun, "~> 2.0.0-rc.1", override: true},
{:finch, "~> 0.15"},
{:jason, "~> 1.2"},
- {:mogrify, "~> 0.8.0"},
+ {:mogrify, "~> 0.9.0", override: "true"},
{:ex_aws, "~> 2.1.6"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.7.2"},
@@ -170,6 +170,8 @@ defmodule Pleroma.Mixfile do
{:swoosh, "~> 1.16.9"},
{:phoenix_swoosh, "~> 1.1"},
{:gen_smtp, "~> 0.13"},
+ {:mua, "~> 0.2.0"},
+ {:mail, "~> 0.3.0"},
{:ex_syslogger, "~> 1.4"},
{:floki, "~> 0.35"},
{:timex, "~> 3.6"},
@@ -204,6 +206,7 @@ defmodule Pleroma.Mixfile do
{:websock_adapter, "~> 0.5.6"},
{:oban_live_dashboard, "~> 0.1.1"},
{:multipart, "~> 0.4.0", optional: true},
+ {:argon2_elixir, "~> 4.0"},
## dev & test
{:phoenix_live_reload, "~> 1.3.3", only: :dev},
diff --git a/mix.lock b/mix.lock
@@ -1,5 +1,6 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
+ "argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"},
"bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
@@ -10,7 +11,7 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
- "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
+ "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
@@ -49,7 +50,7 @@
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"exile": {:hex, :exile, "0.10.0", "b69e2d27a9af670b0f0a0898addca0eda78f6f5ba95ccfbc9bc6ccdd04925436", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c62ee8fee565b5ac4a898d0dcd58d2b04fb5eec1655af1ddcc9eb582c6732c33"},
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
- "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"},
+ "fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
@@ -65,12 +66,13 @@
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
- "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
+ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
"logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"},
+ "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
@@ -82,14 +84,15 @@
"mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
- "mogrify": {:hex, :mogrify, "0.8.0", "3506f3ca3f7b95a155f3b4ef803b5db176f5a0633723e3fe85e0d6399e3b11c8", [:mix], [], "hexpm", "2278d245f07056ea3b586e98801e933695147066fa4cf563f552c1b4f0ff8ad9"},
+ "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
+ "mua": {:hex, :mua, "0.2.3", "46b29b7b2bb14105c0b7be9526f7c452df17a7841b30b69871c024a822ff551c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "7fe861a87fcc06a980d3941bbcb2634e5f0f30fd6ad15ef6c0423ff9dc7e46de"},
"multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
- "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"},
+ "oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"},
"oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"},
"octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"},
"open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"},
diff --git a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po
@@ -3,14 +3,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-22 02:09+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Automatically generated\n"
-"Language-Team: none\n"
+"PO-Revision-Date: 2024-10-13 21:03+0000\n"
+"Last-Translator: Codimp <contact@lithio.fr>\n"
+"Language-Team: French <https://translate.pleroma.social/projects/pleroma/"
+"pleroma-backend-domain-config_descriptions/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Translate Toolkit 3.7.2\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.13.1\n"
## This file is a PO Template file.
##
@@ -21,7 +23,6 @@ msgstr ""
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
-
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :esshd"
@@ -32,25 +33,30 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :logger"
msgid "Logger-related settings"
-msgstr ""
+msgstr "Paramètres liés à la journalisation"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :mime"
msgid "Mime Types settings"
-msgstr ""
+msgstr "Paramètres des types Mime"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma"
msgid "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)"
msgstr ""
+"Permet de configurer un jeton qui peut être utilisé pour authentifier les "
+"requêtes avec des privilèges administrateurs sans utiliser un jeton de "
+"compte utilisateur standard. Pour l'utiliser, ajoutez le paramètre "
+"`admin_token`aux requêtes. (Vous devriez utiliser l'authentification HTTP "
+"Basic ou OAuth à la place si vous le pouvez)"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma"
msgid "Authenticator"
-msgstr ""
+msgstr "Authentifieur"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -62,7 +68,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config label at :cors_plug"
msgid "CORS plug config"
-msgstr ""
+msgstr "Configuration du plug CORS"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -74,25 +80,25 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config label at :logger"
msgid "Logger"
-msgstr ""
+msgstr "Journaliseur"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :mime"
msgid "Mime Types"
-msgstr ""
+msgstr "Types Mime"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma"
msgid "Pleroma Admin Token"
-msgstr ""
+msgstr "Jeton Administrateur Pleroma"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma"
msgid "Pleroma Authenticator"
-msgstr ""
+msgstr "Authentifieur Pleroma"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -104,103 +110,111 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :logger-:console"
msgid "Console logger settings"
-msgstr ""
+msgstr "Paramètres de journalisation de la console"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :logger-:ex_syslogger"
msgid "ExSyslogger-related settings"
-msgstr ""
+msgstr "Paramètres liés à ExSyslogger"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:activitypub"
msgid "ActivityPub-related settings"
-msgstr ""
+msgstr "Paramètres liés à ActivityPub"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:assets"
msgid "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend"
msgstr ""
+"Cette section configure les annexes (assets) à utiliser avec divers "
+"frontaux. La seule option est actuellement liée au mascottes du frontal "
+"mastodon"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:auth"
msgid "Authentication / authorization settings"
-msgstr ""
+msgstr "Paramètres d'authentification/autorisations"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:connections_pool"
msgid "Advanced settings for `Gun` connections pool"
-msgstr ""
+msgstr "Paramètres avancés pour le bac (pool) de connexions `Gun`"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:email_notifications"
msgid "Email notifications settings"
-msgstr ""
+msgstr "Paramètres de notification par email"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:features"
msgid "Customizable features"
-msgstr ""
+msgstr "Fonctionnalités personnalisables"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:feed"
msgid "Configure feed rendering"
-msgstr ""
+msgstr "Configurer le rendu des flux"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontend_configurations"
msgid "This form can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for pleroma_fe are configured. If you want to add your own configuration your settings all fields must be complete."
msgstr ""
+"Ce formulaire peut être utilisé pour configurer une liste de clés (keyword) "
+"qui contiennent les données de configuration pour tout types de frontaux. "
+"Par défaut, les paramètres pour pleroma_fe sont configurés. Si vous voulez "
+"ajouter vos propres paramètres de configurations, tout les champs doivent "
+"être remplis."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends"
msgid "Installed frontends management"
-msgstr ""
+msgstr "Gestion des frontaux installés"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:gopher"
msgid "Gopher settings"
-msgstr ""
+msgstr "Paramètres Gopher"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:hackney_pools"
msgid "Advanced settings for `Hackney` connections pools"
-msgstr ""
+msgstr "Paramètres avancés pour les bacs (pool) de connexions `Hackney`"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:http"
msgid "HTTP settings"
-msgstr ""
+msgstr "Paramètres HTTP"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:http_security"
msgid "HTTP security settings"
-msgstr ""
+msgstr "Paramètres de sécurité HTTP"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance"
msgid "Instance-related settings"
-msgstr ""
+msgstr "Paramètres liés à l'instance"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instances_favicons"
msgid "Control favicons for instances"
-msgstr ""
+msgstr "Gère les favicons des instances"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -212,151 +226,177 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:majic_pool"
msgid "Majic/libmagic configuration"
-msgstr ""
+msgstr "Configuration de majic/libmagic"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:manifest"
msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE."
msgstr ""
+"Cette section décrit les valeurs spécifique à l'instance du manifeste PWA. "
+"Actuellement, cette option ne concerne que MastoFE."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:media_preview_proxy"
msgid "Media preview proxy"
-msgstr ""
+msgstr "Proxy de prévisualisation média"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:media_proxy"
msgid "Media proxy"
-msgstr ""
+msgstr "Proxy média"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:modules"
msgid "Custom Runtime Modules"
-msgstr ""
+msgstr "Modules Runtime Personalisés"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf"
msgid "General MRF settings"
-msgstr ""
+msgstr "Paramètres généraux MRF"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_activity_expiration"
msgid "Adds automatic expiration to all local activities"
-msgstr ""
+msgstr "Ajoute une expiration automatique à toutes les activités locales"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_follow_bot"
msgid "Automatically follows newly discovered accounts."
-msgstr ""
+msgstr "Suivre automatiquement les comptes venant d'être découverts."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_hashtag"
msgid "Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)\n\nNote: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.\n"
msgstr ""
+"Rejeter, Enlever de TWKN ou marquer comme contenu sensible les messages avec "
+"des mots-croisillons (sans mettre le # du début)\n"
+"\n"
+"Note: cette politique MRF est toujours activée. Si vous voulez la "
+"désactiver, vous devez configurer des listes vides.\n"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_hellthread"
msgid "Block messages with excessive user mentions"
-msgstr ""
+msgstr "Bloquer les messages avec un nombre excessif de mentions"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_keyword"
msgid "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html)."
msgstr ""
+"Rejeter ou remplacer les mots des messages qui correspondent à un mot clef "
+"ou à une [expression rationnelle (Regex)](https://hexdocs.pm/elixir/Regex."
+"html)."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_mention"
msgid "Block messages which mention a specific user"
-msgstr ""
+msgstr "Bloquer les messages mentionnant un utilisateur particulier"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_normalize_markup"
msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup."
msgstr ""
+"Paramètres de normalisation MRF. Balaie les balises hypertextes configurées."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_object_age"
msgid "Rejects or delists posts based on their timestamp deviance from your server's clock."
msgstr ""
+"Rejette ou retire des listes les messages selon l'écart entre leur heure et "
+"l'horloge de votre serveur."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_rejectnonpublic"
msgid "RejectNonPublic drops posts with non-public visibility settings."
msgstr ""
+"RejectNonPublic enlève les messages avec des paramètres de visibilité non-"
+"publics."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_simple"
msgid "Simple ingress policies"
-msgstr ""
+msgstr "Politiques simples pour entrants"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_steal_emoji"
msgid "Steals emojis from selected instances when it sees them."
-msgstr ""
+msgstr "Vole les emojis des instances sélectionnées quand il les voit."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_subchain"
msgid "This policy processes messages through an alternate pipeline when a given message matches certain criteria. All criteria are configured as a map of regular expressions to lists of policy modules."
msgstr ""
+"Cette politique traite les messages à travers un tuyau séparé lorsqu'un "
+"message donné correspond à certain critères. Chaque critère est configuré "
+"comme une correspondance entre une expression rationnelle et une liste de "
+"modules de politiques."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_vocabulary"
msgid "Filter messages which belong to certain activity vocabularies"
msgstr ""
+"Filtrer les messages qui correspondent à certain vocabulaires d'activités"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:oauth2"
msgid "Configure OAuth 2 provider capabilities"
-msgstr ""
+msgstr "Configurer les capacités du fournisseur OAuth 2"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:pools"
msgid "Advanced settings for `Gun` workers pools"
-msgstr ""
+msgstr "Paramètres avancés pour les bacs (pools) de travailleurs `Gun`"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:populate_hashtags_table"
msgid "`populate_hashtags_table` background migration settings"
-msgstr ""
+msgstr "Paramètres de migration en arrière-plan `populate_hashtags_table`"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:rate_limit"
msgid "Rate limit settings. This is an advanced feature enabled only for :authentication by default."
msgstr ""
+"Paramètres de limites par secondes. C'est une fonctionnalité avancée qui, "
+"par défaut, n'est activée que pour :authentication."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:restrict_unauthenticated"
msgid "Disallow viewing timelines, user profiles and statuses for unauthenticated users."
msgstr ""
+"Empêche de regarder les flux, les profils utilisateurs et les status pour "
+"les utilisateurs non-authentifiés."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:rich_media"
msgid "If enabled the instance will parse metadata from attached links to generate link previews"
msgstr ""
+"Si activé, l'instance interprétera les métadonnées des liens joins pour "
+"générer les prévisualisations de liens"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -369,6 +409,8 @@ msgstr ""
msgctxt "config description at :pleroma-:static_fe"
msgid "Render profiles and posts using server-generated HTML that is viewable without using JavaScript"
msgstr ""
+"Rendre les profils et les status en utilisant du HTML généré par le serveur "
+"qui ne nécessitera pas de JavaScript"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -380,7 +422,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:uri_schemes"
msgid "URI schemes related settings"
-msgstr ""
+msgstr "Paramètres liés au schémas d'URI"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po b/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po
@@ -3,9 +3,9 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-21 04:21+0300\n"
-"PO-Revision-Date: 2022-07-24 10:04+0000\n"
-"Last-Translator: Yating Zhan <thestrandedvalley@protonmail.com>\n"
-"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/"
+"PO-Revision-Date: 2024-08-02 09:02+0000\n"
+"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n"
+"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
"projects/pleroma/pleroma-backend-domain-config_descriptions/zh_Hans/>\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
@@ -49,6 +49,8 @@ msgstr "Mime 类型设置"
msgctxt "config description at :pleroma"
msgid "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)"
msgstr ""
+"允许设置令牌以不使用普通用户令牌来授权管理员权限。在参数后加上 `admin_token` "
+"来启用该功能。(可用时可以考虑使用 HTTP Basic Auth 或 基于 OAuth 的鉴定方式)"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -126,7 +128,7 @@ msgstr "ActivityPub 相关设置"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:assets"
msgid "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend"
-msgstr ""
+msgstr "该部分配置不同前端使用的资源。目前该选项只对 Mastodon 前端的吉祥物有效"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -138,7 +140,7 @@ msgstr "鉴权/授权设置"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:connections_pool"
msgid "Advanced settings for `Gun` connections pool"
-msgstr "「Gun」连接池的高级设置"
+msgstr "`Gun` 连接池的高级设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -183,7 +185,7 @@ msgstr "Gopher 设置"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:hackney_pools"
msgid "Advanced settings for `Hackney` connections pools"
-msgstr "「Hackney」连接池的高级设置"
+msgstr "`Hackney` 连接池的高级设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -226,7 +228,7 @@ msgid "Majic/libmagic configuration"
msgstr "Majic/libmagic 配置"
#: lib/pleroma/docs/translator.ex:5
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:manifest"
msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE."
msgstr "此处提供针对特定实例的 PWA manifest 数值。目前相关设定尚只支持 MastoFE。"
@@ -244,10 +246,10 @@ msgid "Media proxy"
msgstr "媒体代理"
#: lib/pleroma/docs/translator.ex:5
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:modules"
msgid "Custom Runtime Modules"
-msgstr "自定义 Runtime 模块"
+msgstr "自定义运行库模块"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -299,7 +301,7 @@ msgstr "拒绝提及特定用户的讯息"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_normalize_markup"
msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup."
-msgstr ""
+msgstr "MRF NomalizeMarkup 设置。清楚超文本标记。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -317,7 +319,7 @@ msgstr "RejectNonPublic 丢弃有非公开的可见性设置的文章。"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:mrf_simple"
msgid "Simple ingress policies"
-msgstr ""
+msgstr "简单入口流量控制"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -326,10 +328,11 @@ msgid "Steals emojis from selected instances when it sees them."
msgstr "从选择的实例偷取看到的 emoji。"
#: lib/pleroma/docs/translator.ex:5
-#, elixir-autogen, elixir-format
+#, elixir-autogen, elixir-format, fuzzy
msgctxt "config description at :pleroma-:mrf_subchain"
msgid "This policy processes messages through an alternate pipeline when a given message matches certain criteria. All criteria are configured as a map of regular expressions to lists of policy modules."
-msgstr ""
+msgstr "此策略将会把满足特定条件的信息通过另一管线处理。所有条件都以正则表达式来对应"
+"列出的策略模块配置。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -347,13 +350,13 @@ msgstr "配置 OAuth 2 提供者的能力"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:pools"
msgid "Advanced settings for `Gun` workers pools"
-msgstr "「Gun」工人池的高级设置"
+msgstr "`Gun` worker 池的高级设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:populate_hashtags_table"
msgid "`populate_hashtags_table` background migration settings"
-msgstr "「populate_hashtags_table」后台迁移设置"
+msgstr "`populate_hashtags_table` 后台迁移设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -395,13 +398,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:uri_schemes"
msgid "URI schemes related settings"
-msgstr ""
+msgstr "URI scheme 相关设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:web_cache_ttl"
msgid "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration."
-msgstr "web 回应缓存的过期时间。值应该以毫秒为单位,或者用「nil」来禁用过期。"
+msgstr "网页回应缓存的过期时间。以毫秒为单位,或者用 `nil` 来禁用过期。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -413,7 +416,7 @@ msgstr "欢迎讯息设置"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:workers"
msgid "Includes custom worker options not interpretable directly by `Oban`"
-msgstr "包含不能直接被「Oban」解读的自定工人选项"
+msgstr "包含不能直接被 `Oban` 解读的自定 worker 选项"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -488,7 +491,7 @@ msgstr "过滤器将会匿名化上传文件的文件名"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify"
msgid "Uploads mogrify filter settings"
-msgstr ""
+msgstr "morgify 上传过滤器设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -531,6 +534,9 @@ msgstr "元数据相关设定"
msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp"
msgid "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.\n**If your instance is not behind at least one reverse proxy, you should not enable this plug.**\n"
msgstr ""
+"`Pleroma.Web.Plugs.RemoteIp` 是一个呼叫 [`RemoteIp`](https://git.pleroma."
+"social/pleroma/remote_ip) 的 shim 但是包含运行库配置。\n"
+"**如果您的实例不在至少一个反向代理后面,您不应该启用这个插件。**\n"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -548,13 +554,13 @@ msgstr "失效活动设定"
#, elixir-autogen, elixir-format
msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter"
msgid "Prometheus app metrics endpoint configuration"
-msgstr ""
+msgstr "Prometheus 服务监控端点配置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :web_push_encryption-:vapid_details"
msgid "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it."
-msgstr ""
+msgstr "网页推送通知配置。您可以使用 mix task mix web_push.gen.keypair 来生成它。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -584,7 +590,7 @@ msgstr "ActivityPub"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:assets"
msgid "Assets"
-msgstr ""
+msgstr "资源"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -608,13 +614,13 @@ msgstr "邮件通知"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:emoji"
msgid "Emoji"
-msgstr "Emoji"
+msgstr "表情符号"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:features"
msgid "Features"
-msgstr "特性"
+msgstr "功能"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -644,7 +650,7 @@ msgstr "Gopher"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:hackney_pools"
msgid "Hackney pools"
-msgstr ""
+msgstr "Hackney 池"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -674,25 +680,25 @@ msgstr "实例图标"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:ldap"
msgid "LDAP"
-msgstr ""
+msgstr "LDAP"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:majic_pool"
msgid "Majic pool"
-msgstr ""
+msgstr "Majic 池"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:manifest"
msgid "Manifest"
-msgstr ""
+msgstr "Manifest"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:markup"
msgid "Markup Settings"
-msgstr ""
+msgstr "标记设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -710,25 +716,25 @@ msgstr "媒体文件代理"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:modules"
msgid "Modules"
-msgstr ""
+msgstr "模块"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf"
msgid "MRF"
-msgstr ""
+msgstr "MRF"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_activity_expiration"
msgid "MRF Activity Expiration Policy"
-msgstr ""
+msgstr "MRF 活动过期策略"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_follow_bot"
msgid "MRF FollowBot Policy"
-msgstr ""
+msgstr "MRF FollowBot 策略"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -740,49 +746,49 @@ msgstr "MRF 标签"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_hellthread"
msgid "MRF Hellthread"
-msgstr ""
+msgstr "MRF Hellthread"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_keyword"
msgid "MRF Keyword"
-msgstr ""
+msgstr "MRF 关键词"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_mention"
msgid "MRF Mention"
-msgstr ""
+msgstr "MRF 提及"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_normalize_markup"
msgid "MRF Normalize Markup"
-msgstr ""
+msgstr "MRF 标记标准化"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_object_age"
msgid "MRF Object Age"
-msgstr ""
+msgstr "MRF 对象年龄"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_rejectnonpublic"
msgid "MRF Reject Non Public"
-msgstr ""
+msgstr "MRF 拒绝非公开帖子"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_simple"
msgid "MRF Simple"
-msgstr ""
+msgstr "MRF 简单"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:mrf_steal_emoji"
msgid "MRF Emojis"
-msgstr ""
+msgstr "MRF 表情符号"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -800,13 +806,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:oauth2"
msgid "OAuth2"
-msgstr ""
+msgstr "OAuth2"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:pools"
msgid "Pools"
-msgstr ""
+msgstr "池"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -818,13 +824,13 @@ msgstr "本站话题标签列表"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:rate_limit"
msgid "Rate limit"
-msgstr ""
+msgstr "限流"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:restrict_unauthenticated"
msgid "Restrict Unauthenticated"
-msgstr ""
+msgstr "限制未授权用户"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -842,7 +848,7 @@ msgstr "留言板"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:static_fe"
msgid "Static FE"
-msgstr ""
+msgstr "静态 FE"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -854,7 +860,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:uri_schemes"
msgid "URI Schemes"
-msgstr ""
+msgstr "URI Schemes"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -866,7 +872,7 @@ msgstr "用户"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:web_cache_ttl"
msgid "Web cache TTL"
-msgstr ""
+msgstr "网页缓存 TTL"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -878,13 +884,13 @@ msgstr "欢迎"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:workers"
msgid "Workers"
-msgstr "工人"
+msgstr "Workers"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-ConcurrentLimiter"
msgid "ConcurrentLimiter"
-msgstr ""
+msgstr "ConcurrentLimiter"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -896,133 +902,133 @@ msgstr "Oban"
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Captcha"
msgid "Pleroma.Captcha"
-msgstr ""
+msgstr "Pleroma.Captcha"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha"
msgid "Pleroma.Captcha.Kocaptcha"
-msgstr ""
+msgstr "Pleroma.Captcha.Kocaptcha"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Emails.Mailer"
msgid "Pleroma.Emails.Mailer"
-msgstr ""
+msgstr "Pleroma.Emails.Mailer"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail"
msgid "Pleroma.Emails.NewUsersDigestEmail"
-msgstr ""
+msgstr "Pleroma.Emails.NewUsersDigestEmail"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail"
msgid "Pleroma.Emails.UserEmail"
-msgstr ""
+msgstr "Pleroma.Emails.UserEmail"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Formatter"
msgid "Linkify"
-msgstr ""
+msgstr "Linkify"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.ScheduledActivity"
msgid "Pleroma.ScheduledActivity"
-msgstr ""
+msgstr "Pleroma.ScheduledActivity"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Upload"
msgid "Pleroma.Upload"
-msgstr ""
+msgstr "Pleroma.Upload"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename"
msgid "Pleroma.Upload.Filter.AnonymizeFilename"
-msgstr ""
+msgstr "Pleroma.Upload.Filter.AnonymizeFilename"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify"
msgid "Pleroma.Upload.Filter.Mogrify"
-msgstr ""
+msgstr "Pleroma.Upload.Filter.Mogrify"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Uploaders.Local"
msgid "Pleroma.Uploaders.Local"
-msgstr ""
+msgstr "Pleroma.Uploaders.Local"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Uploaders.S3"
msgid "Pleroma.Uploaders.S3"
-msgstr ""
+msgstr "Pleroma.Uploaders.S3"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.User"
msgid "Pleroma.User"
-msgstr ""
+msgstr "Pleroma.User"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.User.Backup"
msgid "Pleroma.User.Backup"
-msgstr ""
+msgstr "Pleroma.User.Backup"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate"
msgid "Pleroma.Web.ApiSpec.CastAndValidate"
-msgstr ""
+msgstr "Pleroma.Web.ApiSpec.CastAndValidate"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http"
msgid "Pleroma.Web.MediaProxy.Invalidation.Http"
-msgstr ""
+msgstr "Pleroma.Web.MediaProxy.Invalidation.Http"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script"
msgid "Pleroma.Web.MediaProxy.Invalidation.Script"
-msgstr ""
+msgstr "Pleroma.Web.MediaProxy.Invalidation.Script"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.Metadata"
msgid "Pleroma.Web.Metadata"
-msgstr ""
+msgstr "Pleroma.Web.Metadata"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp"
msgid "Pleroma.Web.Plugs.RemoteIp"
-msgstr ""
+msgstr "Pleroma.Web.Plugs.RemoteIp"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Web.Preload"
msgid "Pleroma.Web.Preload"
-msgstr ""
+msgstr "Pleroma.Web.Preload"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity"
msgid "Pleroma.Workers.PurgeExpiredActivity"
-msgstr ""
+msgstr "Pleroma.Workers.PurgeExpiredActivity"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter"
msgid "Pleroma.Web.Endpoint.MetricsExporter"
-msgstr ""
+msgstr "Pleroma.Web.Endpoint.MetricsExporter"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1064,37 +1070,39 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :ex_aws-:s3 > :access_key_id"
msgid "S3 access key ID"
-msgstr ""
+msgstr "S3 访问密钥 ID"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :ex_aws-:s3 > :host"
msgid "S3 host"
-msgstr ""
+msgstr "S3 主机"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :ex_aws-:s3 > :region"
msgid "S3 region (for AWS)"
-msgstr ""
+msgstr "S3 区域(AWS)"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :ex_aws-:s3 > :secret_access_key"
msgid "Secret access key"
-msgstr ""
+msgstr "访问密钥"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :logger > :backends"
msgid "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack."
msgstr ""
+"日志发送的地点,:console - 将日志发送到 stdout, { ExSyslogger, :ex_syslogger "
+"} - 发送到 syslog, Quack.Logger - 发送到 Slack."
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :logger-:console > :format"
msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
-msgstr ""
+msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\""
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1106,13 +1114,13 @@ msgstr "日志等级"
#, elixir-autogen, elixir-format
msgctxt "config description at :logger-:ex_syslogger > :format"
msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\""
-msgstr ""
+msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\""
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :logger-:ex_syslogger > :ident"
msgid "A string that's prepended to every message, and is typically set to the app name"
-msgstr ""
+msgstr "注入在每一个消息前面的字符串,通常设为应用程序的名称"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1148,13 +1156,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:activitypub > :outgoing_blocks"
msgid "Whether to federate blocks to other instances"
-msgstr ""
+msgstr "是否与其他实例同步屏蔽列表"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches"
msgid "Sign object fetches with HTTP signatures"
-msgstr ""
+msgstr "为对象获取进行 HTTP 签名"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1166,7 +1174,7 @@ msgstr "屏蔽对象时是否同时取消对其的关注"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:assets > :default_mascot"
msgid "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`"
-msgstr ""
+msgstr "这将是 MastoFE 的默认吉祥物。默认:`:pleroma_fox_tan`"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1178,13 +1186,15 @@ msgstr "默认用户头像的网址"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:assets > :mascots"
msgid "Keyword of mascots, each element must contain both an URL and a mime_type key"
-msgstr ""
+msgstr "吉祥物关键词,每一个元素必须包含一个 URL 和 mine_type 值"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:auth > :auth_template"
msgid "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`."
msgstr ""
+"授权表达模板。默认是 `show.html`,对应于 `lib/pleroma/web/templates/o_auth/"
+"o_auth/show.html.ee`。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1208,7 +1218,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:connections_pool > :connect_timeout"
msgid "Timeout while `gun` will wait until connection is up. Default: 5000ms."
-msgstr "「Gun」等待连接时触发超时的上限。默认为5000ms。"
+msgstr "`gun` 等待连接时触发超时的上限。默认:5000ms。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1226,7 +1236,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:connections_pool > :max_connections"
msgid "Maximum number of connections in the pool. Default: 250 connections."
-msgstr ""
+msgstr "池的最大连接数量。默认:250 连接。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1262,13 +1272,15 @@ msgstr "单个用户每次收到摘要邮件的间隔"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:email_notifications > :digest > :schedule"
msgid "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"."
-msgstr ""
+msgstr "发送摘要邮件的时间,以 crontab 格式。默认为“0 0 "
+"0”,意味着“每周在周日的午夜时分”。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:emoji > :default_manifest"
msgid "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays)."
-msgstr ""
+msgstr "JSON-manifest 的位置。manifest 包含您可以下载的表情包信息。目前只能添加一个 "
+"manifest(无数列)。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1286,7 +1298,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:emoji > :shortcode_globs"
msgid "Location of custom emoji files. * can be used as a wildcard."
-msgstr ""
+msgstr "自定义表情符号位置。* 可以当作通配符使用。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1298,7 +1310,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:feed > :post_title"
msgid "Configure title rendering"
-msgstr ""
+msgstr "配置标题渲染"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1468,7 +1480,7 @@ msgstr "管理员前端"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends > :admin > name"
msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
-msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。"
+msgstr "已安装的前端名称。有效配置必须包含 `Name` 和 `Reference` 数值。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1498,13 +1510,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends > :available > custom-http-headers"
msgid "The custom HTTP headers for the frontend"
-msgstr ""
+msgstr "前端的自定义 HTTP 响应头"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends > :available > git"
msgid "URL of the git repository of the frontend"
-msgstr ""
+msgstr "前端 git 仓库的 URL"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1522,13 +1534,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends > :primary"
msgid "Primary frontend, the one that is served for all pages by default"
-msgstr ""
+msgstr "主要前端,这是默认服务所有页面的前端"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:frontends > :primary > name"
msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values."
-msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。"
+msgstr "已安装的前端名称。有效配置必须包含 `Name` 和 `Reference` 数值。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1540,7 +1552,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:gopher > :dstport"
msgid "Port advertised in URLs (optional, defaults to port)"
-msgstr ""
+msgstr "URL 中宣传的端口(可选,默认为端口)"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1570,7 +1582,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:hackney_pools > :federation > :max_connections"
msgid "Number workers in the pool."
-msgstr "池内的工人数量。"
+msgstr "池内的 worker 数量。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1588,7 +1600,7 @@ msgstr "媒体池设定。"
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:hackney_pools > :media > :max_connections"
msgid "Number workers in the pool."
-msgstr "池内的工人数量。"
+msgstr "池内的 worker 数量。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1600,7 +1612,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:hackney_pools > :upload"
msgid "Settings for upload pool."
-msgstr ""
+msgstr "上传池设置。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1780,7 +1792,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :email"
msgid "Email used to reach an Administrator/Moderator of the instance"
-msgstr ""
+msgstr "用于联系实例管理员/监管员的电子邮箱"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1813,10 +1825,10 @@ msgid "Timeout (in days) of each external federation target being unreachable pr
msgstr ""
#: lib/pleroma/docs/translator.ex:5
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :healthcheck"
msgid "If enabled, system data will be shown on `/api/pleroma/healthcheck`"
-msgstr "若启用,「/api/pleroma/healthcheck」下将显示系统数据"
+msgstr "若启用,`/api/pleroma/healthcheck` 下将显示系统数据"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1828,7 +1840,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :invites_enabled"
msgid "Enable user invitations for admins (depends on `registrations_open` being disabled)"
-msgstr "只有管理员邀请的用户方能注册(需要关闭「registrations_open」选项)"
+msgstr "只有管理员邀请的用户才能注册(需要关闭 `registrations_open` 选项)"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1900,13 +1912,13 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number"
msgid "Number of backup codes to generate."
-msgstr ""
+msgstr "生成的备份密钥数目。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp"
msgid "TOTP settings"
-msgstr ""
+msgstr "TOTP 设置"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -1973,7 +1985,7 @@ msgstr "允许管理员访问敏感信息(例,更新用户凭据、取得密
#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :profile_directory"
msgid "Enable profile directory."
-msgstr ""
+msgstr "启用用户主页配置。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -2020,10 +2032,10 @@ msgstr ""
"用户(例,“@admin 请留意 @bad_actor”)。默认下为关闭状态"
#: lib/pleroma/docs/translator.ex:5
-#, elixir-autogen, elixir-format, fuzzy
+#, elixir-autogen, elixir-format
msgctxt "config description at :pleroma-:instance > :show_reactions"
msgid "Let favourites and emoji reactions be viewed through the API."
-msgstr "允许通过此API来看见喜欢数量与表情反应。"
+msgstr "允许通过此 API 来看见喜欢数量与表情回应。"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
@@ -3607,7 +3619,7 @@ msgstr ""
#, elixir-autogen, elixir-format
msgctxt "config label at :pleroma-:assets > :mascots"
msgid "Mascots"
-msgstr ""
+msgstr "吉祥物"
#: lib/pleroma/docs/translator.ex:5
#, elixir-autogen, elixir-format
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/default.po b/priv/gettext/zh_Hans/LC_MESSAGES/default.po
@@ -8,9 +8,9 @@
## to merge POT files into PO files.
msgid ""
msgstr ""
-"PO-Revision-Date: 2022-07-22 19:00+0000\n"
-"Last-Translator: Yating Zhan <thestrandedvalley@protonmail.com>\n"
-"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/"
+"PO-Revision-Date: 2024-08-02 09:02+0000\n"
+"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n"
+"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
"projects/pleroma/pleroma-backend-domain-default/zh_Hans/>\n"
"Language: zh_Hans\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -106,7 +106,7 @@ msgstr "转换到 %{polymorphic_type} 中的任一 schema 失败"
#: lib/pleroma/web/api_spec/render_error.ex:71
#, elixir-format
msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed."
-msgstr "把值转换成 %{invalid_schema} 失败。值必须可以被转换成在列的「所有」schema。"
+msgstr "把值转换成 %{invalid_schema} 失败。值必须可以被转换成在列的 `allOf` schema。"
#: lib/pleroma/web/api_spec/render_error.ex:84
#, elixir-format
@@ -136,17 +136,17 @@ msgstr "缺少头:%{name}。"
#: lib/pleroma/web/api_spec/render_error.ex:196
#, elixir-format
msgid "No value provided for required discriminator `%{field}`."
-msgstr ""
+msgstr "没有提供给鉴别器 `%{field}` 提供所需要的值。"
#: lib/pleroma/web/api_spec/render_error.ex:216
#, elixir-format
msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}."
-msgstr ""
+msgstr "对象属性数 %{property_count} 大于 maxProperties: %{max_properties}。"
#: lib/pleroma/web/api_spec/render_error.ex:224
#, elixir-format
msgid "Object property count %{property_count} is less than minProperties: %{min_properties}"
-msgstr ""
+msgstr "对象属性数 %{property_count} 小于 minProperties: %{min_properties}"
#: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2
#, elixir-format
@@ -166,7 +166,7 @@ msgstr "未知的 schema:%{name}。"
#: lib/pleroma/web/api_spec/render_error.ex:192
#, elixir-format
msgid "Value used as discriminator for `%{field}` matches no schemas."
-msgstr ""
+msgstr "用于 `%{field}` 鉴别器的值无法匹配到任何 schema。"
#: lib/pleroma/web/templates/embed/show.html.eex:43
#: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po
@@ -3,9 +3,9 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-20 13:18+0000\n"
-"PO-Revision-Date: 2022-07-22 19:00+0000\n"
-"Last-Translator: Yating Zhan <thestrandedvalley@protonmail.com>\n"
-"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/"
+"PO-Revision-Date: 2024-08-01 08:19+0000\n"
+"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n"
+"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
"projects/pleroma/pleroma-backend-domain-errors/zh_Hans/>\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
@@ -392,7 +392,7 @@ msgid "Invalid answer data"
msgstr "无效的回答数据"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33
-#, elixir-format, fuzzy
+#, elixir-format
msgid "Nodeinfo schema version not handled"
msgstr "Nodeinfo schema 版本没被处理"
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/oauth_scopes.po b/priv/gettext/zh_Hans/LC_MESSAGES/oauth_scopes.po
@@ -0,0 +1,274 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2024-08-01 10:12+0300\n"
+"PO-Revision-Date: 2024-08-01 08:19+0000\n"
+"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n"
+"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
+"projects/pleroma/pleroma-backend-domain-oauth_scopes/zh_Hans/>\n"
+"Language: zh_Hans\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 4.13.1\n"
+
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here has no
+## effect: edit them in PO (.po) files instead.
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin"
+msgstr "全部管理员权限"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read"
+msgstr "使用管理员 API 读取"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write"
+msgstr "使用管理员 API 写入"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "follow"
+msgstr "读取并写入用户关系"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read"
+msgstr "读取任何信息"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:accounts"
+msgstr "读取所有账号的信息"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:blocks"
+msgstr "读取块关系"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:bookmarks"
+msgstr "读取您的书签"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:favourites"
+msgstr "读取您喜欢的帖子"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:filters"
+msgstr "读取您的过滤器设置"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:follows"
+msgstr "读取关注关系"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:lists"
+msgstr "读取您的列表"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:notifications"
+msgstr "读取您的通知"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:search"
+msgstr "执行搜索"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:statuses"
+msgstr "读取您可以看到的动态"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write"
+msgstr "写入任何信息"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:accounts"
+msgstr "更改您的账号信息"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:blocks"
+msgstr "屏蔽或取消屏蔽任何人"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:bookmarks"
+msgstr "从您的书签中添加或移除"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:conversations"
+msgstr "更改收件人,标记为已阅,或删除聊天"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:favourites"
+msgstr "喜欢或取消喜欢动态"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:filters"
+msgstr "更改您的过滤器设置"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:follows"
+msgstr "关注或取消关注任何人"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:lists"
+msgstr "创建,更改或删除您的列表"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:media"
+msgstr "上传媒体文件或更改您上传的媒体文件"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:mutes"
+msgstr "隐藏或取消隐藏任何人"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:notifications"
+msgstr "标记通知为已读"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:statuses"
+msgstr "发表,编辑,转发帖子或对帖子做出回应"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:accounts"
+msgstr "使用管理员 API 读取所有账号"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:chats"
+msgstr "使用管理员 API 读取所有聊天"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:invites"
+msgstr "使用管理员 API 读取所有邀请码"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:media_proxy_caches"
+msgstr "使用管理员 API 读取媒体代理缓存"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:reports"
+msgstr "使用管理员 API 读取所有举报"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:read:statuses"
+msgstr "使用管理员 API 读取所有动态"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:accounts"
+msgstr "使用管理员 API 更改所有账号"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:chats"
+msgstr "使用管理员 API 更改所有聊天"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:follows"
+msgstr "使用管理员 API 更改关注关系"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:invites"
+msgstr "使用管理员 API 创建或吊销邀请码"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:media_proxy_caches"
+msgstr "使用管理员 API 更改媒体代理缓存"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:reports"
+msgstr "使用管理员 API 处理举报"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "admin:write:statuses"
+msgstr "使用管理员 API 删除动态,更改动态的范围,或标记为敏感动态"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:mutes"
+msgstr "读取隐藏关系"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "push"
+msgstr "推送通知"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:backups"
+msgstr "读取您的备份"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:chats"
+msgstr "读取您的聊天"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:media"
+msgstr "读取媒体附件"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "read:reports"
+msgstr "读取您的举报"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:chats"
+msgstr "添加或移除聊天信息,或者标记它们为已阅"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:follow"
+msgstr "关注或取消关注任何人"
+
+#: lib/pleroma/web/api_spec/scopes/translator.ex:5
+#, elixir-autogen, elixir-format
+msgid "write:reports"
+msgstr "提交举报"
diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po
@@ -8,9 +8,9 @@
## to merge POT files into PO files.
msgid ""
msgstr ""
-"PO-Revision-Date: 2022-07-22 19:00+0000\n"
-"Last-Translator: Yating Zhan <thestrandedvalley@protonmail.com>\n"
-"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/"
+"PO-Revision-Date: 2024-08-01 08:19+0000\n"
+"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n"
+"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/"
"projects/pleroma/pleroma-backend-domain-posix_errors/zh_Hans/>\n"
"Language: zh_Hans\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -22,19 +22,19 @@ msgid "eperm"
msgstr "不允许的操作"
msgid "eacces"
-msgstr "权限不够"
+msgstr "拒绝访问"
msgid "eagain"
msgstr "资源暂时不可用"
msgid "ebadf"
-msgstr "坏的文件描述符"
+msgstr "非法的文件描述符"
msgid "ebadmsg"
-msgstr "坏讯息"
+msgstr "非法消息"
msgid "ebusy"
-msgstr "设备或资源忙"
+msgstr "设备或资源繁忙"
msgid "edeadlk"
msgstr "避免了资源死锁"
@@ -46,10 +46,10 @@ msgid "edquot"
msgstr "超出了磁盘配额"
msgid "eexist"
-msgstr "文件存在"
+msgstr "文件已存在"
msgid "efault"
-msgstr "坏地址"
+msgstr "非法地址"
msgid "efbig"
msgstr "文件太大"
@@ -61,7 +61,7 @@ msgid "eintr"
msgstr "系统调用被中断"
msgid "einval"
-msgstr "不合法的参数"
+msgstr "非法参数"
msgid "eio"
msgstr "输入/输出错误"
@@ -79,7 +79,7 @@ msgid "emlink"
msgstr "太多链接"
msgid "emultihop"
-msgstr ""
+msgstr "已尝试多跳"
msgid "enametoolong"
msgstr "文件名太长"
@@ -97,7 +97,7 @@ msgid "enolck"
msgstr "没有可用的锁"
msgid "enolink"
-msgstr "链接被切断了"
+msgstr "链接被切断"
msgid "enoent"
msgstr "没这文件或目录"
@@ -109,19 +109,19 @@ msgid "enospc"
msgstr "设备上没剩余空间"
msgid "enosr"
-msgstr ""
+msgstr "流资源不足"
msgid "enostr"
msgstr "设备不是流"
msgid "enosys"
-msgstr "功能没实现"
+msgstr "功能未实现"
msgid "enotblk"
-msgstr ""
+msgstr "需要块设备"
msgid "enotdir"
-msgstr ""
+msgstr "不是目录"
msgid "enotsup"
msgstr "不受支持的操作"
@@ -136,25 +136,25 @@ msgid "eoverflow"
msgstr "请为给定类型的数据指定较小的数值"
msgid "epipe"
-msgstr ""
+msgstr "管道中断"
msgid "erange"
-msgstr ""
+msgstr "数值超过范围"
msgid "erofs"
-msgstr "只读权限文件系统"
+msgstr "只读文件系统"
msgid "espipe"
-msgstr ""
+msgstr "非法搜寻"
msgid "esrch"
-msgstr "具体进程不存在"
+msgstr "进程不存在"
msgid "estale"
-msgstr ""
+msgstr "过时的文件句柄"
msgid "etxtbsy"
-msgstr "文本文件忙碌"
+msgstr "文本文件繁忙"
msgid "exdev"
-msgstr "该多设备链接不可用"
+msgstr "非法多设备链接"
diff --git a/priv/repo/migrations/20240628160536_deprecate_config_db_workers.exs b/priv/repo/migrations/20240628160536_deprecate_config_db_workers.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.DeprecateConfigDBWorkers do
+ use Ecto.Migration
+
+ def change do
+ execute("DELETE FROM config WHERE config.group = ':workers'")
+ end
+end
diff --git a/priv/repo/migrations/20240904142434_assign_app_user.exs b/priv/repo/migrations/20240904142434_assign_app_user.exs
@@ -0,0 +1,21 @@
+defmodule Pleroma.Repo.Migrations.AssignAppUser do
+ use Ecto.Migration
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Token
+
+ def up do
+ Repo.all(Token)
+ |> Enum.group_by(fn x -> Map.get(x, :app_id) end)
+ |> Enum.each(fn {_app_id, tokens} ->
+ token =
+ Enum.filter(tokens, fn x -> not is_nil(x.user_id) end)
+ |> List.first()
+
+ App.maybe_update_owner(token)
+ end)
+ end
+
+ def down, do: :ok
+end
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
@@ -22,7 +22,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
"u-url",
"mention",
"u-url mention",
- "mention u-url"
+ "mention u-url",
+ "mention hashtag"
])
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
diff --git a/priv/scrubbers/twitter_text.ex b/priv/scrubbers/twitter_text.ex
@@ -23,7 +23,8 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
"u-url",
"mention",
"u-url mention",
- "mention u-url"
+ "mention u-url",
+ "mention hashtag"
])
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
diff --git a/rel/vm.args.eex b/rel/vm.args.eex
@@ -9,3 +9,8 @@
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
+
+# Disable wasteful busywait.
++sbwt none
++sbwtdcpu none
++sbwtdio none
diff --git a/test/fixtures/bastianallgeier.json b/test/fixtures/bastianallgeier.json
@@ -1,117 +0,0 @@
-{
- "@context": [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
- {
- "Curve25519Key": "toot:Curve25519Key",
- "Device": "toot:Device",
- "Ed25519Key": "toot:Ed25519Key",
- "Ed25519Signature": "toot:Ed25519Signature",
- "EncryptedMessage": "toot:EncryptedMessage",
- "PropertyValue": "schema:PropertyValue",
- "alsoKnownAs": {
- "@id": "as:alsoKnownAs",
- "@type": "@id"
- },
- "cipherText": "toot:cipherText",
- "claim": {
- "@id": "toot:claim",
- "@type": "@id"
- },
- "deviceId": "toot:deviceId",
- "devices": {
- "@id": "toot:devices",
- "@type": "@id"
- },
- "discoverable": "toot:discoverable",
- "featured": {
- "@id": "toot:featured",
- "@type": "@id"
- },
- "featuredTags": {
- "@id": "toot:featuredTags",
- "@type": "@id"
- },
- "fingerprintKey": {
- "@id": "toot:fingerprintKey",
- "@type": "@id"
- },
- "focalPoint": {
- "@container": "@list",
- "@id": "toot:focalPoint"
- },
- "identityKey": {
- "@id": "toot:identityKey",
- "@type": "@id"
- },
- "indexable": "toot:indexable",
- "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
- "memorial": "toot:memorial",
- "messageFranking": "toot:messageFranking",
- "messageType": "toot:messageType",
- "movedTo": {
- "@id": "as:movedTo",
- "@type": "@id"
- },
- "publicKeyBase64": "toot:publicKeyBase64",
- "schema": "http://schema.org#",
- "suspended": "toot:suspended",
- "toot": "http://joinmastodon.org/ns#",
- "value": "schema:value"
- }
- ],
- "attachment": [
- {
- "name": "Website",
- "type": "PropertyValue",
- "value": "<a href=\"https://bastianallgeier.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">bastianallgeier.com</span><span class=\"invisible\"></span></a>"
- },
- {
- "name": "Project",
- "type": "PropertyValue",
- "value": "<a href=\"https://getkirby.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">getkirby.com</span><span class=\"invisible\"></span></a>"
- },
- {
- "name": "Github",
- "type": "PropertyValue",
- "value": "<a href=\"https://github.com/bastianallgeier\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">github.com/bastianallgeier</span><span class=\"invisible\"></span></a>"
- }
- ],
- "devices": "https://mastodon.social/users/bastianallgeier/collections/devices",
- "discoverable": true,
- "endpoints": {
- "sharedInbox": "https://mastodon.social/inbox"
- },
- "featured": "https://mastodon.social/users/bastianallgeier/collections/featured",
- "featuredTags": "https://mastodon.social/users/bastianallgeier/collections/tags",
- "followers": "https://mastodon.social/users/bastianallgeier/followers",
- "following": "https://mastodon.social/users/bastianallgeier/following",
- "icon": {
- "mediaType": "image/jpeg",
- "type": "Image",
- "url": "https://files.mastodon.social/accounts/avatars/000/007/393/original/0180a20079617c71.jpg"
- },
- "id": "https://mastodon.social/users/bastianallgeier",
- "image": {
- "mediaType": "image/jpeg",
- "type": "Image",
- "url": "https://files.mastodon.social/accounts/headers/000/007/393/original/13d644ab46d50478.jpeg"
- },
- "inbox": "https://mastodon.social/users/bastianallgeier/inbox",
- "indexable": false,
- "manuallyApprovesFollowers": false,
- "memorial": false,
- "name": "Bastian Allgeier",
- "outbox": "https://mastodon.social/users/bastianallgeier/outbox",
- "preferredUsername": "bastianallgeier",
- "publicKey": {
- "id": "https://mastodon.social/users/bastianallgeier#main-key",
- "owner": "https://mastodon.social/users/bastianallgeier",
- "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fz+hpgVztO9z6HUhyzv\nwP++ERBBoIwSLKf1TyIM8bvzGFm2YXaO5uxu1HvumYFTYc3ACr3q4j8VUb7NMxkQ\nlzu4QwPjOFJ43O+fY+HSPORXEDW5fXDGC5DGpox4+i08LxRmx7L6YPRUSUuPN8nI\nWyq1Qsq1zOQrNY/rohMXkBdSXxqC3yIRqvtLt4otCgay/5tMogJWkkS6ZKyFhb9z\nwVVy1fsbV10c9C+SHy4NH26CKaTtpTYLRBMjhTCS8bX8iDSjGIf2aZgYs1ir7gEz\n9wf5CvLiENmVWGwm64t6KSEAkA4NJ1hzgHUZPCjPHZE2SmhO/oHaxokTzqtbbENJ\n1QIDAQAB\n-----END PUBLIC KEY-----\n"
- },
- "published": "2016-11-01T00:00:00Z",
- "summary": "<p>Designer & developer. Creator of Kirby CMS</p>",
- "tag": [],
- "type": "Person",
- "url": "https://mastodon.social/@bastianallgeier"
-}
diff --git a/test/fixtures/receiver_worker_signature_activity.json b/test/fixtures/receiver_worker_signature_activity.json
@@ -1,62 +1,109 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
{
+ "claim": {
+ "@id": "toot:claim",
+ "@type": "@id"
+ },
+ "memorial": "toot:memorial",
"atomUri": "ostatus:atomUri",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"blurhash": "toot:blurhash",
- "conversation": "ostatus:conversation",
+ "ostatus": "http://ostatus.org#",
+ "discoverable": "toot:discoverable",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
- "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
- "ostatus": "http://ostatus.org#",
+ "votersCount": "toot:votersCount",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji",
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
"sensitive": "as:sensitive",
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "Device": "toot:Device",
+ "schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
- "votersCount": "toot:votersCount"
- }
- ],
- "atomUri": "https://chaos.social/users/distantnative/statuses/109336635639931467",
- "attachment": [
- {
- "blurhash": "UAK1zS00OXIUxuMxIUM{?b-:-;W:Di?b%2M{",
- "height": 960,
- "mediaType": "image/jpeg",
- "name": null,
- "type": "Document",
- "url": "https://assets.chaos.social/media_attachments/files/109/336/634/286/114/657/original/2e6122063d8bfb26.jpeg",
- "width": 346
+ "cipherText": "toot:cipherText",
+ "suspended": "toot:suspended",
+ "messageType": "toot:messageType",
+ "featuredTags": {
+ "@id": "toot:featuredTags",
+ "@type": "@id"
+ },
+ "Curve25519Key": "toot:Curve25519Key",
+ "deviceId": "toot:deviceId",
+ "Ed25519Signature": "toot:Ed25519Signature",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "devices": {
+ "@id": "toot:devices",
+ "@type": "@id"
+ },
+ "value": "schema:value",
+ "PropertyValue": "schema:PropertyValue",
+ "messageFranking": "toot:messageFranking",
+ "publicKeyBase64": "toot:publicKeyBase64",
+ "identityKey": {
+ "@id": "toot:identityKey",
+ "@type": "@id"
+ },
+ "Ed25519Key": "toot:Ed25519Key",
+ "indexable": "toot:indexable",
+ "EncryptedMessage": "toot:EncryptedMessage",
+ "fingerprintKey": {
+ "@id": "toot:fingerprintKey",
+ "@type": "@id"
+ }
}
],
- "attributedTo": "https://chaos.social/users/distantnative",
- "cc": [
- "https://chaos.social/users/distantnative/followers"
- ],
- "content": "<p>Favorite piece of anthropology meta discourse.</p>",
- "contentMap": {
- "en": "<p>Favorite piece of anthropology meta discourse.</p>"
- },
- "conversation": "tag:chaos.social,2022-11-13:objectId=71843781:objectType=Conversation",
- "id": "https://chaos.social/users/distantnative/statuses/109336635639931467",
+ "actor": "https://phpc.social/users/denniskoch",
+ "cc": [],
+ "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity",
"inReplyTo": null,
"inReplyToAtomUri": null,
- "published": "2022-11-13T13:04:20Z",
- "replies": {
- "first": {
- "items": [],
- "next": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies?only_other_accounts=true&page=true",
- "partOf": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies",
- "type": "CollectionPage"
+ "object": {
+ "atomUri": "https://phpc.social/users/denniskoch/statuses/112847382711461301",
+ "attachment": [],
+ "attributedTo": "https://phpc.social/users/denniskoch",
+ "cc": [],
+ "content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>",
+ "contentMap": {
+ "en": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>"
},
- "id": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies",
- "type": "Collection"
+ "conversation": "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation",
+ "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301",
+ "published": "2024-07-25T13:33:29Z",
+ "replies": null,
+ "sensitive": false,
+ "tag": [],
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type": "Note",
+ "url": "https://phpc.social/@denniskoch/112847382711461301"
+ },
+ "published": "2024-07-25T13:33:29Z",
+ "signature": {
+ "created": "2024-07-25T13:33:29Z",
+ "creator": "https://phpc.social/users/denniskoch#main-key",
+ "signatureValue": "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==",
+ "type": "RsaSignature2017"
},
- "sensitive": false,
- "summary": null,
- "tag": [],
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
- "type": "Note",
- "url": "https://chaos.social/@distantnative/109336635639931467"
+ "type": "Create"
}
diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs
@@ -623,10 +623,12 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
expires_at = DateTime.add(DateTime.utc_now(), 60 * 61)
- Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
- activity_id: activity_id3,
- expires_at: expires_at
- })
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: activity_id3
+ },
+ scheduled_at: expires_at
+ )
Mix.Tasks.Pleroma.Database.run(["ensure_expiration"])
diff --git a/test/mix/tasks/pleroma/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs
@@ -3,12 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.UploadsTest do
+ alias Pleroma.Config
alias Pleroma.Upload
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
import Mock
setup_all do
+ prep_uploads()
Mix.shell(Mix.Shell.Process)
on_exit(fn ->
@@ -18,6 +20,8 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do
:ok
end
+ setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
+
describe "running migrate_local" do
test "uploads migrated" do
with_mock Upload,
@@ -53,4 +57,15 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do
end
end
end
+
+ defp prep_uploads do
+ upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads])
+
+ if not File.exists?(upload_dir) || File.ls!(upload_dir) == [] do
+ File.mkdir_p(upload_dir)
+
+ Path.join([upload_dir, "file.txt"])
+ |> File.touch()
+ end
+ end
end
diff --git a/test/pleroma/html_test.exs b/test/pleroma/html_test.exs
@@ -41,6 +41,10 @@ defmodule Pleroma.HTMLTest do
<span class="h-card"><a class="u-url mention animate-spin">@<span>foo</span></a></span>
"""
+ @mention_hashtags_sample """
+ <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
+ """
+
describe "StripTags scrubber" do
test "works as expected" do
expected = """
@@ -126,6 +130,15 @@ defmodule Pleroma.HTMLTest do
Pleroma.HTML.Scrubber.TwitterText
)
end
+
+ test "does allow mention hashtags" do
+ expected = """
+ <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
+ """
+
+ assert expected ==
+ HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default)
+ end
end
describe "default scrubber" do
@@ -189,6 +202,15 @@ defmodule Pleroma.HTMLTest do
Pleroma.HTML.Scrubber.Default
)
end
+
+ test "does allow mention hashtags" do
+ expected = """
+ <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
+ """
+
+ assert expected ==
+ HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default)
+ end
end
describe "extract_first_external_url_from_object" do
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
@@ -100,7 +100,7 @@ defmodule Pleroma.Object.FetcherTest do
test "it returns thread depth exceeded error if thread depth is exceeded" do
clear_config([:instance, :federation_incoming_replies_max_depth], 0)
- assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1)
+ assert {:allowed_depth, false} = Fetcher.fetch_object_from_id(@ap_id, depth: 1)
end
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
@@ -118,15 +118,18 @@ defmodule Pleroma.Object.FetcherTest do
describe "actor origin containment" do
test "it rejects objects with a bogus origin" do
- {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
+ {:containment, :error} =
+ Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
end
test "it rejects objects when attributedTo is wrong (variant 1)" do
- {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
+ {:containment, :error} =
+ Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
end
test "it rejects objects when attributedTo is wrong (variant 2)" do
- {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
+ {:containment, :error} =
+ Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
end
end
@@ -150,14 +153,14 @@ defmodule Pleroma.Object.FetcherTest do
clear_config([:mrf_keyword, :reject], ["yeah"])
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
- assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
+ assert {:transmogrifier, {:reject, "[KeywordPolicy] Matches with rejected keyword"}} ==
Fetcher.fetch_object_from_id(
"http://mastodon.example.org/@admin/99541947525187367"
)
end
test "it does not fetch a spoofed object uploaded on an instance as an attachment" do
- assert {:error, _} =
+ assert {:fetch, {:error, {:content_type, "application/json"}}} =
Fetcher.fetch_object_from_id(
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
)
diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs
@@ -6,12 +6,10 @@ defmodule Pleroma.ObjectTest do
use Pleroma.DataCase
use Oban.Testing, repo: Pleroma.Repo
- import ExUnit.CaptureLog
import Mox
import Pleroma.Factory
import Tesla.Mock
- alias Pleroma.Activity
alias Pleroma.Hashtag
alias Pleroma.Object
alias Pleroma.Repo
@@ -176,8 +174,9 @@ defmodule Pleroma.ObjectTest do
filename = Path.basename(href)
- assert {:ok, files} = File.ls(uploads_dir)
- assert filename in files
+ expected_path = Path.join([uploads_dir, Pleroma.Upload.Filter.Dedupe.shard_path(filename)])
+
+ assert File.exists?(expected_path)
Object.delete(note)
@@ -185,8 +184,7 @@ defmodule Pleroma.ObjectTest do
assert Object.get_by_id(note.id).data["deleted"]
assert Object.get_by_id(attachment.id) == nil
- assert {:ok, files} = File.ls(uploads_dir)
- refute filename in files
+ refute File.exists?(expected_path)
end
test "with objects that have legacy data.url attribute" do
@@ -282,148 +280,6 @@ defmodule Pleroma.ObjectTest do
end
end
- describe "get_by_id_and_maybe_refetch" do
- setup do
- mock(fn
- %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
- %Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/tesla_mock/poll_original.json"),
- headers: HttpRequestMock.activitypub_object_headers()
- }
-
- env ->
- apply(HttpRequestMock, :request, [env])
- end)
-
- mock_modified = fn resp ->
- mock(fn
- %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
- resp
-
- env ->
- apply(HttpRequestMock, :request, [env])
- end)
- end
-
- on_exit(fn -> mock(fn env -> apply(HttpRequestMock, :request, [env]) end) end)
-
- [mock_modified: mock_modified]
- end
-
- test "refetches if the time since the last refetch is greater than the interval", %{
- mock_modified: mock_modified
- } do
- %Object{} =
- object =
- Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d",
- fetch: true
- )
-
- Object.set_cache(object)
-
- assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
-
- mock_modified.(%Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/tesla_mock/poll_modified.json"),
- headers: HttpRequestMock.activitypub_object_headers()
- })
-
- updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
- object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
- assert updated_object == object_in_cache
- assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
- assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
- end
-
- test "returns the old object if refetch fails", %{mock_modified: mock_modified} do
- %Object{} =
- object =
- Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d",
- fetch: true
- )
-
- Object.set_cache(object)
-
- assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
-
- assert capture_log(fn ->
- mock_modified.(%Tesla.Env{status: 404, body: ""})
-
- updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
- object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
- assert updated_object == object_in_cache
- assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
- end) =~
- "[error] Couldn't refresh https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"
- end
-
- test "does not refetch if the time since the last refetch is greater than the interval", %{
- mock_modified: mock_modified
- } do
- %Object{} =
- object =
- Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d",
- fetch: true
- )
-
- Object.set_cache(object)
-
- assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
-
- mock_modified.(%Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/tesla_mock/poll_modified.json"),
- headers: HttpRequestMock.activitypub_object_headers()
- })
-
- updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: 100)
- object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
- assert updated_object == object_in_cache
- assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
- end
-
- test "preserves internal fields on refetch", %{mock_modified: mock_modified} do
- %Object{} =
- object =
- Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d",
- fetch: true
- )
-
- Object.set_cache(object)
-
- assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
- assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
-
- user = insert(:user)
- activity = Activity.get_create_by_object_ap_id(object.data["id"])
- {:ok, activity} = CommonAPI.favorite(activity.id, user)
- object = Object.get_by_ap_id(activity.data["object"])
-
- assert object.data["like_count"] == 1
-
- mock_modified.(%Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/tesla_mock/poll_modified.json"),
- headers: HttpRequestMock.activitypub_object_headers()
- })
-
- updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
- object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
- assert updated_object == object_in_cache
- assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
- assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
-
- assert updated_object.data["like_count"] == 1
- end
- end
-
describe ":hashtags association" do
test "Hashtag records are created with Object record and updated on its change" do
user = insert(:user)
diff --git a/test/pleroma/release_task_test.exs b/test/pleroma/release_task_test.exs
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReleaseTaskTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.ReleaseTasks
+
+ test "finding the module" do
+ task = "search.meilisearch"
+ assert Mix.Tasks.Pleroma.Search.Meilisearch == ReleaseTasks.find_module(task)
+
+ task = "user"
+ assert Mix.Tasks.Pleroma.User == ReleaseTasks.find_module(task)
+
+ refute ReleaseTasks.find_module("doesnt.exist")
+ end
+end
diff --git a/test/pleroma/upload/filter/dedupe_test.exs b/test/pleroma/upload/filter/dedupe_test.exs
@@ -10,6 +10,10 @@ defmodule Pleroma.Upload.Filter.DedupeTest do
@shasum "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781"
+ test "generates a shard path for a shasum" do
+ assert "e3/03/97/" <> _path = Dedupe.shard_path(@shasum)
+ end
+
test "adds shasum" do
File.cp!(
"test/fixtures/image.jpg",
@@ -23,10 +27,12 @@ defmodule Pleroma.Upload.Filter.DedupeTest do
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
+ expected_path = Dedupe.shard_path(@shasum <> ".jpg")
+
assert {
:ok,
:filtered,
- %Pleroma.Upload{id: @shasum, path: @shasum <> ".jpg"}
+ %Pleroma.Upload{id: @shasum, path: ^expected_path}
} = Dedupe.filter(upload)
end
end
diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs
@@ -149,6 +149,9 @@ defmodule Pleroma.UploadTest do
test "copies the file to the configured folder with deduping" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+ expected_filename = "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg"
+
+ expected_path = Pleroma.Upload.Filter.Dedupe.shard_path(expected_filename)
file = %Plug.Upload{
content_type: "image/jpeg",
@@ -159,8 +162,7 @@ defmodule Pleroma.UploadTest do
{:ok, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.Dedupe])
assert List.first(data["url"])["href"] ==
- Pleroma.Upload.base_url() <>
- "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg"
+ Path.join([Pleroma.Upload.base_url(), expected_path])
end
test "copies the file to the configured folder without deduping" do
diff --git a/test/pleroma/user/import_test.exs b/test/pleroma/user/import_test.exs
@@ -25,11 +25,12 @@ defmodule Pleroma.User.ImportTest do
user3.nickname
]
- {:ok, job} = User.Import.follow_import(user1, identifiers)
+ {:ok, jobs} = User.Import.follows_import(user1, identifiers)
+
+ for job <- jobs do
+ assert {:ok, %User{}} = ObanHelpers.perform(job)
+ end
- assert {:ok, result} = ObanHelpers.perform(job)
- assert is_list(result)
- assert result == [refresh_record(user2), refresh_record(user3)]
assert User.following?(user1, user2)
assert User.following?(user1, user3)
end
@@ -44,11 +45,12 @@ defmodule Pleroma.User.ImportTest do
user3.nickname
]
- {:ok, job} = User.Import.blocks_import(user1, identifiers)
+ {:ok, jobs} = User.Import.blocks_import(user1, identifiers)
+
+ for job <- jobs do
+ assert {:ok, %User{}} = ObanHelpers.perform(job)
+ end
- assert {:ok, result} = ObanHelpers.perform(job)
- assert is_list(result)
- assert result == [user2, user3]
assert User.blocks?(user1, user2)
assert User.blocks?(user1, user3)
end
@@ -63,11 +65,12 @@ defmodule Pleroma.User.ImportTest do
user3.nickname
]
- {:ok, job} = User.Import.mutes_import(user1, identifiers)
+ {:ok, jobs} = User.Import.mutes_import(user1, identifiers)
+
+ for job <- jobs do
+ assert {:ok, %User{}} = ObanHelpers.perform(job)
+ end
- assert {:ok, result} = ObanHelpers.perform(job)
- assert is_list(result)
- assert result == [user2, user3]
assert User.mutes?(user1, user2)
assert User.mutes?(user1, user3)
end
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -657,7 +657,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end
test "without valid signature, " <>
- "it only accepts Create activities and requires enabled federation",
+ "it accepts Create activities and requires enabled federation",
%{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
@@ -684,6 +684,54 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> json_response(400)
end
+ # When activity is delivered to the inbox and we cannot immediately verify signature
+ # we capture all the params and process it later in the Oban job.
+ # Once we begin processing it through Oban we risk fetching the actor to validate the
+ # activity which just leads to inserting a new user to process a Delete not relevant to us.
+ test "Activities of certain types from an unknown actor are discarded", %{conn: conn} do
+ example_bad_types =
+ Pleroma.Constants.activity_types() --
+ Pleroma.Constants.allowed_activity_types_from_strangers()
+
+ Enum.each(example_bad_types, fn bad_type ->
+ params =
+ %{
+ "type" => bad_type,
+ "actor" => "https://unknown.mastodon.instance/users/somebody"
+ }
+ |> Jason.encode!()
+
+ conn
+ |> assign(:valid_signature, false)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/inbox", params)
+ |> json_response(400)
+
+ assert all_enqueued() == []
+ end)
+ end
+
+ test "Unknown activity types are discarded", %{conn: conn} do
+ unknown_types = ["Poke", "Read", "Dazzle"]
+
+ Enum.each(unknown_types, fn bad_type ->
+ params =
+ %{
+ "type" => bad_type,
+ "actor" => "https://unknown.mastodon.instance/users/somebody"
+ }
+ |> Jason.encode!()
+
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/inbox", params)
+ |> json_response(400)
+
+ assert all_enqueued() == []
+ end)
+ end
+
test "accepts Add/Remove activities", %{conn: conn} do
object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
@@ -1272,6 +1320,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
html_body: ~r/#{note.data["object"]}/i
)
end
+
+ test "it accepts an incoming Block", %{conn: conn, data: data} do
+ user = insert(:user)
+
+ data =
+ data
+ |> Map.put("type", "Block")
+ |> Map.put("to", [user.ap_id])
+ |> Map.put("cc", [])
+ |> Map.put("object", user.ap_id)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+ ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+ assert Activity.get_by_ap_id(data["id"])
+ end
end
describe "GET /users/:nickname/outbox" do
@@ -1575,6 +1644,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert json_response(conn, 403)
end
+ test "it rejects update activity of object from other actor", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ note_object = Object.normalize(note_activity, fetch: false)
+ user = insert(:user)
+
+ data = %{
+ type: "Update",
+ object: %{
+ id: note_object.data["id"]
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", data)
+
+ assert json_response(conn, 400)
+ assert note_object == Object.normalize(note_activity, fetch: false)
+ end
+
test "it increases like count when receiving a like action", %{conn: conn} do
note_activity = insert(:note_activity)
note_object = Object.normalize(note_activity, fetch: false)
@@ -1747,7 +1838,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
%{conn: conn} do
user = insert(:user, hide_followers: true)
other_user = insert(:user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
result =
conn
@@ -1843,7 +1934,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
%{conn: conn} do
user = insert(:user, hide_follows: true)
other_user = insert(:user)
- {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
result =
conn
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -232,12 +232,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert user.avatar == %{
"type" => "Image",
- "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
+ "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}],
+ "name" => "profile picture"
}
assert user.banner == %{
"type" => "Image",
- "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
+ "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}],
+ "name" => "profile picture"
}
end
@@ -432,6 +434,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert user.birthday == ~D[2001-02-12]
end
+
+ test "fetches avatar description" do
+ user_id = "https://example.com/users/marcin"
+
+ user_data =
+ "test/fixtures/users_mock/user.json"
+ |> File.read!()
+ |> String.replace("{{nickname}}", "marcin")
+ |> Jason.decode!()
+ |> Map.delete("featured")
+ |> Map.update("icon", %{}, fn image -> Map.put(image, "name", "image description") end)
+ |> Jason.encode!()
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: ^user_id
+ } ->
+ %Tesla.Env{
+ status: 200,
+ body: user_data,
+ headers: [{"content-type", "application/activity+json"}]
+ }
+ end)
+
+ {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
+
+ assert user.avatar["name"] == "image description"
+ end
end
test "it fetches the appropriate tag-restricted posts" do
diff --git a/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs b/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.FODirectReplyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ require Pleroma.Constants
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF.FODirectReply
+ alias Pleroma.Web.CommonAPI
+
+ test "replying to followers-only/private is changed to direct" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} =
+ CommonAPI.post(batman, %{
+ status: "Has anyone seen Selina Kyle's latest selfies?",
+ visibility: "private"
+ })
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman 🤤 ❤️ 🐈⬛",
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ expected_to = [batman.ap_id]
+ expected_cc = []
+
+ assert {:ok, filtered} = FODirectReply.filter(reply)
+
+ assert expected_to == filtered["to"]
+ assert expected_cc == filtered["cc"]
+ assert expected_to == filtered["object"]["to"]
+ assert expected_cc == filtered["object"]["cc"]
+ end
+
+ test "replies to unlisted posts are unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} =
+ CommonAPI.post(batman, %{
+ status: "Has anyone seen Selina Kyle's latest selfies?",
+ visibility: "unlisted"
+ })
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman 🤤 ❤️ 🐈<200d>⬛",
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = FODirectReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "replies to public posts are unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} =
+ CommonAPI.post(batman, %{status: "Has anyone seen Selina Kyle's latest selfies?"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman 🤤 ❤️ 🐈<200d>⬛",
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = FODirectReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "non-reply posts are unmodified" do
+ batman = insert(:user, nickname: "batman")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ assert {:ok, filtered} = FODirectReply.filter(post)
+
+ assert match?(^filtered, post)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs
@@ -0,0 +1,140 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.QuietReplyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ require Pleroma.Constants
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF.QuietReply
+ alias Pleroma.Web.CommonAPI
+
+ test "replying to public post is forced to be quiet" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [
+ batman.ap_id,
+ Pleroma.Constants.as_public()
+ ],
+ "cc" => [robin.follower_address],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [
+ batman.ap_id,
+ Pleroma.Constants.as_public()
+ ],
+ "cc" => [robin.follower_address],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ expected_to = [batman.ap_id, robin.follower_address]
+ expected_cc = [Pleroma.Constants.as_public()]
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert expected_to == filtered["to"]
+ assert expected_cc == filtered["cc"]
+ assert expected_to == filtered["object"]["to"]
+ assert expected_cc == filtered["object"]["cc"]
+ end
+
+ test "replying to unlisted post is unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!", visibility: "private"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [batman.ap_id],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "replying direct is unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [batman.ap_id],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "replying followers-only is unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "non-reply posts are unmodified" do
+ batman = insert(:user, nickname: "batman")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ assert {:ok, filtered} = QuietReply.filter(post)
+
+ assert match?(^filtered, post)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs
@@ -0,0 +1,155 @@
+defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicyTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy
+
+ setup do
+ clear_config([:mrf_remote_report, :reject_all], false)
+ end
+
+ test "doesn't impact local report" do
+ clear_config([:mrf_remote_report, :reject_anonymous], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "http://localhost:4001/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects anonymous report if `reject_anonymous: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves anonymous report if `reject_anonymous: false`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects report on third party if `reject_third_party: true`" do
+ clear_config([:mrf_remote_report, :reject_third_party], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves report on first party if `reject_third_party: true`" do
+ clear_config([:mrf_remote_report, :reject_third_party], true)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["http://localhost:4001/actor"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves report on third party if `reject_third_party: false`" do
+ clear_config([:mrf_remote_report, :reject_third_party], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects empty message report if `reject_empty_message: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "rejects empty message report (\"\") if `reject_empty_message: true`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], true)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"],
+ "content" => ""
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves empty message report if `reject_empty_message: false`" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "preserves anonymous, empty message report with all settings disabled" do
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/actor",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:ok, _} = RemoteReportPolicy.filter(activity)
+ end
+
+ test "reject remote report if `reject_all: true`" do
+ clear_config([:mrf_remote_report, :reject_all], true)
+ clear_config([:mrf_remote_report, :reject_anonymous], false)
+ clear_config([:mrf_remote_report, :reject_empty_message], false)
+
+ activity = %{
+ "type" => "Flag",
+ "actor" => "https://mastodon.social/users/Gargron",
+ "content" => "Transphobia",
+ "object" => ["https://mastodon.online/users/Gargron"]
+ }
+
+ assert {:reject, _} = RemoteReportPolicy.filter(activity)
+ end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
@@ -252,6 +252,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
remote_message = build_remote_message()
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ assert SimplePolicy.id_filter(remote_message["actor"])
end
test "activity has a matching host" do
@@ -260,6 +261,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
remote_message = build_remote_message()
assert {:reject, _} = SimplePolicy.filter(remote_message)
+ refute SimplePolicy.id_filter(remote_message["actor"])
end
test "activity matches with wildcard domain" do
@@ -268,6 +270,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
remote_message = build_remote_message()
assert {:reject, _} = SimplePolicy.filter(remote_message)
+ refute SimplePolicy.id_filter(remote_message["actor"])
end
test "actor has a matching host" do
@@ -276,6 +279,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
remote_user = build_remote_user()
assert {:reject, _} = SimplePolicy.filter(remote_user)
+ refute SimplePolicy.id_filter(remote_user["id"])
end
test "reject Announce when object would be rejected" do
@@ -288,6 +292,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
}
assert {:reject, _} = SimplePolicy.filter(announce)
+ # Note: Non-Applicable for id_filter/1
end
test "reject by URI object" do
@@ -300,6 +305,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
}
assert {:reject, _} = SimplePolicy.filter(announce)
+ # Note: Non-Applicable for id_filter/1
end
end
@@ -370,6 +376,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ assert SimplePolicy.id_filter(local_message["actor"])
+ assert SimplePolicy.id_filter(remote_message["actor"])
end
test "is not empty but activity doesn't have a matching host" do
@@ -380,6 +388,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert {:reject, _} = SimplePolicy.filter(remote_message)
+ assert SimplePolicy.id_filter(local_message["actor"])
+ refute SimplePolicy.id_filter(remote_message["actor"])
end
test "activity has a matching host" do
@@ -390,6 +400,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ assert SimplePolicy.id_filter(local_message["actor"])
+ assert SimplePolicy.id_filter(remote_message["actor"])
end
test "activity matches with wildcard domain" do
@@ -400,6 +412,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ assert SimplePolicy.id_filter(local_message["actor"])
+ assert SimplePolicy.id_filter(remote_message["actor"])
end
test "actor has a matching host" do
@@ -408,6 +422,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
+ assert SimplePolicy.id_filter(remote_user["id"])
end
end
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.PublisherTest do
+ use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase
import ExUnit.CaptureLog
@@ -13,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Object
+ alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.CommonAPI
@@ -150,32 +152,20 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
_actor = insert(:user)
assert {:ok, %{body: "port 42"}} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox42,
activity_id: activity.id,
unreachable_since: true
})
+ |> Publisher.publish_one()
assert {:ok, %{body: "port 80"}} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox80,
activity_id: activity.id,
unreachable_since: true
})
- end
-
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
- Instances,
- [:passthrough],
- [] do
- _actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
- activity = insert(:note_activity)
-
- assert {:ok, _} =
- Publisher.publish_one(%{inbox: inbox, activity_id: activity.id})
-
- assert called(Instances.set_reachable(inbox))
+ |> Publisher.publish_one()
end
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
@@ -187,11 +177,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
activity = insert(:note_activity)
assert {:ok, _} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
- unreachable_since: NaiveDateTime.utc_now()
+ unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
})
+ |> Publisher.publish_one()
assert called(Instances.set_reachable(inbox))
end
@@ -205,11 +196,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
activity = insert(:note_activity)
assert {:ok, _} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
unreachable_since: nil
})
+ |> Publisher.publish_one()
refute called(Instances.set_reachable(inbox))
end
@@ -223,7 +215,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
activity = insert(:note_activity)
assert {:cancel, _} =
- Publisher.publish_one(%{inbox: inbox, activity_id: activity.id})
+ Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
+ |> Publisher.publish_one()
assert called(Instances.set_unreachable(inbox))
end
@@ -238,10 +231,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert capture_log(fn ->
assert {:error, _} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id
})
+ |> Publisher.publish_one()
end) =~ "connrefused"
assert called(Instances.set_unreachable(inbox))
@@ -256,7 +250,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
activity = insert(:note_activity)
assert {:ok, _} =
- Publisher.publish_one(%{inbox: inbox, activity_id: activity.id})
+ Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id})
+ |> Publisher.publish_one()
refute called(Instances.set_unreachable(inbox))
end
@@ -271,11 +266,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert capture_log(fn ->
assert {:error, _} =
- Publisher.publish_one(%{
+ Publisher.prepare_one(%{
inbox: inbox,
activity_id: activity.id,
- unreachable_since: NaiveDateTime.utc_now()
+ unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string()
})
+ |> Publisher.publish_one()
end) =~ "connrefused"
refute called(Instances.set_unreachable(inbox))
@@ -310,12 +306,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert res == :ok
- assert not called(
- Publisher.enqueue_one(%{
- inbox: "https://domain.com/users/nick1/inbox",
- activity_id: note_activity.id
- })
- )
+ refute_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "params" => %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ activity_id: note_activity.id
+ }
+ }
+ )
end
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
@@ -345,15 +344,16 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert res == :ok
- assert called(
- Publisher.enqueue_one(
- %{
- inbox: "https://domain.com/users/nick1/inbox",
- activity_id: note_activity.id
- },
- priority: 1
- )
- )
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "params" => %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ activity_id: note_activity.id
+ }
+ },
+ priority: 1
+ )
end
test_with_mock "Publishes to directly addressed actors with higher priority.",
@@ -403,12 +403,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
res = Publisher.publish(actor, note_activity)
assert res == :ok
- assert called(
- Publisher.enqueue_one(%{
- inbox: "https://domain.com/users/nick1/inbox",
- activity_id: note_activity.id
- })
- )
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "params" => %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ activity_id: note_activity.id
+ }
+ }
+ )
end
test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
@@ -452,25 +455,69 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
res = Publisher.publish(actor, delete)
assert res == :ok
- assert called(
- Publisher.enqueue_one(
- %{
- inbox: "https://domain.com/users/nick1/inbox",
- activity_id: delete.id
- },
- priority: 1
- )
- )
-
- assert called(
- Publisher.enqueue_one(
- %{
- inbox: "https://domain2.com/users/nick1/inbox",
- activity_id: delete.id
- },
- priority: 1
- )
- )
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "params" => %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ activity_id: delete.id
+ }
+ },
+ priority: 1
+ )
+
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "params" => %{
+ inbox: "https://domain2.com/users/nick1/inbox",
+ activity_id: delete.id
+ }
+ },
+ priority: 1
+ )
end
end
+
+ test "cc in prepared json for a follow request is an empty list" do
+ user = insert(:user)
+ remote_user = insert(:user, local: false)
+
+ {:ok, _, _, activity} = CommonAPI.follow(remote_user, user)
+
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "activity_id" => activity.id,
+ "op" => "publish"
+ }
+ )
+
+ ObanHelpers.perform_all()
+
+ expected_params =
+ %{
+ "activity_id" => activity.id,
+ "inbox" => remote_user.inbox,
+ "unreachable_since" => nil
+ }
+
+ assert_enqueued(
+ worker: "Pleroma.Workers.PublisherWorker",
+ args: %{
+ "op" => "publish_one",
+ "params" => expected_params
+ }
+ )
+
+ # params need to be atom keys for Publisher.prepare_one.
+ # this is done in the Oban job.
+ expected_params = Map.new(expected_params, fn {k, v} -> {String.to_atom(k), v} end)
+
+ %{json: json} = Publisher.prepare_one(expected_params)
+
+ {:ok, decoded} = Jason.decode(json)
+
+ assert decoded["cc"] == []
+ end
end
diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs
@@ -54,20 +54,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
[
stream: fn _, _ -> nil end
]
- },
- {
- Pleroma.Web.Push,
- [],
- [
- send: fn _ -> nil end
- ]
}
]) do
SideEffects.handle_after_transaction(meta)
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
- assert called(Pleroma.Web.Push.send(notification))
+
+ assert_enqueued(
+ worker: "Pleroma.Workers.WebPusherWorker",
+ args: %{"notification_id" => notification.id, "op" => "web_push"}
+ )
end
end
end
diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs
@@ -68,6 +68,23 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
result = UserView.render("user.json", %{user: user})
assert result["icon"]["url"] == "https://someurl"
assert result["image"]["url"] == "https://somebanner"
+
+ refute result["icon"]["name"]
+ refute result["image"]["name"]
+ end
+
+ test "Avatar has a description if the user set one" do
+ user =
+ insert(:user,
+ avatar: %{
+ "url" => [%{"href" => "https://someurl"}],
+ "name" => "a drawing of pleroma-tan using pleroma groups"
+ }
+ )
+
+ result = UserView.render("user.json", %{user: user})
+
+ assert result["icon"]["name"] == "a drawing of pleroma-tan using pleroma groups"
end
test "renders an invisible user with the invisible property set to true" do
@@ -138,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "sets totalItems to zero when followers are hidden" do
user = insert(:user)
other_user = insert(:user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
user = Map.merge(user, %{hide_followers_count: true, hide_followers: true})
refute UserView.render("followers.json", %{user: user}) |> Map.has_key?("totalItems")
@@ -147,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "sets correct totalItems when followers are hidden but the follower counter is not" do
user = insert(:user)
other_user = insert(:user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
user = Map.merge(user, %{hide_followers_count: false, hide_followers: true})
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
@@ -158,7 +175,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "sets totalItems to zero when follows are hidden" do
user = insert(:user)
other_user = insert(:user)
- {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
user = Map.merge(user, %{hide_follows_count: true, hide_follows: true})
assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
@@ -167,7 +184,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "sets correct totalItems when follows are hidden but the follow counter is not" do
user = insert(:user)
other_user = insert(:user)
- {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user)
+ {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
user = Map.merge(user, %{hide_follows_count: false, hide_follows: true})
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
@@ -1420,7 +1420,7 @@ defmodule Pleroma.Web.CommonAPITest do
describe "follow/2" do
test "directly follows a non-locked local user" do
[follower, followed] = insert_pair(:user)
- {:ok, follower, followed, _} = CommonAPI.follow(followed, follower)
+ {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
assert User.following?(follower, followed)
end
@@ -1429,7 +1429,7 @@ defmodule Pleroma.Web.CommonAPITest do
describe "unfollow/2" do
test "also unsubscribes a user" do
[follower, followed] = insert_pair(:user)
- {:ok, follower, followed, _} = CommonAPI.follow(followed, follower)
+ {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
{:ok, _subscription} = User.subscribe(follower, followed)
assert User.subscribed_to?(follower, followed)
@@ -1441,7 +1441,7 @@ defmodule Pleroma.Web.CommonAPITest do
test "also unpins a user" do
[follower, followed] = insert_pair(:user)
- {:ok, follower, followed, _} = CommonAPI.follow(followed, follower)
+ {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
{:ok, _endorsement} = User.endorse(follower, followed)
assert User.endorses?(follower, followed)
@@ -1455,7 +1455,7 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
followed = insert(:user, is_locked: true)
- assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+ assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} =
CommonAPI.follow(followed, follower)
assert User.get_follow_state(follower, followed) == :follow_pending
@@ -1477,7 +1477,7 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
followed = insert(:user, is_locked: true, local: false)
- assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+ assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} =
CommonAPI.follow(followed, follower)
assert User.get_follow_state(follower, followed) == :follow_pending
diff --git a/test/pleroma/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs
@@ -32,7 +32,7 @@ defmodule Pleroma.Web.FallbackTest do
resp = get(conn, "/foo")
assert html_response(resp, 200) =~ "<title>a cool title</title>"
- refute html_response(resp, 200) =~ "initial-results"
+ assert html_response(resp, 200) =~ "<meta content=\"noindex, noarchive\" name=\"robots\">"
end
test "GET /*path", %{conn: conn} do
diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
@@ -191,4 +191,60 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
|> response(404)
end
end
+
+ describe "restricted for unauthenticated" do
+ test "returns 404 when local timeline is disabled", %{conn: conn} do
+ clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false})
+
+ conn
+ |> put_req_header("accept", "application/rss+xml")
+ |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
+ |> response(404)
+ end
+
+ test "returns local posts only when federated timeline is disabled", %{conn: conn} do
+ clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true})
+
+ local_user = insert(:user)
+ remote_user = insert(:user, local: false)
+
+ local_note =
+ insert(:note,
+ user: local_user,
+ data: %{
+ "content" => "local post #PleromaArt",
+ "summary" => "",
+ "tag" => ["pleromaart"]
+ }
+ )
+
+ remote_note =
+ insert(:note,
+ user: remote_user,
+ data: %{
+ "content" => "remote post #PleromaArt",
+ "summary" => "",
+ "tag" => ["pleromaart"]
+ },
+ local: false
+ )
+
+ insert(:note_activity, user: local_user, note: local_note)
+ insert(:note_activity, user: remote_user, note: remote_note, local: false)
+
+ response =
+ conn
+ |> put_req_header("accept", "application/rss+xml")
+ |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
+ |> response(200)
+
+ xml = parse(response)
+
+ assert xpath(xml, ~x"//channel/title/text()") == ~c"#pleromaart"
+
+ assert xpath(xml, ~x"//channel/item/title/text()"l) == [
+ ~c"local post #PleromaArt"
+ ]
+ end
+ end
end
diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
@@ -147,6 +147,15 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
assert response(conn, 404)
end
+ test "returns noindex meta for missing user", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("accept", "text/html")
+ |> get("/users/nonexisting")
+
+ assert html_response(conn, 200) =~ "<meta content=\"noindex, noarchive\" name=\"robots\">"
+ end
+
test "returns feed with public and unlisted activities", %{conn: conn} do
user = insert(:user)
diff --git a/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs
@@ -5,6 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do
use Pleroma.Web.ConnCase, async: true
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.Web.CommonAPI
+
import Pleroma.Factory
describe "GET /api/v1/markers" do
@@ -127,5 +131,36 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do
assert response == %{"error" => "Insufficient permissions: write:statuses."}
end
+
+ test "marks notifications as read", %{conn: conn} do
+ user1 = insert(:user)
+ token = insert(:oauth_token, user: user1, scopes: ["write:statuses"])
+
+ user2 = insert(:user)
+ {:ok, _activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
+ {:ok, _activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"})
+ {:ok, _activity3} = CommonAPI.post(user2, %{status: "HIE @#{user1.nickname}"})
+
+ [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
+
+ refute Repo.get(Notification, notification1.id).seen
+ refute Repo.get(Notification, notification2.id).seen
+ refute Repo.get(Notification, notification3.id).seen
+
+ conn
+ |> assign(:user, user1)
+ |> assign(:token, token)
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/markers", %{
+ notifications: %{last_read_id: to_string(notification2.id)}
+ })
+ |> json_response_and_validate_schema(200)
+
+ [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
+
+ assert Repo.get(Notification, notification1.id).seen
+ assert Repo.get(Notification, notification2.id).seen
+ refute Repo.get(Notification, notification3.id).seen
+ end
end
end
diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/v2/media", %{"file" => image, "description" => desc})
- |> json_response_and_validate_schema(202)
+ |> json_response_and_validate_schema(200)
assert media_id = response["id"]
@@ -111,7 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
"file" => large_binary,
"description" => desc
})
- |> json_response_and_validate_schema(202)
+ |> json_response_and_validate_schema(200)
assert media_id = response["id"]
diff --git a/test/pleroma/web/mastodon_api/controllers/poll_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/poll_controller_test.exs
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.PollControllerTest do
+ use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Object
@@ -27,6 +28,33 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do
response = json_response_and_validate_schema(conn, 200)
id = to_string(object.id)
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
+
+ # Local activities should not generate an Oban job to refresh
+ assert activity.local
+
+ refute_enqueued(
+ worker: Pleroma.Workers.PollWorker,
+ args: %{"op" => "refresh", "activity_id" => activity.id}
+ )
+ end
+
+ test "creates an oban job to refresh poll if activity is remote", %{conn: conn} do
+ user = insert(:user, local: false)
+ question = insert(:question, user: user)
+ activity = insert(:question_activity, question: question, local: false)
+
+ # Ensure this is not represented as a local activity
+ refute activity.local
+
+ object = Object.normalize(activity, fetch: false)
+
+ get(conn, "/api/v1/polls/#{object.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert_enqueued(
+ worker: Pleroma.Workers.PollWorker,
+ args: %{"op" => "refresh", "activity_id" => activity.id}
+ )
end
test "does not expose polls for private statuses", %{conn: conn} do
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -922,13 +922,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
%{id: id1} = insert(:note_activity)
%{id: id2} = insert(:note_activity)
- query_string = "ids[]=#{id1}&ids[]=#{id2}"
+ query_string = "id[]=#{id1}&id[]=#{id2}"
conn = get(conn, "/api/v1/statuses/?#{query_string}")
assert [%{"id" => ^id1}, %{"id" => ^id2}] =
Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
end
+ test "get statuses by IDs falls back to ids[]" do
+ %{conn: conn} = oauth_access(["read:statuses"])
+ %{id: id} = insert(:note_activity)
+
+ query_string = "ids[]=#{id}"
+ conn = get(conn, "/api/v1/statuses/?#{query_string}")
+
+ assert [%{"id" => ^id}] = json_response_and_validate_schema(conn, 200)
+ end
+
describe "getting statuses by ids with restricted unauthenticated for local and remote" do
setup do: local_and_remote_activities()
@@ -937,7 +947,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
assert json_response_and_validate_schema(res_conn, 200) == []
end
@@ -945,7 +955,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
end
@@ -957,7 +967,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
remote_id = remote.id
assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
@@ -966,7 +976,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
end
@@ -978,7 +988,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
local_id = local.id
assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
@@ -987,7 +997,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
test "if user is authenticated", %{local: local, remote: remote} do
%{conn: conn} = oauth_access(["read"])
- res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
+ res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
end
@@ -2241,7 +2251,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
result =
conn
- |> get("/api/v1/statuses/?ids[]=#{activity.id}")
+ |> get("/api/v1/statuses/?id[]=#{activity.id}")
|> json_response_and_validate_schema(200)
assert [
@@ -2254,7 +2264,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
result =
conn
- |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
+ |> get("/api/v1/statuses/?id[]=#{activity.id}&with_muted=true")
|> json_response_and_validate_schema(200)
assert [
diff --git a/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs
@@ -6,15 +6,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
use Pleroma.Web.ConnCase, async: true
describe "empty_array/2 (stubs)" do
- test "GET /api/v1/accounts/:id/identity_proofs" do
- %{user: user, conn: conn} = oauth_access(["read:accounts"])
-
- assert [] ==
- conn
- |> get("/api/v1/accounts/#{user.id}/identity_proofs")
- |> json_response(200)
- end
-
test "GET /api/v1/endorsements" do
%{conn: conn} = oauth_access(["read:accounts"])
diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
@@ -430,6 +430,75 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
+ test "adds avatar description with a new avatar", %{user: user, conn: conn} do
+ new_avatar = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ res =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ "avatar" => new_avatar,
+ "avatar_description" => "me and pleroma tan"
+ })
+
+ assert json_response_and_validate_schema(res, 200)
+
+ user = User.get_by_id(user.id)
+ assert user.avatar["name"] == "me and pleroma tan"
+ end
+
+ test "adds avatar description to existing avatar", %{user: user, conn: conn} do
+ new_avatar = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ assert user.avatar == %{}
+
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
+
+ assert conn
+ |> assign(:user, User.get_by_id(user.id))
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "avatar_description" => "me and pleroma tan"
+ })
+ |> json_response_and_validate_schema(200)
+
+ user = User.get_by_id(user.id)
+ assert user.avatar["name"] == "me and pleroma tan"
+ end
+
+ test "limit", %{user: user, conn: conn} do
+ new_header = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ assert user.banner == %{}
+
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
+
+ description_limit = Config.get([:instance, :description_limit], 100)
+
+ description = String.duplicate(".", description_limit + 1)
+
+ conn =
+ conn
+ |> assign(:user, User.get_by_id(user.id))
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "header_description" => description
+ })
+
+ assert %{"error" => "Banner description is too long"} =
+ json_response_and_validate_schema(conn, 413)
+ end
+
test "Strip / from upload files", %{user: user, conn: conn} do
new_image = %Plug.Upload{
content_type: "image/jpeg",
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -96,7 +96,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
- accepts_chat_messages: nil
+ accepts_chat_messages: nil,
+ avatar_description: "",
+ header_description: ""
}
}
@@ -340,7 +342,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
- accepts_chat_messages: nil
+ accepts_chat_messages: nil,
+ avatar_description: "",
+ header_description: ""
}
}
@@ -532,7 +536,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user)
other_user = insert(:user, is_locked: true)
- {:ok, user, other_user, _} = CommonAPI.follow(other_user, user)
+ {:ok, other_user, user, _} = CommonAPI.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
@@ -599,8 +603,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
test "shows when follows/followers are hidden" do
user = insert(:user, hide_followers: true, hide_follows: true)
other_user = insert(:user)
- {:ok, user, other_user, _activity} = CommonAPI.follow(other_user, user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{
followers_count: 1,
@@ -612,11 +616,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
test "shows actual follower/following count to the account owner" do
user = insert(:user, hide_followers: true, hide_follows: true)
other_user = insert(:user)
- {:ok, user, other_user, _activity} = CommonAPI.follow(other_user, user)
+ {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert User.following?(user, other_user)
assert Pleroma.FollowingRelationship.follower_count(other_user) == 1
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{
followers_count: 1,
@@ -735,7 +739,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
other_user = insert(:user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{locked: true, follow_requests_count: 1} =
AccountView.render("show.json", %{user: user, for: user})
@@ -747,7 +751,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
other_user = insert(:user)
- {:ok, other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{locked: true, follow_requests_count: 1} =
AccountView.render("show.json", %{user: user, for: user})
@@ -764,7 +768,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
other_user = insert(:user)
- {:ok, other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
assert %{locked: true, follow_requests_count: 1} =
AccountView.render("show.json", %{user: user, for: user})
@@ -781,7 +785,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
other_user = insert(:user)
- {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user)
+ {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
{:ok, user} = User.update_and_set_cache(user, %{is_locked: false})
diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs
@@ -56,6 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:chat_mention",
account: AccountView.render("show.json", %{user: user, for: recipient}),
@@ -75,6 +76,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "mention",
account:
@@ -99,6 +101,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "favourite",
account: AccountView.render("show.json", %{user: another_user, for: user}),
@@ -119,6 +122,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "reblog",
account: AccountView.render("show.json", %{user: another_user, for: user}),
@@ -132,11 +136,12 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
test "Follow notification" do
follower = insert(:user)
followed = insert(:user)
- {:ok, follower, followed, _activity} = CommonAPI.follow(followed, follower)
+ {:ok, followed, follower, _activity} = CommonAPI.follow(followed, follower)
notification = Notification |> Repo.one() |> Repo.preload(:activity)
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "follow",
account: AccountView.render("show.json", %{user: follower, for: followed}),
@@ -165,6 +170,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "move",
account: AccountView.render("show.json", %{user: old_user, for: follower}),
@@ -190,6 +196,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:emoji_reaction",
emoji: "☕",
@@ -229,6 +236,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:emoji_reaction",
emoji: ":dinosaur:",
@@ -248,6 +256,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "poll",
account:
@@ -274,6 +283,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:report",
account: AccountView.render("show.json", %{user: reporting_user, for: moderator_user}),
@@ -300,6 +310,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "update",
account: AccountView.render("show.json", %{user: user, for: repeat_user}),
@@ -322,6 +333,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: true, is_muted: true},
type: "favourite",
account: AccountView.render("show.json", %{user: another_user, for: user}),
@@ -345,6 +357,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
+ group_key: "ungrouped-#{to_string(notification.id)}",
pleroma: %{is_seen: false, is_muted: false},
type: "status",
account:
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -342,7 +342,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
parent_visible: false,
pinned_at: nil,
quotes_count: 0,
- bookmark_folder: nil
+ bookmark_folder: nil,
+ list_id: nil
}
}
@@ -912,6 +913,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
status = StatusView.render("show.json", activity: activity)
assert status.visibility == "list"
+ assert status.pleroma.list_id == nil
+
+ status = StatusView.render("show.json", activity: activity, for: user)
+
+ assert status.pleroma.list_id == list.id
end
test "has a field for parent visibility" do
diff --git a/test/pleroma/web/metadata/providers/activity_pub_test.exs b/test/pleroma/web/metadata/providers/activity_pub_test.exs
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Providers.ActivityPubTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Metadata.Providers.ActivityPub
+
+ setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
+
+ test "it renders a link for user info" do
+ user = insert(:user)
+ res = ActivityPub.build_tags(%{user: user})
+
+ assert res == [
+ {:link, [rel: "alternate", type: "application/activity+json", href: user.ap_id], []}
+ ]
+ end
+
+ test "it renders a link for a post" do
+ user = insert(:user)
+ {:ok, %{id: activity_id, object: object}} = CommonAPI.post(user, %{status: "hi"})
+
+ result = ActivityPub.build_tags(%{object: object, user: user, activity_id: activity_id})
+
+ assert [
+ {:link,
+ [rel: "alternate", type: "application/activity+json", href: object.data["id"]], []}
+ ] == result
+ end
+
+ test "it returns an empty array for anything else" do
+ result = ActivityPub.build_tags(%{})
+
+ assert result == []
+ end
+end
diff --git a/test/pleroma/web/metadata/providers/feed_test.exs b/test/pleroma/web/metadata/providers/feed_test.exs
@@ -15,4 +15,10 @@ defmodule Pleroma.Web.Metadata.Providers.FeedTest do
[rel: "alternate", type: "application/atom+xml", href: "/users/lain/feed.atom"], []}
]
end
+
+ test "it doesn't render a link to remote user's feed" do
+ user = insert(:user, nickname: "lain@lain.com", local: false)
+
+ assert Feed.build_tags(%{user: user}) == []
+ end
end
diff --git a/test/pleroma/web/o_auth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs
@@ -53,4 +53,21 @@ defmodule Pleroma.Web.OAuth.AppTest do
assert Enum.sort(App.get_user_apps(user)) == Enum.sort(apps)
end
+
+ test "removes orphaned apps" do
+ attrs = %{client_name: "Mastodon-Local", redirect_uris: "."}
+ {:ok, %App{} = old_app} = App.get_or_make(attrs, ["write"])
+
+ attrs = %{client_name: "PleromaFE", redirect_uris: "."}
+ {:ok, %App{} = app} = App.get_or_make(attrs, ["write"])
+
+ # backdate the old app so it's within the threshold for being cleaned up
+ {:ok, _} =
+ "UPDATE apps SET inserted_at = now() - interval '1 hour' WHERE id = #{old_app.id}"
+ |> Pleroma.Repo.query()
+
+ App.remove_orphans()
+
+ assert [app] == Pleroma.Repo.all(App)
+ end
end
diff --git a/test/pleroma/web/o_auth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs
@@ -28,11 +28,7 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
{:eldap, [],
[
open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
- simple_bind: fn _connection, _dn, ^password -> :ok end,
- close: fn _connection ->
- send(self(), :close_connection)
- :ok
- end
+ simple_bind: fn _connection, _dn, ^password -> :ok end
]}
] do
conn =
@@ -50,7 +46,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
token = Repo.get_by(Token, token: token)
assert token.user_id == user.id
- assert_received :close_connection
end
end
@@ -72,10 +67,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
wholeSubtree: fn -> :ok end,
search: fn _connection, _options ->
{:ok, {:eldap_search_result, [{:eldap_entry, ~c"", []}], []}}
- end,
- close: fn _connection ->
- send(self(), :close_connection)
- :ok
end
]}
] do
@@ -94,7 +85,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
token = Repo.get_by(Token, token: token) |> Repo.preload(:user)
assert token.user.nickname == user.nickname
- assert_received :close_connection
end
end
@@ -111,11 +101,7 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
{:eldap, [],
[
open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end,
- simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end,
- close: fn _connection ->
- send(self(), :close_connection)
- :ok
- end
+ simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end
]}
] do
conn =
@@ -129,7 +115,6 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
})
assert %{"error" => "Invalid credentials"} = json_response(conn, 400)
- assert_received :close_connection
end
end
end
diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
alias Pleroma.MFA.TOTP
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.Token
@@ -770,6 +771,9 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
{:ok, auth} = Authorization.create_authorization(app, user, ["write"])
+ # Verify app has no associated user yet
+ assert %Pleroma.Web.OAuth.App{user_id: nil} = Repo.get_by(App, %{id: app.id})
+
conn =
build_conn()
|> post("/oauth/token", %{
@@ -786,6 +790,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
assert token
assert token.scopes == auth.scopes
assert user.ap_id == ap_id
+
+ # Verify app has an associated user now
+ user_id = user.id
+ assert %Pleroma.Web.OAuth.App{user_id: ^user_id} = Repo.get_by(App, %{id: app.id})
end
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
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
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
test "it returns HTTP 200", %{conn: conn} do
user2 = insert(:user)
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"})
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
"Account address,Show boosts\n#{user2.ap_id},true"
end}
]) do
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/follow_import", %{
@@ -46,9 +46,9 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == [refresh_record(user2)]
- assert [%Pleroma.User{follower_count: 1}] = job_result
+ assert [{:ok, updated_user}] = ObanHelpers.perform_all()
+ assert updated_user.id == user2.id
+ assert updated_user.follower_count == 1
end
end
@@ -63,7 +63,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
})
|> json_response_and_validate_schema(200)
- assert response == "job started"
+ assert response == "jobs started"
end
test "requires 'follow' or 'write:follows' permissions" do
@@ -102,14 +102,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
]
|> Enum.join("\n")
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/follow_import", %{"list" => identifiers})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == Enum.map(users, &refresh_record/1)
+ results = ObanHelpers.perform_all()
+
+ returned_users =
+ for {_, returned_user} <- results do
+ returned_user
+ end
+
+ assert returned_users == Enum.map(users, &refresh_record/1)
end
end
@@ -120,7 +126,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
test "it returns HTTP 200", %{conn: conn} do
user2 = insert(:user)
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"})
@@ -133,7 +139,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
with_mocks([
{File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end}
]) do
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/blocks_import", %{
@@ -141,8 +147,14 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == users
+ results = ObanHelpers.perform_all()
+
+ returned_users =
+ for {_, returned_user} <- results do
+ returned_user
+ end
+
+ assert returned_users == users
end
end
@@ -159,14 +171,25 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
]
|> Enum.join(" ")
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/blocks_import", %{"list" => identifiers})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == users
+ results = ObanHelpers.perform_all()
+
+ returned_user_ids =
+ for {_, user} <- results do
+ user.id
+ end
+
+ original_user_ids =
+ for user <- users do
+ user.id
+ end
+
+ assert match?(^original_user_ids, returned_user_ids)
end
end
@@ -177,24 +200,25 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
test "it returns HTTP 200", %{user: user, conn: conn} do
user2 = insert(:user)
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/mutes_import", %{"list" => "#{user2.ap_id}"})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == [user2]
+ [{:ok, result_user}] = ObanHelpers.perform_all()
+
+ assert result_user == refresh_record(user2)
assert Pleroma.User.mutes?(user, user2)
end
test "it imports mutes users from file", %{user: user, conn: conn} do
- users = [user2, user3] = insert_list(2, :user)
+ [user2, user3] = insert_list(2, :user)
with_mocks([
{File, [], read!: fn "mutes_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end}
]) do
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/mutes_import", %{
@@ -202,14 +226,19 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == users
- assert Enum.all?(users, &Pleroma.User.mutes?(user, &1))
+ results = ObanHelpers.perform_all()
+
+ returned_users =
+ for {_, returned_user} <- results do
+ returned_user
+ end
+
+ assert Enum.all?(returned_users, &Pleroma.User.mutes?(user, &1))
end
end
test "it imports mutes with different nickname variations", %{user: user, conn: conn} do
- users = [user2, user3, user4, user5, user6] = insert_list(5, :user)
+ [user2, user3, user4, user5, user6] = insert_list(5, :user)
identifiers =
[
@@ -221,15 +250,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do
]
|> Enum.join(" ")
- assert "job started" ==
+ assert "jobs started" ==
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/mutes_import", %{"list" => identifiers})
|> json_response_and_validate_schema(200)
- assert [{:ok, job_result}] = ObanHelpers.perform_all()
- assert job_result == users
- assert Enum.all?(users, &Pleroma.User.mutes?(user, &1))
+ results = ObanHelpers.perform_all()
+
+ returned_users =
+ for {_, returned_user} <- results do
+ returned_user
+ end
+
+ assert Enum.all?(returned_users, &Pleroma.User.mutes?(user, &1))
end
end
end
diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs
@@ -70,6 +70,24 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
assert "$pbkdf2" <> _ = user.password_hash
end
+ test "with an argon2 hash, it updates to a pkbdf2 hash", %{conn: conn} do
+ user = insert(:user, password_hash: Argon2.hash_pwd_salt("123"))
+ assert "$argon2" <> _ = user.password_hash
+
+ conn =
+ conn
+ |> assign(:auth_user, user)
+ |> assign(:auth_credentials, %{password: "123"})
+ |> AuthenticationPlug.call(%{})
+
+ assert conn.assigns.user.id == conn.assigns.auth_user.id
+ assert conn.assigns.token == nil
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+
+ user = User.get_by_id(user.id)
+ assert "$pbkdf2" <> _ = user.password_hash
+ end
+
describe "checkpw/2" do
test "check pbkdf2 hash" do
hash =
@@ -86,6 +104,14 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
refute AuthenticationPlug.checkpw("password1", hash)
end
+ test "check argon2 hash" do
+ hash =
+ "$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g"
+
+ assert AuthenticationPlug.checkpw("password", hash)
+ refute AuthenticationPlug.checkpw("password1", hash)
+ end
+
test "it returns false when hash invalid" do
hash =
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
diff --git a/test/pleroma/web/twitter_api/controller_test.exs b/test/pleroma/web/twitter_api/controller_test.exs
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|> hd()
|> Map.keys()
- assert keys -- ["id", "app_name", "valid_until"] == []
+ assert Enum.sort(keys) == Enum.sort(["id", "app_name", "valid_until", "scopes"])
end
test "revoke token", %{token: token} do
diff --git a/test/pleroma/web/views/streamer_view_test.exs b/test/pleroma/web/views/streamer_view_test.exs
@@ -0,0 +1,100 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.StreamerViewTest do
+ use Pleroma.Web.ConnCase, async: true
+ # import ExUnit.CaptureLog
+ import Pleroma.Factory
+
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.StreamerView
+
+ describe "follow_relationships_update.json" do
+ test "shows follower/following count normally" do
+ other_user = insert(:user)
+ %{id: following_id} = following = insert(:user)
+ follower = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(other_user, following)
+ {:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
+
+ result =
+ StreamerView.render(
+ "follow_relationships_update.json",
+ %{follower: follower, following: following, state: :test},
+ "user:test"
+ )
+
+ {:ok, %{"payload" => payload}} = Jason.decode(result)
+
+ {:ok, decoded_payload} = Jason.decode(payload)
+
+ # check the payload updating the user that was followed
+ assert match?(
+ %{"follower_count" => 1, "following_count" => 1, "id" => ^following_id},
+ decoded_payload["following"]
+ )
+ end
+
+ test "hides follower count for :hide_followers and :hide_followers_count" do
+ user_attrs = [%{hide_followers: true}, %{hide_followers_count: true}]
+
+ Enum.each(user_attrs, fn attrs ->
+ other_user = insert(:user)
+ %{id: following_id} = following = insert(:user, attrs)
+ follower = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(other_user, following)
+ {:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
+
+ result =
+ StreamerView.render(
+ "follow_relationships_update.json",
+ %{follower: follower, following: following, state: :test},
+ "user:test"
+ )
+
+ {:ok, %{"payload" => payload}} = Jason.decode(result)
+
+ {:ok, decoded_payload} = Jason.decode(payload)
+
+ # check the payload updating the user that was followed
+ assert match?(
+ %{"follower_count" => 0, "following_count" => 1, "id" => ^following_id},
+ decoded_payload["following"]
+ )
+ end)
+ end
+
+ test "hides follows count for :hide_follows and :hide_follows_count" do
+ user_attrs = [%{hide_follows: true}, %{hide_follows_count: true}]
+
+ Enum.each(user_attrs, fn attrs ->
+ other_user = insert(:user)
+ %{id: following_id} = following = insert(:user, attrs)
+ follower = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(other_user, following)
+ {:ok, following, follower, _activity} = CommonAPI.follow(following, follower)
+
+ result =
+ StreamerView.render(
+ "follow_relationships_update.json",
+ %{follower: follower, following: following, state: :test},
+ "user:test"
+ )
+
+ {:ok, %{"payload" => payload}} = Jason.decode(result)
+
+ {:ok, decoded_payload} = Jason.decode(payload)
+
+ # check the payload updating the user that was followed
+ assert match?(
+ %{"follower_count" => 1, "following_count" => 0, "id" => ^following_id},
+ decoded_payload["following"]
+ )
+ end)
+ end
+ end
+end
diff --git a/test/pleroma/workers/poll_worker_test.exs b/test/pleroma/workers/poll_worker_test.exs
@@ -11,10 +11,10 @@ defmodule Pleroma.Workers.PollWorkerTest do
alias Pleroma.Workers.PollWorker
- test "poll notification job" do
+ test "local poll ending notification job" do
user = insert(:user)
question = insert(:question, user: user)
- activity = insert(:question_activity, question: question)
+ activity = insert(:question_activity, question: question, user: user)
PollWorker.schedule_poll_end(activity)
@@ -44,6 +44,65 @@ defmodule Pleroma.Workers.PollWorkerTest do
# Ensure notifications were streamed out when job executes
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], :_))
assert called(Pleroma.Web.Push.send(:_))
+
+ # Skip refreshing polls for local activities
+ assert activity.local
+
+ refute_enqueued(
+ worker: PollWorker,
+ args: %{"op" => "refresh", "activity_id" => activity.id}
+ )
+ end
+ end
+
+ test "remote poll ending notification job schedules refresh" do
+ user = insert(:user, local: false)
+ question = insert(:question, user: user)
+ activity = insert(:question_activity, question: question, user: user)
+
+ PollWorker.schedule_poll_end(activity)
+
+ expected_job_args = %{"activity_id" => activity.id, "op" => "poll_end"}
+
+ assert_enqueued(args: expected_job_args)
+
+ [job] = all_enqueued(worker: PollWorker)
+ PollWorker.perform(job)
+
+ refute activity.local
+
+ assert_enqueued(
+ worker: PollWorker,
+ args: %{"op" => "refresh", "activity_id" => activity.id}
+ )
+ end
+
+ test "poll refresh" do
+ user = insert(:user, local: false)
+ question = insert(:question, user: user)
+ activity = insert(:question_activity, question: question)
+
+ PollWorker.new(%{"op" => "refresh", "activity_id" => activity.id})
+ |> Oban.insert()
+
+ expected_job_args = %{"activity_id" => activity.id, "op" => "refresh"}
+
+ assert_enqueued(args: expected_job_args)
+
+ with_mocks([
+ {
+ Pleroma.Web.Streamer,
+ [],
+ [
+ stream: fn _, _ -> nil end
+ ]
+ }
+ ]) do
+ [job] = all_enqueued(worker: PollWorker)
+ PollWorker.perform(job)
+
+ # Ensure updates are streamed out
+ assert called(Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], :_))
end
end
end
diff --git a/test/pleroma/workers/purge_expired_activity_test.exs b/test/pleroma/workers/purge_expired_activity_test.exs
@@ -14,10 +14,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do
activity = insert(:note_activity)
assert {:ok, _} =
- PurgeExpiredActivity.enqueue(%{
- activity_id: activity.id,
- expires_at: DateTime.add(DateTime.utc_now(), 3601)
- })
+ PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: activity.id
+ },
+ scheduled_at: DateTime.add(DateTime.utc_now(), 3601)
+ )
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
@@ -34,10 +36,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do
activity = insert(:note_activity)
assert {:ok, _} =
- PurgeExpiredActivity.enqueue(%{
- activity_id: activity.id,
- expires_at: DateTime.add(DateTime.utc_now(), 3601)
- })
+ PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: activity.id
+ },
+ scheduled_at: DateTime.add(DateTime.utc_now(), 3601)
+ )
user = Pleroma.User.get_by_ap_id(activity.actor)
Pleroma.Repo.delete(user)
@@ -48,10 +52,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do
test "error if actiivity was not found" do
assert {:ok, _} =
- PurgeExpiredActivity.enqueue(%{
- activity_id: "some_id",
- expires_at: DateTime.add(DateTime.utc_now(), 3601)
- })
+ PurgeExpiredActivity.enqueue(
+ %{
+ activity_id: "some_id"
+ },
+ scheduled_at: DateTime.add(DateTime.utc_now(), 3601)
+ )
assert {:cancel, :activity_not_found} =
perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: "some_if"})
diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
import Mock
import Pleroma.Factory
+ alias Pleroma.User
alias Pleroma.Web.Federator
alias Pleroma.Workers.ReceiverWorker
@@ -51,25 +52,106 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
})
end
- test "it can validate the signature" do
- Tesla.Mock.mock(fn
- %{url: "https://mastodon.social/users/bastianallgeier"} ->
- %Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/bastianallgeier.json"),
- headers: [{"content-type", "application/activity+json"}]
- }
+ describe "cancels on a failed user fetch" do
+ setup do
+ Tesla.Mock.mock(fn
+ %{url: "https://springfield.social/users/bart"} ->
+ %Tesla.Env{
+ status: 403,
+ body: ""
+ }
- %{url: "https://mastodon.social/users/bastianallgeier/collections/featured"} ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-type", "application/activity+json"}],
- body:
- File.read!("test/fixtures/users_mock/masto_featured.json")
- |> String.replace("{{domain}}", "mastodon.social")
- |> String.replace("{{nickname}}", "bastianallgeier")
- }
+ %{url: "https://springfield.social/users/troymcclure"} ->
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }
+
+ %{url: "https://springfield.social/users/hankscorpio"} ->
+ %Tesla.Env{
+ status: 410,
+ body: ""
+ }
+ end)
+ end
+
+ test "when request returns a 403" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/bart")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :forbidden}} = ReceiverWorker.perform(oban_job)
+ end
+ test "when request returns a 404" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/troymcclure")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "when request returns a 410" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/hankscorpio")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "when user account is disabled" do
+ user = insert(:user)
+
+ fake_activity = URI.parse(user.ap_id) |> Map.put(:path, "/fake-activity") |> to_string
+
+ params =
+ insert(:note_activity, user: user).data
+ |> Map.put("id", fake_activity)
+
+ {:ok, %User{}} = User.set_activation(user, false)
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:user_active, false}} = ReceiverWorker.perform(oban_job)
+ end
+ end
+
+ test "it can validate the signature" do
+ Tesla.Mock.mock(fn
%{url: "https://phpc.social/users/denniskoch"} ->
%Tesla.Env{
status: 200,
@@ -86,136 +168,10 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
|> String.replace("{{domain}}", "phpc.social")
|> String.replace("{{nickname}}", "denniskoch")
}
-
- %{url: "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281"} ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-type", "application/activity+json"}],
- body: File.read!("test/fixtures/receiver_worker_signature_activity.json")
- }
end)
- params = %{
- "@context" => [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
- %{
- "claim" => %{"@id" => "toot:claim", "@type" => "@id"},
- "memorial" => "toot:memorial",
- "atomUri" => "ostatus:atomUri",
- "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
- "blurhash" => "toot:blurhash",
- "ostatus" => "http://ostatus.org#",
- "discoverable" => "toot:discoverable",
- "focalPoint" => %{"@container" => "@list", "@id" => "toot:focalPoint"},
- "votersCount" => "toot:votersCount",
- "Hashtag" => "as:Hashtag",
- "Emoji" => "toot:Emoji",
- "alsoKnownAs" => %{"@id" => "as:alsoKnownAs", "@type" => "@id"},
- "sensitive" => "as:sensitive",
- "movedTo" => %{"@id" => "as:movedTo", "@type" => "@id"},
- "inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
- "conversation" => "ostatus:conversation",
- "Device" => "toot:Device",
- "schema" => "http://schema.org#",
- "toot" => "http://joinmastodon.org/ns#",
- "cipherText" => "toot:cipherText",
- "suspended" => "toot:suspended",
- "messageType" => "toot:messageType",
- "featuredTags" => %{"@id" => "toot:featuredTags", "@type" => "@id"},
- "Curve25519Key" => "toot:Curve25519Key",
- "deviceId" => "toot:deviceId",
- "Ed25519Signature" => "toot:Ed25519Signature",
- "featured" => %{"@id" => "toot:featured", "@type" => "@id"},
- "devices" => %{"@id" => "toot:devices", "@type" => "@id"},
- "value" => "schema:value",
- "PropertyValue" => "schema:PropertyValue",
- "messageFranking" => "toot:messageFranking",
- "publicKeyBase64" => "toot:publicKeyBase64",
- "identityKey" => %{"@id" => "toot:identityKey", "@type" => "@id"},
- "Ed25519Key" => "toot:Ed25519Key",
- "indexable" => "toot:indexable",
- "EncryptedMessage" => "toot:EncryptedMessage",
- "fingerprintKey" => %{"@id" => "toot:fingerprintKey", "@type" => "@id"}
- }
- ],
- "actor" => "https://phpc.social/users/denniskoch",
- "cc" => [
- "https://phpc.social/users/denniskoch/followers",
- "https://mastodon.social/users/bastianallgeier",
- "https://chaos.social/users/distantnative",
- "https://fosstodon.org/users/kev"
- ],
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity",
- "object" => %{
- "atomUri" => "https://phpc.social/users/denniskoch/statuses/112847382711461301",
- "attachment" => [],
- "attributedTo" => "https://phpc.social/users/denniskoch",
- "cc" => [
- "https://phpc.social/users/denniskoch/followers",
- "https://mastodon.social/users/bastianallgeier",
- "https://chaos.social/users/distantnative",
- "https://fosstodon.org/users/kev"
- ],
- "content" =>
- "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>",
- "contentMap" => %{
- "en" =>
- "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>"
- },
- "conversation" =>
- "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation",
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301",
- "inReplyTo" =>
- "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281",
- "inReplyToAtomUri" =>
- "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281",
- "published" => "2024-07-25T13:33:29Z",
- "replies" => %{
- "first" => %{
- "items" => [],
- "next" =>
- "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies?only_other_accounts=true&page=true",
- "partOf" =>
- "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies",
- "type" => "CollectionPage"
- },
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies",
- "type" => "Collection"
- },
- "sensitive" => false,
- "tag" => [
- %{
- "href" => "https://mastodon.social/users/bastianallgeier",
- "name" => "@bastianallgeier@mastodon.social",
- "type" => "Mention"
- },
- %{
- "href" => "https://chaos.social/users/distantnative",
- "name" => "@distantnative@chaos.social",
- "type" => "Mention"
- },
- %{
- "href" => "https://fosstodon.org/users/kev",
- "name" => "@kev@fosstodon.org",
- "type" => "Mention"
- }
- ],
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "type" => "Note",
- "url" => "https://phpc.social/@denniskoch/112847382711461301"
- },
- "published" => "2024-07-25T13:33:29Z",
- "signature" => %{
- "created" => "2024-07-25T13:33:29Z",
- "creator" => "https://phpc.social/users/denniskoch#main-key",
- "signatureValue" =>
- "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==",
- "type" => "RsaSignature2017"
- },
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "type" => "Create"
- }
+ params =
+ File.read!("test/fixtures/receiver_worker_signature_activity.json") |> Jason.decode!()
req_headers = [
["accept-encoding", "gzip"],
@@ -245,4 +201,46 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
assert {:ok, %Pleroma.Activity{}} = ReceiverWorker.perform(oban_job)
end
+
+ test "cancels due to origin containment" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("id", "https://notorigindomain.com/activity")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, :origin_containment_failed} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "canceled due to deleted object" do
+ params =
+ insert(:announce_activity).data
+ |> Map.put("object", "http://localhost:4001/deleted")
+
+ Tesla.Mock.mock(fn
+ %{url: "http://localhost:4001/deleted"} ->
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }
+ end)
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, _} = ReceiverWorker.perform(oban_job)
+ end
end
diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs
@@ -12,6 +12,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
@deleted_object_two "https://deleted-410.example.com/"
@unauthorized_object "https://unauthorized.example.com/"
@depth_object "https://depth.example.com/"
+ @content_type_object "https://bad_content_type.example.com/"
describe "RemoteFetcherWorker" do
setup do
@@ -35,34 +36,48 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do
%Tesla.Env{
status: 200
}
+
+ %{method: :get, url: @content_type_object} ->
+ %Tesla.Env{
+ status: 200,
+ headers: [{"content-type", "application/json"}],
+ body: File.read!("test/fixtures/spoofed-object.json")
+ }
end)
end
- test "does not requeue a deleted object" do
- assert {:cancel, _} =
- RemoteFetcherWorker.perform(%Oban.Job{
- args: %{"op" => "fetch_remote", "id" => @deleted_object_one}
- })
+ test "does not retry jobs for a deleted object" do
+ [
+ %{"op" => "fetch_remote", "id" => @deleted_object_one},
+ %{"op" => "fetch_remote", "id" => @deleted_object_two}
+ ]
+ |> Enum.each(fn job -> assert {:cancel, _} = perform_job(RemoteFetcherWorker, job) end)
+ end
+ test "does not retry jobs for an unauthorized object" do
assert {:cancel, _} =
- RemoteFetcherWorker.perform(%Oban.Job{
- args: %{"op" => "fetch_remote", "id" => @deleted_object_two}
+ perform_job(RemoteFetcherWorker, %{
+ "op" => "fetch_remote",
+ "id" => @unauthorized_object
})
end
- test "does not requeue an unauthorized object" do
+ test "does not retry jobs for an an object that exceeded depth" do
+ clear_config([:instance, :federation_incoming_replies_max_depth], 0)
+
assert {:cancel, _} =
- RemoteFetcherWorker.perform(%Oban.Job{
- args: %{"op" => "fetch_remote", "id" => @unauthorized_object}
+ perform_job(RemoteFetcherWorker, %{
+ "op" => "fetch_remote",
+ "id" => @depth_object,
+ "depth" => 1
})
end
- test "does not requeue an object that exceeded depth" do
- clear_config([:instance, :federation_incoming_replies_max_depth], 0)
-
+ test "does not retry jobs for when object returns wrong content type" do
assert {:cancel, _} =
- RemoteFetcherWorker.perform(%Oban.Job{
- args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1}
+ perform_job(RemoteFetcherWorker, %{
+ "op" => "fetch_remote",
+ "id" => @content_type_object
})
end
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
@@ -53,6 +53,13 @@ defmodule Pleroma.Factory do
keys: pem
}
+ user
+ |> Map.put(:raw_bio, user.bio)
+ |> merge_attributes(Map.delete(attrs, :domain))
+ |> make_user_urls(attrs)
+ end
+
+ defp make_user_urls(user, attrs) do
urls =
if attrs[:local] == false do
base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])
@@ -60,26 +67,22 @@ defmodule Pleroma.Factory do
ap_id = "https://#{base_domain}/users/#{user.nickname}"
%{
- ap_id: ap_id,
- follower_address: ap_id <> "/followers",
- following_address: ap_id <> "/following",
- featured_address: ap_id <> "/collections/featured"
+ ap_id: attrs[:ap_id] || ap_id,
+ follower_address: attrs[:follower_address] || ap_id <> "/followers",
+ following_address: attrs[:following_address] || ap_id <> "/following",
+ featured_address: attrs[:featured_address] || ap_id <> "/collections/featured",
+ inbox: attrs[:inbox] || "https://#{base_domain}/inbox"
}
else
%{
- ap_id: User.ap_id(user),
- follower_address: User.ap_followers(user),
- following_address: User.ap_following(user),
- featured_address: User.ap_featured_collection(user)
+ ap_id: attrs[:ap_id] || User.ap_id(user),
+ follower_address: attrs[:follower_address] || User.ap_followers(user),
+ following_address: attrs[:following_address] || User.ap_following(user),
+ featured_address: attrs[:featured_address] || User.ap_featured_collection(user)
}
end
- attrs = Map.delete(attrs, :domain)
-
- user
- |> Map.put(:raw_bio, user.bio)
- |> Map.merge(urls)
- |> merge_attributes(attrs)
+ Map.merge(user, urls)
end
def user_relationship_factory(attrs \\ %{}) do
@@ -238,6 +241,7 @@ defmodule Pleroma.Factory do
def question_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user)
+ closed = attrs[:closed] || DateTime.utc_now() |> DateTime.add(86_400) |> DateTime.to_iso8601()
data = %{
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
@@ -248,7 +252,7 @@ defmodule Pleroma.Factory do
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [user.follower_address],
"context" => Pleroma.Web.ActivityPub.Utils.generate_context_id(),
- "closed" => DateTime.utc_now() |> DateTime.add(86_400) |> DateTime.to_iso8601(),
+ "closed" => closed,
"content" => "Which flavor of ice cream do you prefer?",
"oneOf" => [
%{
@@ -506,7 +510,8 @@ defmodule Pleroma.Factory do
%Pleroma.Activity{
data: data,
actor: data["actor"],
- recipients: data["to"]
+ recipients: data["to"],
+ local: user.local
}
|> Map.merge(attrs)
end