diff options
184 files changed, 3345 insertions, 1193 deletions
| diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e04dae76..675d0e067 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,8 @@ -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.17.3    POSTGRES_DB: pleroma_test    POSTGRES_USER: postgres    POSTGRES_PASSWORD: postgres @@ -71,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 @@ -119,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 @@ -134,7 +134,7 @@ unit-testing-1.13.4-otp-25:    script: &testing_script      - mix ecto.create      - mix ecto.migrate -    - mix test --cover --preload-modules +    - mix pleroma.test_runner --cover --preload-modules    coverage: '/^Line total: ([^ ]*%)$/'    artifacts:      reports: @@ -272,7 +272,8 @@ stop_review_app:  amd64:    stage: release -  image: elixir:$ELIXIR_VER +  image:  +    name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011    only: &release-only    - stable@pleroma/pleroma    - develop@pleroma/pleroma @@ -297,8 +298,9 @@ amd64:    variables: &release-variables      MIX_ENV: prod      VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS +    DEBIAN_FRONTEND: noninteractive    before_script: &before-release -  - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev +  - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git    - echo "import Config" > config/prod.secret.exs    - mix local.hex --force    - mix local.rebar --force @@ -313,7 +315,8 @@ amd64-musl:    stage: release    artifacts: *release-artifacts    only: *release-only -  image: elixir:$ELIXIR_VER-alpine +  image:  +    name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-alpine-3.17.9    tags:      - amd64    cache: *release-cache @@ -327,6 +330,7 @@ amd64-musl:  arm:    stage: release +  allow_failure: true    artifacts: *release-artifacts    only: *release-only    tags: @@ -355,7 +359,8 @@ arm64:    only: *release-only    tags:      - arm -  image: arm64v8/elixir:$ELIXIR_VER +  image:  +    name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011    cache: *release-cache    variables: *release-variables    before_script: *before-release @@ -367,7 +372,8 @@ arm64-musl:    only: *release-only    tags:      - arm -  image: arm64v8/elixir:$ELIXIR_VER-alpine +  image:  +    name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-alpine-3.17.9    cache: *release-cache    variables: *release-variables    before_script: *before-release-musl diff --git a/CHANGELOG.md b/CHANGELOG.md index 61bb2ab54..71178c89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,77 @@ 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 +- Accept `application/activity+json` for requests to `/.well-known/nodeinfo` + +### Fixed +- Truncate remote user fields, avoids them getting rejected +- Improve the `FollowValidator` to successfully incoming activities with an errant `cc` field. +- Resolved edge case where the API can report you are following a user but the relationship is not fully established. +- The Swoosh email adapter for Mailgun was missing a new dependency on `:multipart` +- Fix Mastodon WebSocket authentication +  ## 2.7.0  ### Security diff --git a/Dockerfile b/Dockerfile index 72461305c..fff58154e 100644 --- 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/changelog.d/301-small-image-redirect.change b/changelog.d/301-small-image-redirect.change new file mode 100644 index 000000000..c5be80539 --- /dev/null +++ b/changelog.d/301-small-image-redirect.change @@ -0,0 +1 @@ +Performance: Use 301 (permanent) redirect instead of 302 (temporary) when redirecting small images in media proxy. This allows browsers to cache the redirect response. 
\ No newline at end of file diff --git a/changelog.d/actor-published-date.add b/changelog.d/actor-published-date.add new file mode 100644 index 000000000..feac85894 --- /dev/null +++ b/changelog.d/actor-published-date.add @@ -0,0 +1 @@ +Include "published" in actor view diff --git a/changelog.d/backup-links.add b/changelog.d/backup-links.add new file mode 100644 index 000000000..ff19e736b --- /dev/null +++ b/changelog.d/backup-links.add @@ -0,0 +1 @@ +Link to exported outbox/followers/following collections in backup actor.json diff --git a/changelog.d/bugfix-truncate-remote-user-fields.fix b/changelog.d/bugfix-truncate-remote-user-fields.fix deleted file mode 100644 index 239a3c224..000000000 --- a/changelog.d/bugfix-truncate-remote-user-fields.fix +++ /dev/null @@ -1 +0,0 @@ -Truncate remote user fields, avoids them getting rejected diff --git a/changelog.d/ci-git-fetch.skip b/changelog.d/ci-builder-skip-arm32.skip index e69de29bb..e69de29bb 100644 --- a/changelog.d/ci-git-fetch.skip +++ b/changelog.d/ci-builder-skip-arm32.skip diff --git a/changelog.d/deprecate-subscribe.change b/changelog.d/deprecate-subscribe.change deleted file mode 100644 index bd7e8aec7..000000000 --- a/changelog.d/deprecate-subscribe.change +++ /dev/null @@ -1 +0,0 @@ -Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe`
\ No newline at end of file diff --git a/changelog.d/commonapi.skip b/changelog.d/description-update-suggestions.skip index e69de29bb..e69de29bb 100644 --- a/changelog.d/commonapi.skip +++ b/changelog.d/description-update-suggestions.skip diff --git a/changelog.d/fix-mastodon-edits.fix b/changelog.d/fix-mastodon-edits.fix new file mode 100644 index 000000000..2e79977e0 --- /dev/null +++ b/changelog.d/fix-mastodon-edits.fix @@ -0,0 +1 @@ +Fix Mastodon incoming edits with inlined "likes" diff --git a/changelog.d/follow-request.fix b/changelog.d/follow-request.fix deleted file mode 100644 index 59d34e9bf..000000000 --- a/changelog.d/follow-request.fix +++ /dev/null @@ -1 +0,0 @@ -Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them. diff --git a/changelog.d/follow-validator.fix b/changelog.d/follow-validator.fix deleted file mode 100644 index d49932b7b..000000000 --- a/changelog.d/follow-validator.fix +++ /dev/null @@ -1 +0,0 @@ -Improve the FollowValidator to successfully incoming activities with an errant cc field. diff --git a/changelog.d/get-statuses-param.change b/changelog.d/get-statuses-param.change deleted file mode 100644 index 3edcad268..000000000 --- a/changelog.d/get-statuses-param.change +++ /dev/null @@ -1 +0,0 @@ -Support `id` param in `GET /api/v1/statuses`
\ No newline at end of file diff --git a/changelog.d/fix-test-failures.skip b/changelog.d/hexpm-build-images.skip index e69de29bb..e69de29bb 100644 --- a/changelog.d/fix-test-failures.skip +++ b/changelog.d/hexpm-build-images.skip diff --git a/changelog.d/identity-proofs.remove b/changelog.d/identity-proofs.remove deleted file mode 100644 index efe1c34f5..000000000 --- a/changelog.d/identity-proofs.remove +++ /dev/null @@ -1 +0,0 @@ -Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0)
\ No newline at end of file diff --git a/changelog.d/mailgun.fix b/changelog.d/mailgun.fix deleted file mode 100644 index 855588752..000000000 --- a/changelog.d/mailgun.fix +++ /dev/null @@ -1 +0,0 @@ -The Swoosh email adapter for Mailgun was missing a new dependency on :multipart diff --git a/changelog.d/mogrify.skip b/changelog.d/mogrify.skip deleted file mode 100644 index e69de29bb..000000000 --- a/changelog.d/mogrify.skip +++ /dev/null diff --git a/changelog.d/mrf-cleanup.skip b/changelog.d/mrf-cleanup.skip deleted file mode 100644 index e69de29bb..000000000 --- a/changelog.d/mrf-cleanup.skip +++ /dev/null diff --git a/changelog.d/mrf-fodirectreply.add b/changelog.d/mrf-fodirectreply.add deleted file mode 100644 index 10fd5d16a..000000000 --- a/changelog.d/mrf-fodirectreply.add +++ /dev/null @@ -1 +0,0 @@ -Added MRF.FODirectReply which changes replies to followers-only posts to be direct. diff --git a/changelog.d/mrf-quietreply.add b/changelog.d/mrf-quietreply.add deleted file mode 100644 index 4ed20bce6..000000000 --- a/changelog.d/mrf-quietreply.add +++ /dev/null @@ -1 +0,0 @@ -Added MRF.QuietReply which prevents replies to public posts from being published to the timelines diff --git a/changelog.d/notifications-marker.change b/changelog.d/notifications-marker.change deleted file mode 100644 index 9e350a95c..000000000 --- a/changelog.d/notifications-marker.change +++ /dev/null @@ -1 +0,0 @@ -Fix 'Setting a marker should mark notifications as read'
\ No newline at end of file diff --git a/changelog.d/oban_gun_snooze.change b/changelog.d/oban_gun_snooze.change deleted file mode 100644 index c94525b2a..000000000 --- a/changelog.d/oban_gun_snooze.change +++ /dev/null @@ -1 +0,0 @@ -Publisher behavior improvement when snoozing Oban jobs due to Gun connection pool contention. diff --git a/changelog.d/publisher-reachability.fix b/changelog.d/publisher-reachability.fix deleted file mode 100644 index 3f50be581..000000000 --- a/changelog.d/publisher-reachability.fix +++ /dev/null @@ -1 +0,0 @@ -Address case where instance reachability status couldn't be updated diff --git a/changelog.d/remote-object-fetcher.fix b/changelog.d/remote-object-fetcher.fix deleted file mode 100644 index dcf2b1b31..000000000 --- a/changelog.d/remote-object-fetcher.fix +++ /dev/null @@ -1 +0,0 @@ -Remote Fetcher Worker recognizes more permanent failure errors diff --git a/changelog.d/stream-follow-relationships-count.fix b/changelog.d/stream-follow-relationships-count.fix deleted file mode 100644 index 68452a88b..000000000 --- a/changelog.d/stream-follow-relationships-count.fix +++ /dev/null @@ -1 +0,0 @@ -StreamerView: Do not leak follows count if hidden
\ No newline at end of file diff --git a/changelog.d/text-extensions.skip b/changelog.d/text-extensions.skip deleted file mode 100644 index e69de29bb..000000000 --- a/changelog.d/text-extensions.skip +++ /dev/null diff --git a/changelog.d/update-oban.change b/changelog.d/update-oban.change deleted file mode 100644 index a67b3e3cf..000000000 --- a/changelog.d/update-oban.change +++ /dev/null @@ -1 +0,0 @@ -Update Oban to 2.18 diff --git a/changelog.d/user-factory.skip b/changelog.d/user-factory.skip deleted file mode 100644 index e69de29bb..000000000 --- a/changelog.d/user-factory.skip +++ /dev/null diff --git a/changelog.d/vips-blurhash.fix b/changelog.d/vips-blurhash.fix new file mode 100644 index 000000000..9e8951b15 --- /dev/null +++ b/changelog.d/vips-blurhash.fix @@ -0,0 +1 @@ +Fix blurhash generation crashes diff --git a/changelog.d/workerhelper.change b/changelog.d/workerhelper.change deleted file mode 100644 index 539c9b54f..000000000 --- a/changelog.d/workerhelper.change +++ /dev/null @@ -1 +0,0 @@ -Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues. diff --git a/ci/elixir-1.12/build_and_push.sh b/ci/elixir-1.12/build_and_push.sh deleted file mode 100755 index 508262ed8..000000000 --- a/ci/elixir-1.12/build_and_push.sh +++ /dev/null @@ -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 deleted file mode 100644 index 25a1639e8..000000000 --- a/ci/elixir-1.13.4-otp-25/Dockerfile +++ /dev/null @@ -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.12/Dockerfile b/ci/elixir-1.14.5-otp-25/Dockerfile index a2b566873..3a35c84c3 100644 --- a/ci/elixir-1.12/Dockerfile +++ b/ci/elixir-1.14.5-otp-25/Dockerfile @@ -1,4 +1,4 @@ -FROM elixir:1.12.3 +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 diff --git a/ci/elixir-1.13.4-otp-25/build_and_push.sh b/ci/elixir-1.14.5-otp-25/build_and_push.sh index b8ca1d24d..912c47d0c 100755 --- a/ci/elixir-1.13.4-otp-25/build_and_push.sh +++ b/ci/elixir-1.14.5-otp-25/build_and_push.sh @@ -1 +1 @@ -docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-25 --push . +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 index ad6b1cb94..07e98011d 100644 --- 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,7 +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} +    {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}, +    {"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}    ]  config :pleroma, Pleroma.Formatter, @@ -611,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") @@ -711,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 index 15faecb38..e8d154124 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2241,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", @@ -2268,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", @@ -2292,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"]        }      ]    }, @@ -3300,8 +3302,7 @@ config :pleroma, :config_description, [          suggestions: [            Pleroma.Web.Preload.Providers.Instance,            Pleroma.Web.Preload.Providers.User, -          Pleroma.Web.Preload.Providers.Timelines, -          Pleroma.Web.Preload.Providers.StatusNet +          Pleroma.Web.Preload.Providers.Timelines          ]        }      ] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 0b4e53b6f..36e9cbba2 100644 --- 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 index 5b373b8e1..409e78a1e 100644 --- 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 index 41464e802..cbd0d6bce 100644 --- 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. diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md index b61e4addd..21cfe2bff 100644 --- 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 index 5a0823a63..0817934ff 100644 --- 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 index 02513daf2..920bc7d35 100644 --- 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 index bdb7f94d3..9f07f62c6 100644 --- 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 index e58e144d2..78bbf399f 100644 --- 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 index 73aca3a6f..d7c94d8a0 100644 --- 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 new file mode 100644 index 000000000..463dabbfb --- /dev/null +++ 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/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 8379a0c25..edce9e871 100644 --- 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/mix/tasks/pleroma/test_runner.ex b/lib/mix/tasks/pleroma/test_runner.ex new file mode 100644 index 000000000..69fefb001 --- /dev/null +++ b/lib/mix/tasks/pleroma/test_runner.ex @@ -0,0 +1,25 @@ +defmodule Mix.Tasks.Pleroma.TestRunner do +  @shortdoc "Retries tests once if they fail" + +  use Mix.Task + +  def run(args \\ []) do +    case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do +      {_, 0} -> +        :ok + +      _ -> +        retry(args) +    end +  end + +  def retry(args) do +    case System.cmd("mix", ["test", "--failed"] ++ args, into: IO.stream(:stdio, :line)) do +      {_, 0} -> +        :ok + +      _ -> +        exit(1) +    end +  end +end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index cb15dc1e9..3f199c002 100644 --- 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 index ffc95f144..140dd7711 100644 --- 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 index a018e91fc..2d08cd7a1 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -87,6 +87,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/frontend.ex b/lib/pleroma/frontend.ex index 816499917..a4f427ae5 100644 --- 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 index dcb27a29d..32c1080f7 100644 --- 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 new file mode 100644 index 000000000..181caed7e --- /dev/null +++ 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 index 1fe8dd4b2..30ba26765 100644 --- 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/ldap.ex b/lib/pleroma/ldap.ex new file mode 100644 index 000000000..b591c2918 --- /dev/null +++ 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 index 5020a8ff8..1afbde484 100644 --- 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/object.ex b/lib/pleroma/object.ex index 748f18e6c..77dfda851 100644 --- 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 diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 9d9a201ca..c85a8b09f 100644 --- 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])}, @@ -141,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 @@ -156,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 index bcfcd1243..af2d35c8f 100644 --- 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/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 9bba5b30f..cafae8099 100644 --- 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/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex index 7ee643277..a8480bf36 100644 --- a/lib/pleroma/upload/filter/analyze_metadata.ex +++ b/lib/pleroma/upload/filter/analyze_metadata.ex @@ -90,9 +90,13 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do        {:ok, rgb} =          if Image.has_alpha?(resized_image) do            # remove alpha channel -          resized_image -          |> Operation.extract_band!(0, n: 3) -          |> Image.write_to_binary() +          case Operation.extract_band(resized_image, 0, n: 3) do +            {:ok, data} -> +              Image.write_to_binary(data) + +            _ -> +              Image.write_to_binary(resized_image) +          end          else            Image.write_to_binary(resized_image)          end diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex index ef793d390..7b278d299 100644 --- 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 index c6c536943..7a36ece77 100644 --- 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) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 7feaa22bf..cdff297a9 100644 --- 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 @@ -250,7 +246,13 @@ defmodule Pleroma.User.Backup do    defp actor(dir, user) do      with {:ok, json} <-             UserView.render("user.json", %{user: user}) -           |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"}) +           |> Map.merge(%{ +             "bookmarks" => "bookmarks.json", +             "likes" => "likes.json", +             "outbox" => "outbox.json", +             "followers" => "followers.json", +             "following" => "following.json" +           })             |> Jason.encode() do        File.write(Path.join(dir, "actor.json"), json)      end @@ -297,9 +299,6 @@ defmodule Pleroma.User.Backup do                )                acc - -            _ -> -              acc            end          end) diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index 11905237c..ab6bdb8d4 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -5,87 +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.new(%{ -      "op" => "blocks_import", -      "user_id" => blocker.id, -      "identifiers" => identifiers -    }) -    |> Oban.insert() +  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.new(%{ -      "op" => "follow_import", -      "user_id" => follower.id, -      "identifiers" => identifiers -    }) -    |> Oban.insert() +  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.new(%{ -      "op" => "mutes_import", -      "user_id" => user.id, -      "identifiers" => identifiers -    }) -    |> Oban.insert() +  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 index a2a94a0ff..df8795fe4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1542,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 diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index cdd054e1a..7ac0bbab4 100644 --- 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 index bc418d908..51ab476b7 100644 --- 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/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index e4fcc9935..cf07db7f3 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -14,5 +14,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do    end    @impl true +  def id_filter(id) do +    Logger.debug("REJECTING #{id}") +    false +  end + +  @impl true    def describe, do: {:ok, %{}}  end diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex index 54ca4b735..08bcac08a 100644 --- a/lib/pleroma/web/activity_pub/mrf/policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/policy.ex @@ -4,6 +4,7 @@  defmodule Pleroma.Web.ActivityPub.MRF.Policy do    @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/remote_report_policy.ex b/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex new file mode 100644 index 000000000..fa0610bf1 --- /dev/null +++ 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 index ae7f18bfe..a97e8db7b 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -192,6 +192,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do    end    @impl true +  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) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index abec9b038..ee12f3ebf 100644 --- 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 @@ -39,6 +41,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 @@ -165,7 +177,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} @@ -173,7 +185,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} @@ -203,9 +215,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/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 4e27284aa..81ab354fe 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -85,6 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do      |> fix_replies()      |> fix_attachments()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> Transmogrifier.fix_content_map()      |> CommonFixes.maybe_add_language() diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index 65ac6bb93..034c6f33f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -100,6 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> fix_url()      |> fix_content() diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index a9dc4a312..87d3e0c8f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -119,6 +119,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do    def fix_quote_url(data), do: data +  # On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`, +  # not a list of users. +  # https://github.com/mastodon/mastodon/pull/32007 +  def fix_likes(%{"likes" => %{}} = data), do: Map.drop(data, ["likes"]) + +  def fix_likes(data), do: data +    # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md    def object_link_tag?(%{          "type" => "Link", diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index ec23770ad..ea14d6aca 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -48,6 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do      data      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> CommonFixes.maybe_add_language()      |> CommonFixes.maybe_add_content_map() diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 7f9d4d648..21940f4f1 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -64,6 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do      |> CommonFixes.fix_actor()      |> CommonFixes.fix_object_defaults()      |> CommonFixes.fix_quote_url() +    |> CommonFixes.fix_likes()      |> Transmogrifier.fix_emoji()      |> fix_closed()    end diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex index 1e940a400..aab90235f 100644 --- 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 index 7f11a4d67..fc36935d5 100644 --- 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/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 937e4fd67..61975387b 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -127,10 +127,25 @@ defmodule Pleroma.Web.ActivityPub.UserView do        "capabilities" => capabilities,        "alsoKnownAs" => user.also_known_as,        "vcard:bday" => birthday, -      "webfinger" => "acct:#{User.full_nickname(user)}" +      "webfinger" => "acct:#{User.full_nickname(user)}", +      "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)      } -    |> 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 +320,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 index d9614bc48..21a779dcb 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -813,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 index e6df21246..588b42e06 100644 --- 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 index 2dc0f66df..94d1f6b82 100644 --- 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/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 8aeb821a8..1f73ef60c 100644 --- 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 index 6e537b5da..25548d75b 100644 --- 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 index 01bf1575c..95be892cd 100644 --- 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 index ea5620cf6..ec6601fb9 100644 --- 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 index 09a58eb66..0da3f19fc 100644 --- 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 index a077cfa41..97b901036 100644 --- 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 index 921e414c3..412424dae 100644 --- 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]) @@ -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,7 +558,7 @@ 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) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index fef907ace..bab3c9fd0 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.Endpoint do      websocket: [        path: "/",        compress: false, +      connect_info: [:sec_websocket_protocol],        error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []},        fullsweep_after: 20      ] diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 4a0885fab..6637848a9 100644 --- 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 index 2df716556..58260afa8 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -102,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)}, @@ -121,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 index e60767327..02d639296 100644 --- 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 index 6657c2b3e..304313068 100644 --- 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 index 54d46c86b..68157b0c4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -232,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:      # @@ -277,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") diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 844673ae0..6cfeb712e 100644 --- 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/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 056bad844..41056d389 100644 --- 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 index a2af8148c..6526457df 100644 --- 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/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 6976ca6e5..f6727d29d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -92,14 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          User.get_follow_state(reading_user, target)        end -    followed_by = -      if following_relationships do -        case FollowingRelationship.find(following_relationships, target, reading_user) do -          %{state: :follow_accept} -> true -          _ -> false -        end -      else -        User.following?(target, reading_user) +    followed_by = FollowingRelationship.following?(target, reading_user) +    following = FollowingRelationship.following?(reading_user, target) + +    requested = +      cond do +        following -> false +        true -> match?(:follow_pending, follow_state)        end      subscribing = @@ -114,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags      %{        id: to_string(target.id), -      following: follow_state == :follow_accept, +      following: following,        followed_by: followed_by,        blocking:          UserRelationship.exists?( @@ -150,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          ),        subscribing: subscribing,        notifying: subscribing, -      requested: follow_state == :follow_pending, +      requested: requested,        domain_blocking: User.blocks_domain?(reading_user, target),        showing_reblogs:          not UserRelationship.exists?( @@ -220,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, @@ -322,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 index 3f2478719..c277af98b 100644 --- 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 index a739b5d1a..10966edd6 100644 --- 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 @@ -851,4 +840,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/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 730295a4c..3ed1cdd6c 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do    # This only prepares the connection and is not in the process yet    @impl Phoenix.Socket.Transport    def connect(%{params: params} = transport_info) do -    with access_token <- Map.get(params, "access_token"), +    with access_token <- find_access_token(transport_info),           {:ok, user, oauth_token} <- authenticate_request(access_token),           {:ok, topic} <-             Streamer.get_topic(params["stream"], user, oauth_token, params) do @@ -244,4 +244,13 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do    def handle_error(conn, _reason) do      Plug.Conn.send_resp(conn, 404, "Not Found")    end + +  defp find_access_token(%{ +         connect_info: %{sec_websocket_protocol: [token]} +       }), +       do: token + +  defp find_access_token(%{params: %{"access_token" => token}}), do: token + +  defp find_access_token(_), do: nil  end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 0b446e0a6..a0aafc32e 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -71,11 +71,15 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do            drop_static_param_and_redirect(conn)          content_type == "image/gif" -> -          redirect(conn, external: media_proxy_url) +          conn +          |> put_status(301) +          |> redirect(external: media_proxy_url)          min_content_length_for_preview() > 0 and content_length > 0 and              content_length < min_content_length_for_preview() -> -          redirect(conn, external: media_proxy_url) +          conn +          |> put_status(301) +          |> redirect(external: media_proxy_url)          true ->            handle_preview(content_type, conn, media_proxy_url) diff --git a/lib/pleroma/web/metadata.ex b/lib/pleroma/web/metadata.ex index 59d018730..4ee7c41ec 100644 --- 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 new file mode 100644 index 000000000..bd9f92332 --- /dev/null +++ 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 index e97d6a54f..5a0f2338e 100644 --- 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 index 97d3865ed..fa5fbe553 100644 --- 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 index eabd8cb00..39aa71f06 100644 --- 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 index 426022c65..7f50877c3 100644 --- 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 index d1bf6dd18..7661c2566 100644 --- 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 index 47b03215f..0b3de5481 100644 --- 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/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex index 96466f192..d65c30dab 100644 --- 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 index f912a1542..af7d7f45a 100644 --- 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 new file mode 100644 index 000000000..0064cce76 --- /dev/null +++ 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 index 6d777142e..77f77f88e 100644 --- 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()) diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index e2889b351..d4be97957 100644 --- 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 index 6492e3861..0423ca9e2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,7 +189,7 @@ defmodule Pleroma.Web.Router do    end    pipeline :well_known do -    plug(:accepts, ["json", "jrd", "jrd+json", "xml", "xrd+xml"]) +    plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml"])    end    pipeline :config do @@ -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 @@ -920,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 index 6805233df..aeafa195d 100644 --- 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 index 2e492c13f..36776ce3b 100644 --- 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/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 60da2d5ca..4737c6ea2 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -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{ diff --git a/lib/pleroma/workers/cron/app_cleanup_worker.ex b/lib/pleroma/workers/cron/app_cleanup_worker.ex new file mode 100644 index 000000000..ee71cd7b6 --- /dev/null +++ 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/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex index d263aa1b9..a9afe9d63 100644 --- a/lib/pleroma/workers/poll_worker.ex +++ b/lib/pleroma/workers/poll_worker.ex @@ -11,27 +11,46 @@ defmodule Pleroma.Workers.PollWorker do    alias Pleroma.Activity    alias Pleroma.Notification    alias Pleroma.Object +  alias Pleroma.Object.Fetcher + +  @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 true -  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/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index d4db97b63..11b672bef 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ReceiverWorker do    alias Pleroma.User    alias Pleroma.Web.Federator -  use Oban.Worker, queue: :federator_incoming, max_attempts: 5 +  use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]    @impl true @@ -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 @@ -56,17 +56,29 @@ defmodule Pleroma.Workers.ReceiverWorker do    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 index 9d3f1ec53..aa09362f5 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Workers.RemoteFetcherWorker do    alias Pleroma.Object.Fetcher -  use Oban.Worker, queue: :background +  use Oban.Worker, queue: :background, unique: [period: :infinity]    @impl true    def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do diff --git a/lib/pleroma/workers/rich_media_worker.ex b/lib/pleroma/workers/rich_media_worker.ex index d5ba7b63e..e351ecd6e 100644 --- a/lib/pleroma/workers/rich_media_worker.ex +++ b/lib/pleroma/workers/rich_media_worker.ex @@ -7,7 +7,7 @@ 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 true    def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex index 222a4a8f7..ee276774b 100644 --- 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 diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index f4232d02a..879b26cc3 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.WebPusherWorker do    alias Pleroma.Repo    alias Pleroma.Web.Push.Impl -  use Oban.Worker, queue: :web_push +  use Oban.Worker, queue: :web_push, unique: [period: :infinity]    @impl true    def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do @@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do    def project do      [        app: :pleroma, -      version: version("2.7.0"), -      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], @@ -132,7 +132,8 @@ defmodule Pleroma.Mixfile do    # Type `mix help deps` for examples and options.    defp deps do      [ -      {:phoenix, "~> 1.7.3"}, +      {:phoenix, +       git: "https://github.com/feld/phoenix", branch: "v1.7.14-websocket-headers", override: true},        {:phoenix_ecto, "~> 4.4"},        {:ecto_sql, "~> 3.10"},        {:ecto_enum, "~> 1.4"}, @@ -153,7 +154,7 @@ 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"}, @@ -169,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"}, @@ -203,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}, @@ -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"}, @@ -22,7 +23,7 @@    "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},    "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},    "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, -  "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, +  "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},    "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},    "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},    "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, @@ -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"}, @@ -71,6 +72,7 @@    "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"}, @@ -84,18 +86,19 @@    "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},    "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.18.2", "583e78965ee15263ac968e38c983bad169ae55eadaa8e1e39912562badff93ba", [: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", "9dd25fd35883a91ed995e9fe516e479344d3a8623dfe2b8c3fc8e5be0228ec3a"}, +  "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"},    "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},    "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, -  "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, +  "phoenix": {:git, "https://github.com/feld/phoenix", "fb6dc76c657422e49600896c64aab4253fceaef6", [branch: "v1.7.14-websocket-headers"]},    "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},    "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},    "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"}, diff --git a/priv/gettext/fr/LC_MESSAGES/config_descriptions.po b/priv/gettext/fr/LC_MESSAGES/config_descriptions.po index e43db68aa..c24ab6751 100644 --- 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 index ff9ad5245..a56c90724 100644 --- 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 index ed0d1576b..7d5828fae 100644 --- 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 index 4431445e3..668472939 100644 --- 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 new file mode 100644 index 000000000..204414836 --- /dev/null +++ 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 index c486a5486..930f5db1e 100644 --- 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/20240904142434_assign_app_user.exs b/priv/repo/migrations/20240904142434_assign_app_user.exs new file mode 100644 index 000000000..11bec529b --- /dev/null +++ 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 index a75a6465d..dad9dc1a1 100644 --- 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 index 6e23b3efb..4df840735 100644 --- 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 index 71e803264..8e38fee4b 100644 --- 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 deleted file mode 100644 index 6b47e7db9..000000000 --- a/test/fixtures/bastianallgeier.json +++ /dev/null @@ -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/break_analyze.png b/test/fixtures/break_analyze.pngBinary files differ new file mode 100644 index 000000000..b5e91b08a --- /dev/null +++ b/test/fixtures/break_analyze.png diff --git a/test/fixtures/mastodon-update-with-likes.json b/test/fixtures/mastodon-update-with-likes.json new file mode 100644 index 000000000..3bdb3ba3d --- /dev/null +++ b/test/fixtures/mastodon-update-with-likes.json @@ -0,0 +1,90 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    { +      "atomUri": "ostatus:atomUri", +      "conversation": "ostatus:conversation", +      "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +      "ostatus": "http://ostatus.org#", +      "sensitive": "as:sensitive", +      "toot": "http://joinmastodon.org/ns#", +      "votersCount": "toot:votersCount" +    }, +    "https://w3id.org/security/v1" +  ], +  "actor": "https://pol.social/users/mkljczk", +  "cc": ["https://www.w3.org/ns/activitystreams#Public", +   "https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk", +   "https://pl.fediverse.pl/users/mkljczk", +   "https://fedi.kutno.pl/users/mkljczk"], +  "id": "https://pol.social/users/mkljczk/statuses/113907871635572263#updates/1738096776", +  "object": { +    "atomUri": "https://pol.social/users/mkljczk/statuses/113907871635572263", +    "attachment": [], +    "attributedTo": "https://pol.social/users/mkljczk", +    "cc": ["https://www.w3.org/ns/activitystreams#Public", +     "https://pol.social/users/aemstuz", "https://gts.mkljczk.pl/users/mkljczk", +     "https://pl.fediverse.pl/users/mkljczk", +     "https://fedi.kutno.pl/users/mkljczk"], +    "content": "<p>test</p>", +    "contentMap": { +      "pl": "<p>test</p>" +    }, +    "conversation": "https://fedi.kutno.pl/contexts/43c14c70-d3fb-42b4-a36d-4eacfab9695a", +    "id": "https://pol.social/users/mkljczk/statuses/113907871635572263", +    "inReplyTo": "https://pol.social/users/aemstuz/statuses/113907854282654767", +    "inReplyToAtomUri": "https://pol.social/users/aemstuz/statuses/113907854282654767", +    "likes": { +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/likes", +      "totalItems": 1, +      "type": "Collection" +    }, +    "published": "2025-01-28T20:29:45Z", +    "replies": { +      "first": { +        "items": [], +        "next": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies?only_other_accounts=true&page=true", +        "partOf": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies", +        "type": "CollectionPage" +      }, +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/replies", +      "type": "Collection" +    }, +    "sensitive": false, +    "shares": { +      "id": "https://pol.social/users/mkljczk/statuses/113907871635572263/shares", +      "totalItems": 0, +      "type": "Collection" +    }, +    "summary": null, +    "tag": [ +      { +        "href": "https://pol.social/users/aemstuz", +        "name": "@aemstuz", +        "type": "Mention" +      }, +      { +        "href": "https://gts.mkljczk.pl/users/mkljczk", +        "name": "@mkljczk@gts.mkljczk.pl", +        "type": "Mention" +      }, +      { +        "href": "https://pl.fediverse.pl/users/mkljczk", +        "name": "@mkljczk@fediverse.pl", +        "type": "Mention" +      }, +      { +        "href": "https://fedi.kutno.pl/users/mkljczk", +        "name": "@mkljczk@fedi.kutno.pl", +        "type": "Mention" +      } +    ], +    "to": ["https://pol.social/users/mkljczk/followers"], +    "type": "Note", +    "updated": "2025-01-28T20:39:36Z", +    "url": "https://pol.social/@mkljczk/113907871635572263" +  }, +  "published": "2025-01-28T20:39:36Z", +  "to": ["https://pol.social/users/mkljczk/followers"], +  "type": "Update" +} diff --git a/test/fixtures/receiver_worker_signature_activity.json b/test/fixtures/receiver_worker_signature_activity.json index 3c3fb3fd2..19dc0087f 100644 --- 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/pleroma/html_test.exs b/test/pleroma/html_test.exs index 1be161971..d17b07540 100644 --- 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/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index f499f54ad..88f32762d 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -268,6 +268,17 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do        end)      end +    test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do +      assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}]) + +      capture_log(fn -> +        assert {:error, %WebSockex.RequestError{code: 401}} = +                 start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}]) + +        Process.sleep(30) +      end) +    end +      test "accepts valid token on client-sent event", %{token: token} do        assert {:ok, pid} = start_socket() diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs index 48d4d86eb..ed5c2b6c8 100644 --- 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 new file mode 100644 index 000000000..5a4293189 --- /dev/null +++ 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/analyze_metadata_test.exs b/test/pleroma/upload/filter/analyze_metadata_test.exs index e4ac673b2..6e1f2afaf 100644 --- a/test/pleroma/upload/filter/analyze_metadata_test.exs +++ b/test/pleroma/upload/filter/analyze_metadata_test.exs @@ -34,6 +34,20 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadataTest do      assert meta.blurhash == "eXJi-E:SwCEm5rCmn$+YWYn+15K#5A$xxCi{SiV]s*W:Efa#s.jE-T"    end +  test "it gets dimensions for grayscale images" do +    upload = %Pleroma.Upload{ +      name: "break_analyze.png", +      content_type: "image/png", +      path: Path.absname("test/fixtures/break_analyze.png"), +      tempfile: Path.absname("test/fixtures/break_analyze.png") +    } + +    {:ok, :filtered, meta} = AnalyzeMetadata.filter(upload) + +    assert %{width: 1410, height: 2048} = meta +    assert is_nil(meta.blurhash) +  end +    test "adds the dimensions for videos" do      upload = %Pleroma.Upload{        name: "coolvideo.mp4", diff --git a/test/pleroma/upload/filter/dedupe_test.exs b/test/pleroma/upload/filter/dedupe_test.exs index 29c181509..4dc28b998 100644 --- 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 index facb634c3..5fd62fa43 100644 --- 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/backup_test.exs b/test/pleroma/user/backup_test.exs index 24fe09f7e..f4b92adf8 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -185,13 +185,13 @@ defmodule Pleroma.User.BackupTest do                 %{"@language" => "und"}               ],               "bookmarks" => "bookmarks.json", -             "followers" => "http://cofe.io/users/cofe/followers", -             "following" => "http://cofe.io/users/cofe/following", +             "followers" => "followers.json", +             "following" => "following.json",               "id" => "http://cofe.io/users/cofe",               "inbox" => "http://cofe.io/users/cofe/inbox",               "likes" => "likes.json",               "name" => "Cofe", -             "outbox" => "http://cofe.io/users/cofe/outbox", +             "outbox" => "outbox.json",               "preferredUsername" => "cofe",               "publicKey" => %{                 "id" => "http://cofe.io/users/cofe#main-key", diff --git a/test/pleroma/user/import_test.exs b/test/pleroma/user/import_test.exs index f75305e0e..1d6469a4f 100644 --- 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 index af1a32fed..b627478dc 100644 --- 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) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index b4f6fb68a..72222ae88 100644 --- 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/remote_report_policy_test.exs b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs new file mode 100644 index 000000000..8d2a6b4fa --- /dev/null +++ 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 index 1a51b7d30..f49a7b8ff 100644 --- 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/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index b711d8e91..829598246 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -128,6 +128,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest      %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)    end +  test "a Note with validated likes collection validates" do +    insert(:user, ap_id: "https://pol.social/users/mkljczk") + +    %{"object" => note} = +      "test/fixtures/mastodon-update-with-likes.json" +      |> File.read!() +      |> Jason.decode!() + +    %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) +  end +    test "Fedibird quote post" do      insert(:user, ap_id: "https://fedibird.com/users/noellabo") diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 651e535ac..a32e72829 100644 --- 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 diff --git a/test/pleroma/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs index ed34d6490..9184cf8f1 100644 --- 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 index 7d196b228..662235f31 100644 --- 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 index 1c17d47b4..0a3aaff5c 100644 --- 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/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 4adbaa640..3f696d94d 100644 --- 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 index 7912b1d5f..51af87742 100644 --- 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/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index bea0cae69..97ad2e849 100644 --- 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 index dca64853d..5d24c0e9f 100644 --- 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: ""        }      } @@ -456,6 +460,45 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        test_relationship_rendering(user, other_user, expected)      end +    test "relationship does not indicate following if a FollowingRelationship is missing" do +      user = insert(:user) +      other_user = insert(:user, local: false) + +      # Create a follow relationship with the real Follow Activity and Accept it +      assert {:ok, _, _, _} = CommonAPI.follow(other_user, user) +      assert {:ok, _} = CommonAPI.accept_follow_request(user, other_user) + +      assert %{data: %{"state" => "accept"}} = +               Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, other_user) + +      # Fetch the relationship and forcibly delete it to simulate +      # a Follow Accept that did not complete processing +      %{following_relationships: [relationship]} = +        Pleroma.UserRelationship.view_relationships_option(user, [other_user]) + +      assert {:ok, _} = Pleroma.Repo.delete(relationship) + +      assert %{following_relationships: [], user_relationships: []} == +               Pleroma.UserRelationship.view_relationships_option(user, [other_user]) + +      expected = +        Map.merge( +          @blank_response, +          %{ +            following: false, +            followed_by: false, +            muting: false, +            muting_notifications: false, +            subscribing: false, +            notifying: false, +            showing_reblogs: true, +            id: to_string(other_user.id) +          } +        ) + +      test_relationship_rendering(user, other_user, expected) +    end +      test "represent a relationship for the blocking and blocked user" do        user = insert(:user)        other_user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index 75ab375aa..b1f3523ac 100644 --- 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}), @@ -137,6 +141,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: "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 index 0ff43ada4..e6a164d72 100644 --- 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/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index f0c1dd640..f7e52483c 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -248,8 +248,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        response = get(conn, url) -      assert response.status == 302 -      assert redirected_to(response) == media_proxy_url +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url      end      test "with `static` param and non-GIF image preview requested, " <> @@ -290,8 +290,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        response = get(conn, url) -      assert response.status == 302 -      assert redirected_to(response) == media_proxy_url +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url      end      test "thumbnails PNG images into PNG", %{ @@ -356,5 +356,32 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do        assert response.status == 302        assert redirected_to(response) == media_proxy_url      end + +    test "redirects to media proxy URI with 301 when image is too small for preview", %{ +      conn: conn, +      url: url, +      media_proxy_url: media_proxy_url +    } do +      clear_config([:media_preview_proxy], +        enabled: true, +        min_content_length: 1000, +        image_quality: 85, +        thumbnail_max_width: 100, +        thumbnail_max_height: 100 +      ) + +      Tesla.Mock.mock(fn +        %{method: :head, url: ^media_proxy_url} -> +          %Tesla.Env{ +            status: 200, +            body: "", +            headers: [{"content-type", "image/png"}, {"content-length", "500"}] +          } +      end) + +      response = get(conn, url) +      assert response.status == 301 +      assert redirected_to(response, 301) == media_proxy_url +    end    end  end diff --git a/test/pleroma/web/metadata/providers/activity_pub_test.exs b/test/pleroma/web/metadata/providers/activity_pub_test.exs new file mode 100644 index 000000000..c5cf78a60 --- /dev/null +++ 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 index e593453da..40d9d0909 100644 --- 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/node_info_test.exs b/test/pleroma/web/node_info_test.exs index f474220be..afe4ebb36 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -24,6 +24,19 @@ defmodule Pleroma.Web.NodeInfoTest do        |> get(href)        |> json_response(200)      end) + +    accept_types = [ +      "application/activity+json", +      "application/json", +      "application/jrd+json" +    ] + +    for type <- accept_types do +      conn +      |> put_req_header("accept", type) +      |> get("/.well-known/nodeinfo") +      |> json_response(200) +    end    end    test "nodeinfo shows staff accounts", %{conn: conn} do diff --git a/test/pleroma/web/o_auth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs index 96a67de6b..44219cf90 100644 --- 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 index 07ce2eed8..35b947fd0 100644 --- 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 index 83a08d9fc..260442771 100644 --- 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 index 52a62e416..efdc743e3 100644 --- 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 index b8acd01c5..bdbf3de32 100644 --- 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 index 495d371d2..494be9ec7 100644 --- 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/workers/poll_worker_test.exs b/test/pleroma/workers/poll_worker_test.exs index 749df8aff..a7cbbdb83 100644 --- 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/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 33be91085..4d53c44ed 100644 --- 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/support/factory.ex b/test/support/factory.ex index 8f1c6faf9..91e5805c8 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -241,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(), @@ -251,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" => [          %{ @@ -509,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 | 
