diff --git a/.github/workflows/pl.yaml b/.github/workflows/pl.yaml index e66ccea358..10dc9369fb 100644 --- a/.github/workflows/pl.yaml +++ b/.github/workflows/pl.yaml @@ -1,4 +1,4 @@ -# Adapter from https://fly.io/phoenix-files/github-actions-for-elixir-ci/ +# Adapted from https://fly.io/phoenix-files/github-actions-for-elixir-ci/ name: pl CI @@ -20,7 +20,7 @@ jobs: test: services: db: - image: postgres:12 + image: postgres:16 ports: ['5432:5432'] env: POSTGRES_DB: pleroma_test diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e04dae76f..39947c75e6 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.14.5 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: diff --git a/Dockerfile b/Dockerfile index 72461305ca..fff58154e8 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/README.md b/README.md index b58ff35eba..1074593fce 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,22 @@ Added features: - [Partial implementation of Mastodon admin API](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3671) - [Moderators can assign users to reports](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3670) - [Mastodon-compatible webhooks](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3683) -- UI is restyled to match pl-fe/Soapbox visuals +- UI is restyled to match pl-fe visuals +- Improved compatibility with akkoma-fe (profiles saving is still missing) Features not authored by me: - [Chat deletion](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3029) -- [AntiDuplicationPolicy, AntiMentionSpamPolicy](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/249), [RemoteReportPolicy](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/202) MRFs +- [AntiDuplicationPolicy and AntiMentionSpamPolicy MRFs](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/249) - [Bubble timeline](https://akkoma.dev/AkkomaGang/akkoma/pulls/100) - [Hashtag following](https://akkoma.dev/AkkomaGang/akkoma/pulls/341) - [Ability to auto-approve followbacks](https://akkoma.dev/AkkomaGang/akkoma/pulls/674) There might be more, it's hard to keep track of it. I'm trying to keep the fork as close to upstream and I hope the list will eventually get much shorter. -It should be possible to migrate from Pleroma or Rebased to `pl` without issues. It is recommended to use `pl` with [`pl-fe`](https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-fe) for full feature compatibility, but pleroma-fe and other frontends work fine. +**DISCLAIMER:** +Although `pl` *just works* for me, I cannot guarantee that it'll work well for you. There might be bugs I simply don't care about or I might decide to abandon the project one day. + +It should be possible to migrate from Pleroma or Rebased to `pl` without issues. It is recommended to use `pl` with [`pl-fe`](https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-fe) for full feature compatibility, but pleroma-fe and other frontends work fine too. --- diff --git a/changelog.d/admin-report-notification-type.change b/changelog.d/admin-report-notification-type.change new file mode 100644 index 0000000000..6899a131fd --- /dev/null +++ b/changelog.d/admin-report-notification-type.change @@ -0,0 +1 @@ +Use `admin.report` for report notification type \ No newline at end of file diff --git a/changelog.d/akkoma-migration.add b/changelog.d/akkoma-migration.add new file mode 100644 index 0000000000..c8b457ec23 --- /dev/null +++ b/changelog.d/akkoma-migration.add @@ -0,0 +1 @@ +Add instructions for migrating from Akkoma \ No newline at end of file diff --git a/changelog.d/argon2-passwords.add b/changelog.d/argon2-passwords.add new file mode 100644 index 0000000000..36fd7faf22 --- /dev/null +++ b/changelog.d/argon2-passwords.add @@ -0,0 +1 @@ +Added support for argon2 passwords and their conversion for migration from Akkoma fork to upstream. diff --git a/changelog.d/atom-tag.change b/changelog.d/atom-tag.change new file mode 100644 index 0000000000..1b3590dea8 --- /dev/null +++ b/changelog.d/atom-tag.change @@ -0,0 +1 @@ +Metadata: Do not include .atom feed links for remote accounts diff --git a/changelog.d/fix-test-failures.skip b/changelog.d/elixir-1.14-docker.skip similarity index 100% rename from changelog.d/fix-test-failures.skip rename to changelog.d/elixir-1.14-docker.skip diff --git a/changelog.d/elixir.change b/changelog.d/elixir.change new file mode 100644 index 0000000000..779c01562b --- /dev/null +++ b/changelog.d/elixir.change @@ -0,0 +1 @@ +Elixir 1.14 and Erlang/OTP 23 is now the minimum supported release diff --git a/changelog.d/hashtag-feeds-restricted.add b/changelog.d/hashtag-feeds-restricted.add new file mode 100644 index 0000000000..accac9c9cc --- /dev/null +++ b/changelog.d/hashtag-feeds-restricted.add @@ -0,0 +1 @@ +Repesct :restrict_unauthenticated for hashtag rss/atom feeds \ No newline at end of file diff --git a/changelog.d/incoming-blocks.fix b/changelog.d/incoming-blocks.fix new file mode 100644 index 0000000000..3228d7318c --- /dev/null +++ b/changelog.d/incoming-blocks.fix @@ -0,0 +1 @@ +Fix incoming Block activities being rejected diff --git a/changelog.d/ldap-ca.add b/changelog.d/ldap-ca.add new file mode 100644 index 0000000000..32ecbb5c02 --- /dev/null +++ b/changelog.d/ldap-ca.add @@ -0,0 +1 @@ +LDAP configuration now permits overriding the CA root certificate file for TLS validation. diff --git a/changelog.d/ldap-password-change.add b/changelog.d/ldap-password-change.add new file mode 100644 index 0000000000..7ca555ee47 --- /dev/null +++ b/changelog.d/ldap-password-change.add @@ -0,0 +1 @@ +LDAP now supports users changing their passwords diff --git a/changelog.d/ldap-refactor.change b/changelog.d/ldap-refactor.change new file mode 100644 index 0000000000..1510eea6aa --- /dev/null +++ b/changelog.d/ldap-refactor.change @@ -0,0 +1 @@ +LDAP authentication has been refactored to operate as a GenServer process which will maintain an active connection to the LDAP server. diff --git a/changelog.d/ldap-tls.fix b/changelog.d/ldap-tls.fix new file mode 100644 index 0000000000..b15137d775 --- /dev/null +++ b/changelog.d/ldap-tls.fix @@ -0,0 +1 @@ +STARTTLS certificate and hostname verification for LDAP authentication diff --git a/changelog.d/ldap-warning.skip b/changelog.d/ldap-warning.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/ldaps.fix b/changelog.d/ldaps.fix new file mode 100644 index 0000000000..a1dc901ab0 --- /dev/null +++ b/changelog.d/ldaps.fix @@ -0,0 +1 @@ +LDAPS connections (implicit TLS) are now supported. diff --git a/changelog.d/manifest-icon-size.skip b/changelog.d/manifest-icon-size.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/mrf-id_filter.add b/changelog.d/mrf-id_filter.add new file mode 100644 index 0000000000..f556f9bc43 --- /dev/null +++ b/changelog.d/mrf-id_filter.add @@ -0,0 +1 @@ +Add `id_filter` to MRF to filter URLs and their domain prior to fetching \ No newline at end of file diff --git a/changelog.d/notifications-group-key.add b/changelog.d/notifications-group-key.add new file mode 100644 index 0000000000..386927f4a4 --- /dev/null +++ b/changelog.d/notifications-group-key.add @@ -0,0 +1 @@ +Add `group_key` to notifications \ No newline at end of file diff --git a/changelog.d/oban-update.change b/changelog.d/oban-update.change new file mode 100644 index 0000000000..48a54ed2d2 --- /dev/null +++ b/changelog.d/oban-update.change @@ -0,0 +1 @@ +Oban updated to 2.18.3 diff --git a/changelog.d/poll-refresh.change b/changelog.d/poll-refresh.change new file mode 100644 index 0000000000..b755128a12 --- /dev/null +++ b/changelog.d/poll-refresh.change @@ -0,0 +1 @@ +Poll results refreshing is handled asynchronously and will not attempt to keep fetching updates to a closed poll. diff --git a/changelog.d/profile-image-descriptions.skip b/changelog.d/profile-image-descriptions.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/remote-report-policy.add b/changelog.d/remote-report-policy.add new file mode 100644 index 0000000000..1cf25b1a8f --- /dev/null +++ b/changelog.d/remote-report-policy.add @@ -0,0 +1 @@ +Added RemoteReportPolicy from Rebased for handling bogus federated reports diff --git a/changelog.d/swoosh-mua.add b/changelog.d/swoosh-mua.add new file mode 100644 index 0000000000..d4c4bbd084 --- /dev/null +++ b/changelog.d/swoosh-mua.add @@ -0,0 +1 @@ +Added dependencies for Swoosh's Mua mail adapter 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 508262ed82..0000000000 --- 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 25a1639e89..0000000000 --- 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 similarity index 91% rename from ci/elixir-1.12/Dockerfile rename to ci/elixir-1.14.5-otp-25/Dockerfile index a2b5668730..3a35c84c39 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 similarity index 52% rename from ci/elixir-1.13.4-otp-25/build_and_push.sh rename to ci/elixir-1.14.5-otp-25/build_and_push.sh index b8ca1d24d7..912c47d0c4 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 5d2bf01fa5..776c5eef28 100644 --- a/config/config.exs +++ b/config/config.exs @@ -351,7 +351,7 @@ icons: [ %{ src: "/static/logo.svg", - sizes: "144x144", + sizes: "512x512", purpose: "any", type: "image/svg+xml" } @@ -625,14 +625,17 @@ 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") @@ -830,6 +833,21 @@ "https://lily-is.land/infra/glitch-lily/-/jobs/artifacts/${ref}/download?job=build", "ref" => "servant", "build_dir" => "public" + }, + "akkoma-fe" => %{ + "name" => "akkoma-fe", + "git" => "https://akkoma.dev/AkkomaGang/akkoma-fe", + "build_url" => + "https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/akkoma-fe.zip", + "ref" => "develop", + "build_dir" => "dist" + }, + "akkoma-admin-fe" => %{ + "name" => "akkoma-admin-fe", + "git" => "https://akkoma.dev/AkkomaGang/admin-fe", + "build_url" => + "https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/admin-fe.zip", + "ref" => "stable" } } @@ -963,8 +981,6 @@ config :geospatial, Geospatial.HTTP, user_agent: &Pleroma.Application.user_agent/0 -import_config "soapbox.exs" - config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch config :pleroma, Pleroma.Search.Meilisearch, @@ -994,6 +1010,8 @@ vectors: %{size: 384, distance: "Cosine"} } +import_config "pl-fe.exs" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/description.exs b/config/description.exs index f8dc6ff65e..b843e99597 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2305,14 +2305,8 @@ 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, @@ -2332,14 +2326,8 @@ 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, @@ -2356,11 +2344,25 @@ }, %{ 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"] } ] }, @@ -3583,6 +3585,19 @@ "translateLocally intermediate language (used when direct source->target model is not available)", type: :string, suggestions: ["en"] + }, + %{ + group: {:subgroup, Pleroma.Language.Translation.Mozhi}, + key: :base_url, + label: "Mozhi instance URL", + type: :string + }, + %{ + group: {:subgroup, Pleroma.Language.Translation.Mozhi}, + key: :engine, + label: "Engine used for Mozhi", + type: :string, + suggestions: ["libretranslate"] } ] }, diff --git a/config/soapbox.exs b/config/pl-fe.exs similarity index 86% rename from config/soapbox.exs rename to config/pl-fe.exs index ab90181c9c..584c1c6d52 100644 --- a/config/soapbox.exs +++ b/config/pl-fe.exs @@ -1,11 +1,8 @@ -# Soapbox default config overrides +# pl-fe default config overrides # This file gets loaded after config.exs # and before prod.secret.exs import Config -# Twitter-like block behavior -config :pleroma, :activitypub, blockers_visible: false - # Sane default upload filters config :pleroma, Pleroma.Upload, filters: [ @@ -45,12 +42,7 @@ # Sane default media attachment limit config :pleroma, :instance, max_media_attachments: 20 -# Use Soapbox branding config :pleroma, :instance, - name: "Soapbox", - description: "Social media owned by you", - instance_thumbnail: "/instance/thumbnail.png", - favicon: "/favicon.svg", account_approval_required: true, moderator_privileges: [ :users_read, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 88b5cccb37..6835b74d23 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/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 808e88040e..2ca90a9e20 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -238,21 +238,12 @@ The `type` value is `pleroma:chat_mention` - `account`: The account who sent the message - `chat_message`: The chat message -### Report Notification (not default) - -This notification has to be requested explicitly. - -The `type` value is `pleroma:report` - -- `account`: The account who reported -- `report`: The report - ## GET `/api/v1/notifications` Accepts additional parameters: - `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`. -- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`. +- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `admin.report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`. ## DELETE `/api/v1/notifications/destroy_multiple` diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index 5a0823a634..0817934fff 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/generic_dependencies.include b/docs/installation/generic_dependencies.include index bdb7f94d3a..9f07f62c6c 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/migrating_from_akkoma.md b/docs/installation/migrating_from_akkoma.md new file mode 100644 index 0000000000..563eb9763d --- /dev/null +++ b/docs/installation/migrating_from_akkoma.md @@ -0,0 +1,17 @@ +# Migrating from Akkoma + +## Database migration + +> Note: You will lose data related about Akkoma-specific features, including: MastoFE settings, user frontend profiles, status auto-expiration config and DM restrictions. Consider taking a backup. + +To rollback Akkoma-specific migrations: + +- OTP: `./bin/pleroma_ctl rollback --migrations-path priv/repo/optional_migrations/akkoma_rollbacks` +- From Source: `mix ecto.rollback --migrations-path priv/repo/optional_migrations/akkoma_rollbacks` + +Then, just + +- OTP: `./bin/pleroma_ctl migrate` +- From Source: `mix ecto.migrate` + +to apply Pleroma database migrations. \ No newline at end of file diff --git a/installation/openldap/pw_self_service.ldif b/installation/openldap/pw_self_service.ldif new file mode 100644 index 0000000000..463dabbfb5 --- /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/test_runner.ex b/lib/mix/tasks/pleroma/test_runner.ex new file mode 100644 index 0000000000..69fefb0014 --- /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 d154af6f71..be21532cea 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -97,6 +97,7 @@ def start(_type, _args) 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 222edb32f0..fdb669f146 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -21,7 +21,8 @@ defp reboot_time_keys, {: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 362eaa7768..5894ece22a 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -103,6 +103,7 @@ defmodule Pleroma.Constants do const(activity_types, do: [ + "Block", "Create", "Update", "Delete", @@ -131,6 +132,10 @@ defmodule Pleroma.Constants do ] ) + 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/hashtag.ex b/lib/pleroma/hashtag.ex index 5a8126c1a2..148428d4aa 100644 --- a/lib/pleroma/hashtag.ex +++ b/lib/pleroma/hashtag.ex @@ -10,9 +10,9 @@ defmodule Pleroma.Hashtag do alias Ecto.Multi alias Pleroma.Hashtag - alias Pleroma.User.HashtagFollow alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User.HashtagFollow schema "hashtags" do field(:name, :string) diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index e027035b4d..98b2a7e369 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -30,7 +30,7 @@ def translate(content, source_language, target_language) do text: content, source_lang: source_language |> String.upcase(), target_lang: target_language, - tag_handling: "html" + tag_handling: @name }), "", [ diff --git a/lib/pleroma/language/translation/libretranslate.ex b/lib/pleroma/language/translation/libretranslate.ex index 69ecf23b0b..fd727d1cf2 100644 --- a/lib/pleroma/language/translation/libretranslate.ex +++ b/lib/pleroma/language/translation/libretranslate.ex @@ -46,7 +46,7 @@ def translate(content, source_language, target_language) do %{ content: content, detected_source_language: source_language, - provider: "LibreTranslate" + provider: @name }} _ -> diff --git a/lib/pleroma/language/translation/mozhi.ex b/lib/pleroma/language/translation/mozhi.ex new file mode 100644 index 0000000000..958f2ef57b --- /dev/null +++ b/lib/pleroma/language/translation/mozhi.ex @@ -0,0 +1,109 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Language.Translation.Mozhi do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Language.Translation.Provider + + use Provider + + @behaviour Provider + + @name "Mozhi" + + @impl Provider + def configured?, do: not_empty_string(base_url()) and not_empty_string(engine()) + + @impl Provider + def translate(content, source_language, target_language) do + endpoint = + base_url() + |> URI.merge("/api/translate") + |> URI.to_string() + + case Pleroma.HTTP.get( + endpoint <> + "?" <> + URI.encode_query(%{ + engine: engine(), + text: content, + from: source_language, + to: target_language + }), + [{"Accept", "application/json"}] + ) do + {:ok, %{status: 200} = res} -> + %{ + "translated-text" => content, + "source_language" => source_language + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: source_language, + provider: @name + }} + + _ -> + {:error, :internal_server_error} + end + end + + @impl Provider + def supported_languages(type) when type in [:source, :target] do + path = + case type do + :source -> "/api/source_languages" + :target -> "/api/target_languages" + end + + endpoint = + base_url() + |> URI.merge(path) + |> URI.to_string() + + case Pleroma.HTTP.get( + endpoint <> + "?" <> + URI.encode_query(%{ + engine: engine() + }), + [{"Accept", "application/json"}] + ) do + {:ok, %{status: 200} = res} -> + languages = + Jason.decode!(res.body) + |> Enum.map(fn %{"Id" => language} -> language end) + + {:ok, languages} + + _ -> + {:error, :internal_server_error} + end + end + + @impl Provider + def languages_matrix do + with {:ok, source_languages} <- supported_languages(:source), + {:ok, target_languages} <- supported_languages(:target) do + {:ok, + Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)} + else + {:error, error} -> {:error, error} + end + end + + @impl Provider + def name, do: @name + + defp base_url do + Pleroma.Config.get([__MODULE__, :base_url]) + end + + defp engine do + Pleroma.Config.get([__MODULE__, :engine]) + end +end diff --git a/lib/pleroma/ldap.ex b/lib/pleroma/ldap.ex new file mode 100644 index 0000000000..b591c2918a --- /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/notification.ex b/lib/pleroma/notification.ex index eab82ab10a..1ec8ea320e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -70,7 +70,7 @@ def unread_notifications_count(%User{id: user_id}) do move pleroma:chat_mention pleroma:emoji_reaction - pleroma:report + admin.report reblog poll status @@ -445,7 +445,7 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do "pleroma:emoji_reaction" "Flag" -> - "pleroma:report" + "admin.report" # Compatibility with old reactions "EmojiReaction" -> diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index cf2218bca6..5befa24c84 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -99,27 +99,6 @@ defp hashtags_changed?(_, _), do: false 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 7b87b9fbfa..b1abde39bf 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -146,6 +146,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) 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 @@ -161,6 +162,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {:error, e} -> {:error, e} + {:mrf, false} -> + {:error, {:reject, "Filtered by id"}} + e -> {:error, e} end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index dc75908a66..bb554e4042 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -21,7 +21,6 @@ defmodule Pleroma.User do alias Pleroma.FollowingRelationship alias Pleroma.Formatter alias Pleroma.Hashtag - alias Pleroma.User.HashtagFollow alias Pleroma.HTML alias Pleroma.Keys alias Pleroma.MFA @@ -31,6 +30,7 @@ defmodule Pleroma.User do alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserRelationship + alias Pleroma.User.HashtagFollow alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline @@ -440,6 +440,11 @@ def banner_url(user, options \\ []) 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}" @@ -1167,7 +1172,7 @@ def needs_update?(_), do: true # "Locked" (self-locked) users demand explicit authorization of follow requests @spec can_direct_follow_local(User.t(), User.t()) :: true | false def can_direct_follow_local(%User{} = follower, %User{local: true} = followed) do - !followed.is_locked || (followed.permit_followback and is_friend_of(follower, followed)) + !followed.is_locked || (followed.permit_followback and friend_of?(follower, followed)) end @spec maybe_direct_follow(User.t(), User.t()) :: @@ -1552,7 +1557,7 @@ def get_familiar_followers(%User{} = user, %User{} = current_user, page \\ nil) |> Repo.all() end - def is_friend_of(%User{} = potential_friend, %User{local: true} = user) do + def friend_of?(%User{} = potential_friend, %User{local: true} = user) do user |> get_friends_query() |> where(id: ^potential_friend.id) diff --git a/lib/pleroma/user/hashtag_follow.ex b/lib/pleroma/user/hashtag_follow.ex index dd0254ef4c..3e28b130b4 100644 --- a/lib/pleroma/user/hashtag_follow.ex +++ b/lib/pleroma/user/hashtag_follow.ex @@ -3,9 +3,9 @@ defmodule Pleroma.User.HashtagFollow do import Ecto.Query import Ecto.Changeset - alias Pleroma.User alias Pleroma.Hashtag alias Pleroma.Repo + alias Pleroma.User schema "user_follows_hashtag" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a29018eb5c..1842c24dfd 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1717,16 +1717,23 @@ defp get_actor_url(url) when is_list(url) 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/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index d36996e012..5591c9d8f1 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -109,6 +109,14 @@ def filter(policies, %{} = message) 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 e4fcc9935b..cf07db7f30 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -13,6 +13,12 @@ def filter(activity) do {:reject, activity} 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 54ca4b7357..08bcac08a6 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 index 0bd83d8f00..fa0610bf10 100644 --- a/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex @@ -9,6 +9,7 @@ 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 @@ -37,6 +38,22 @@ defp maybe_reject_anonymous(%{"actor" => actor} = object) do 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} @@ -83,6 +100,12 @@ def config_description do 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, diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index f2c4bfc09c..ebe4e4e6f2 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -191,6 +191,18 @@ defp instance_list(config_key) do |> MRF.instance_list_from_tuples() 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 49615fa8cb..7f7a62539c 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 @@ -42,6 +44,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 diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a66167fa17..c5fe2fa56a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1021,6 +1021,7 @@ defp replace_instance_host(value, nil), do: value defp replace_instance_host(content, host) when is_binary(content) do content |> String.replace("$INSTANCE$host$", host) + |> String.replace("$INSTANCE$tld$", get_tld(host, "$INSTANCE$tld$")) end defp replace_instance_host(object, host) when is_map(object) do @@ -1032,6 +1033,14 @@ defp replace_instance_host(object, host) when is_map(object) do defp replace_instance_host(value, _), do: value + defp get_tld(host, default) do + with [domain | _] <- String.split(host, ".") |> Enum.reverse() do + domain + else + _ -> default + end + end + defp patch_content_map(%{"contentMap" => %{} = content_map}, host) do content_map |> Enum.map(fn {key, value} -> {key, replace_instance_host(value, host)} end) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index cd041d43a9..4f81813c4f 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -132,8 +132,22 @@ def render("user.json", %{user: user}) do "webfinger" => "acct:#{User.full_nickname(user)}", "vcard:Address" => user.location } - |> 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 @@ -314,16 +328,24 @@ def collection(collection, iri, page, show_items \\ true, total \\ nil) 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/akkoma_compat_controller.ex b/lib/pleroma/web/akkoma_compat_controller.ex new file mode 100644 index 0000000000..57abc29d05 --- /dev/null +++ b/lib/pleroma/web/akkoma_compat_controller.ex @@ -0,0 +1,100 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AkkomaCompatController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Language.Translation + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(:skip_auth when action == :translation_languages) + + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]} when action == :translate + ) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AkkomaCompatOperation + + @doc "GET /api/v1/akkoma/translation/languages" + def translation_languages(conn, _params) do + with {:enabled, true} <- {:enabled, Translation.configured?()}, + {:ok, source_languages} <- Translation.supported_languages(:source), + {:ok, target_languages} <- Translation.supported_languages(:target) do + source_languages = + source_languages + |> Enum.map(fn lang -> %{code: lang, name: lang} end) + + target_languages = + target_languages + |> Enum.map(fn lang -> %{code: lang, name: lang} end) + + conn + |> json(%{source: source_languages, target: target_languages}) + else + {:enabled, false} -> + json(conn, %{}) + + e -> + {:error, e} + end + end + + @doc "GET /api/v1/statuses/:id/translations/:language" + def translate( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{params: %{id: status_id} = params}} + } = conn, + _ + ) do + with {:authentication, true} <- + {:authentication, + !is_nil(user) || + Pleroma.Config.get([Translation, :allow_unauthenticated])}, + %Activity{object: object} <- Activity.get_by_id_with_object(status_id), + {:visibility, visibility} when visibility in ["public", "unlisted"] <- + {:visibility, Visibility.get_visibility(object)}, + {:allow_remote, true} <- + {:allow_remote, + Object.local?(object) || + Pleroma.Config.get([Translation, :allow_remote])}, + {:language, language} when is_binary(language) <- + {:language, Map.get(params, :language) || user.language}, + {:ok, result} <- + Translation.translate( + object.data["content"], + object.data["language"], + language + ) do + json(conn, %{detected_language: result.detected_source_language, text: result.content}) + else + {:authentication, false} -> + render_error(conn, :unauthorized, "Authorization is required to translate statuses") + + {:allow_remote, false} -> + render_error(conn, :bad_request, "You can't translate remote posts") + + {:language, nil} -> + render_error(conn, :bad_request, "Language not specified") + + {:visibility, _} -> + render_error(conn, :not_found, "Record not found") + + {:error, :not_found} -> + render_error(conn, :not_found, "Translation service not configured") + + {:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] -> + render_error(conn, :service_unavailable, "Translation service not available") + + nil -> + render_error(conn, :not_found, "Record not found") + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/akkoma_compat_operation.ex b/lib/pleroma/web/api_spec/operations/akkoma_compat_operation.ex new file mode 100644 index 0000000000..3f38e54e64 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/akkoma_compat_operation.ex @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.AkkomaCompatOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + # Adapted from https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/api_spec/operations/translate_operation.ex + def translation_languages_operation do + %Operation{ + tags: ["Akkoma compatibility routes"], + summary: "Get translation languages", + description: "Retreieve a list of supported source and target language", + operationId: "AkkomaCompatController.translation_languages", + responses: %{ + 200 => + Operation.response( + "Translation languages", + "application/json", + source_dest_languages_schema() + ) + } + } + end + + defp source_dest_languages_schema do + %Schema{ + type: :object, + required: [:source, :target], + properties: %{ + source: languages_schema(), + target: languages_schema() + } + } + end + + defp languages_schema do + %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + code: %Schema{type: :string}, + name: %Schema{type: :string} + } + } + } + end + + def translate_operation do + %Operation{ + tags: ["Akkoma compatibility routes"], + summary: "Translate status", + description: "Translate status with an external API", + operationId: "AkkomaCompatController.translate", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [ + Operation.parameter(:id, :path, FlakeID.schema(), "Status ID", + example: "9umDrYheeY451cQnEe", + required: true + ), + Operation.parameter(:language, :path, :string, "Target language code", example: "en"), + Operation.parameter(:from, :query, :string, "Source language code (unused)", + example: "en" + ) + ], + responses: %{ + 200 => + Operation.response( + "Translated status", + "application/json", + %Schema{ + type: :object, + required: [:detected_language, :text], + properties: %{ + detected_language: %Schema{type: :string}, + text: %Schema{type: :string} + } + } + ) + } + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index 51e41beed6..8551f530ce 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -157,6 +157,10 @@ def notification 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{ @@ -184,6 +188,7 @@ def notification do }, example: %{ "id" => "34975861", + "group-key" => "ungrouped-34975861", "type" => "mention", "created_at" => "2019-11-23T07:49:02.064Z", "account" => Account.schema().example, @@ -203,7 +208,6 @@ defp notification_type do "mention", "pleroma:emoji_reaction", "pleroma:chat_mention", - "pleroma:report", "move", "follow_request", "poll", @@ -229,7 +233,6 @@ defp notification_type do - `move` - Someone moved their account - `pleroma:emoji_reaction` - Someone reacted with emoji to your status - `pleroma:chat_mention` - Someone mentioned you in a chat message - - `pleroma:report` - Someone was reported - `status` - Someone you are subscribed to created a status - `update` - A status you boosted has been edited - `pleroma:event_reminder` – An event you are participating in or created is taking place soon diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index 01bf1575c4..95be892cd6 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 ea5620cf60..ec6601fb90 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 @@ def get_user(%Plug.Conn{} = conn) 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}} + 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 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}} - 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 09a58eb66b..0da3f19fce 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 @@ def handle_error(%Plug.Conn{} = _conn, error) 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 a077cfa416..97b901036c 100644 --- a/lib/pleroma/web/auth/wrapper_authenticator.ex +++ b/lib/pleroma/web/auth/wrapper_authenticator.ex @@ -39,4 +39,8 @@ def oauth_consumer_template 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/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index e60767327c..02d6392960 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 @@ def feed(conn, params) 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/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 46f07f3bf1..a15029d92d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -8,10 +8,10 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + alias Pleroma.Pagination alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Pagination plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(:assign_follower when action != :index) diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index a2af8148ca..6526457df3 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 @@ defp get_cached_vote_or_vote(object, user, choices) do end end) end + + defp maybe_refresh_poll(%Activity{object: %Object{} = object} = activity) do + with false <- activity.local, + {:ok, end_time} <- NaiveDateTime.from_iso8601(object.data["closed"]), + {_, :lt} <- {:closed_compare, NaiveDateTime.compare(object.updated_at, end_time)} do + PollWorker.new(%{"op" => "refresh", "activity_id" => activity.id}) + |> Oban.insert(unique: [period: @poll_refresh_interval]) + end + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/tag_controller.ex b/lib/pleroma/web/mastodon_api/controllers/tag_controller.ex index ca5ee48ac1..21c21e984d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/tag_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/tag_controller.ex @@ -2,9 +2,9 @@ defmodule Pleroma.Web.MastodonAPI.TagController do @moduledoc "Hashtag routes for mastodon API" use Pleroma.Web, :controller - alias Pleroma.User alias Pleroma.Hashtag alias Pleroma.Pagination + alias Pleroma.User import Pleroma.Web.ControllerHelper, only: [ diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index c9e045d238..9e049fcdcd 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -65,14 +65,14 @@ def get_notifications(user, params \\ %{}) do cast_params(params) |> Map.update(:include_types, [], fn include_types -> include_types end) options = - if ("pleroma:report" not in options.include_types and + if ("admin.report" not in options.include_types and User.privileged?(user, :reports_manage_reports)) or User.privileged?(user, :reports_manage_reports) do options else options - |> Map.update(:exclude_types, ["pleroma:report"], fn current_exclude_types -> - current_exclude_types ++ ["pleroma:report"] + |> Map.update(:exclude_types, ["admin.report"], fn current_exclude_types -> + current_exclude_types ++ ["admin.report"] 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 9ae6058137..0e2c69abe4 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -219,10 +219,10 @@ defp do_render("show.json", %{user: user} = opts) do avatar = User.avatar_url(user) |> MediaProxy.url() avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true) - avatar_description = image_description(user.avatar) + 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 = image_description(user.banner) + header_description = User.image_description(user.banner) following_count = if !user.hide_follows_count or !user.hide_follows or self, @@ -352,10 +352,6 @@ defp username_from_nickname(string) when is_binary(string) do defp username_from_nickname(_), do: nil - defp image_description(%{"name" => name}), do: name - - defp image_description(_), do: "" - defp maybe_put_follow_requests_count( data, %User{id: user_id} = user, diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 39f1d7947b..3d8923f8b5 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -78,6 +78,11 @@ def render("show2.json", _) do email: Keyword.get(instance, :email), account: contact_account(Keyword.get(instance, :contact_username)) }, + api_versions: %{ + "mastodon" => 2137, + "social.pleroma" => 420, + "pl.mkljczk.pl" => 69 + }, # Extra (not present in Mastodon): pleroma: pleroma_configuration2(instance) # soapbox: %{ @@ -186,6 +191,13 @@ def features do "events", "multitenancy", "pleroma:bites", + if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do + "bubble_timeline" + end, + # Akkoma compatibility + if Pleroma.Language.Translation.configured?() do + "akkoma:machine_translation" + end, "pleroma:multi_language", "bigbuffet" ] diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ff84738505..ce36ad2b69 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 @@ def render( 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, @@ -131,7 +132,7 @@ def render( "pleroma:chat_mention" -> put_chat_message(response, activity, reading_user, status_render_opts) - "pleroma:report" -> + "admin.report" -> put_report(response, activity) "pleroma:participation_accepted" -> diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex index e97d6a54f0..3811f96f63 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,6 @@ def build_tags(%{user: user}) do ], []} ] end + + def build_tags(_), do: [] end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 612bfd3bcc..c86d8823c5 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -75,6 +75,7 @@ def get_nodeinfo("2.0") do restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true), + localBubbleInstances: Config.get([:instance, :local_bubble], []), publicTimelineVisibility: %{ federated: !Config.restrict_unauthenticated_access?(:timelines, :federated) && diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index f912a1542f..af7d7f45a8 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -47,6 +47,11 @@ def checkpw(password, "$pbkdf2" <> _ = password_hash) 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 @@ def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) 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/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index 58f38b4b1f..c43f4ecf68 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -92,7 +92,7 @@ defp csp_string do static_url = Pleroma.Web.Endpoint.static_url() websocket_url = Pleroma.Web.Endpoint.websocket_url() report_uri = @config_impl.get([:http_security, :report_uri]) - sentry_dsn = @config_impl.get([:frontend_configurations, :soapbox_fe, "sentryDsn"]) + sentry_dsn = @config_impl.get([:frontend_configurations, :pl_fe, "sentryDsn"]) img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" @@ -200,7 +200,7 @@ defp build_csp_multimedia_source_list do defp map_tile_server do with tile_server when is_binary(tile_server) <- - @config_impl.get([:frontend_configurations, :soapbox_fe, "tileServer"]), + @config_impl.get([:frontend_configurations, :pl_fe, "tileServer"]), %{host: host} <- URI.parse(tile_server) do ["*.#{host}"] else diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 479da948e0..876b96e2ac 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -731,6 +731,13 @@ defmodule Pleroma.Web.Router do end end + scope "/", Pleroma.Web do + pipe_through(:api) + + get("/api/v1/akkoma/translation/languages", AkkomaCompatController, :translation_languages) + get("/api/v1/statuses/:id/translations/:language", AkkomaCompatController, :translate) + end + scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:authenticated_api) diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index ac5d9800c1..0e78567e93 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -9,13 +9,13 @@ :root { <%= Pleroma.Web.Utils.Colors.shades_to_css( "primary", - Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "brandColor"], "#0482d8"), - Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "colors", "primary"], %{}) + Pleroma.Config.get([:frontend_configurations, :pl_fe, "brandColor"], "#d80482"), + Pleroma.Config.get([:frontend_configurations, :pl_fe, "colors", "primary"], %{}) ) %> <%= Pleroma.Web.Utils.Colors.shades_to_css( "accent", - Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "accentColor"], "#2bd110"), - Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "colors", "accent"], %{}) + Pleroma.Config.get([:frontend_configurations, :pl_fe, "accentColor"], "#d110b4"), + Pleroma.Config.get([:frontend_configurations, :pl_fe, "colors", "accent"], %{}) ) %> } @@ -23,7 +23,7 @@ diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index e71b926a06..a41eb5a317 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 @@ def change_password( %{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/utils/colors.ex b/lib/pleroma/web/utils/colors.ex index 449393f2b4..c00a938109 100644 --- a/lib/pleroma/web/utils/colors.ex +++ b/lib/pleroma/web/utils/colors.ex @@ -51,7 +51,7 @@ def get_shades("#" <> base_color, overrides) do shades end - def get_shades(_, overrides), do: get_shades("#0482d8", overrides) + def get_shades(_, overrides), do: get_shades("#d80482", overrides) defp get_override(level, overrides) do if Map.has_key?(overrides, "#{level}") do diff --git a/lib/pleroma/web/views/manifest_view.ex b/lib/pleroma/web/views/manifest_view.ex index f8adf0f58f..14f6ba5455 100644 --- a/lib/pleroma/web/views/manifest_view.ex +++ b/lib/pleroma/web/views/manifest_view.ex @@ -12,9 +12,14 @@ def render("manifest.json", _params) do name: Config.get([:instance, :name]), description: Config.get([:instance, :description]), icons: Config.get([:manifest, :icons]), - theme_color: Config.get([:manifest, :theme_color]), + theme_color: + Config.get( + [:frontend_configurations, :pl_fe, "brandColor"], + Config.get([:manifest, :theme_color]) + ), background_color: Config.get([:manifest, :background_color]), display: "standalone", + display_override: ["window-controls-overlay"], scope: Endpoint.url(), start_url: "/", categories: [ diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex index d263aa1b9e..a9afe9d63a 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 + 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) + + :ok + else + {:activity, nil} -> {:cancel, :poll_activity_not_found} + {:refetch, _} = e -> {:cancel, e} + end + end + @impl true def timeout(_job), do: :timer.seconds(5) - defp find_poll_activity(activity_id) do - with nil <- Activity.get_by_id(activity_id) do - {:error, :poll_activity_not_found} - end - end - 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 @@ def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = 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/mix.exs b/mix.exs index da68d9807e..dd1076cf27 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,7 @@ def project do name: "pl", compat_name: "Pleroma", version: version("2.7.0"), - elixir: "~> 1.13", + elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors(), prune_code_paths: false], @@ -156,7 +156,7 @@ defp deps do {:cachex, "~> 3.2"}, {:csv, "~> 2.4"}, {: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"}, @@ -172,6 +172,8 @@ defp deps 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"}, @@ -208,6 +210,7 @@ defp deps do {:multipart, "~> 0.4.0", optional: true}, {:icalendar, "~> 1.1"}, {:geospatial, "~> 0.3.1"}, + {:argon2_elixir, "~> 4.0"}, ## dev & test {:phoenix_live_reload, "~> 1.3.3", only: :dev}, diff --git a/mix.lock b/mix.lock index 37d17fdc23..c9c89d4c29 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, + "argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"}, "bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, @@ -10,7 +11,7 @@ "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]}, - "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, + "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -79,6 +80,7 @@ "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "linkify": {:git, "https://gitlab.com/mkljczk/linkify", "b854b2f1701a5e13dfea50add729a8525a549f64", [branch: "master"]}, "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"}, @@ -92,6 +94,7 @@ "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"}, @@ -99,7 +102,7 @@ "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oauth2": {:hex, :oauth2, "0.9.4", "632e8e8826a45e33ac2ea5ac66dcc019ba6bb5a0d2ba77e342d33e3b7b252c6e", [:mix], [{:hackney, "~> 1.7", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "407c6b9f60aa0d01b915e2347dc6be78adca706a37f0c530808942da3b62e7af"}, "oauther": {:hex, :oauther, "1.3.0", "82b399607f0ca9d01c640438b34d74ebd9e4acd716508f868e864537ecdb1f76", [:mix], [], "hexpm", "78eb888ea875c72ca27b0864a6f550bc6ee84f2eeca37b093d3d833fbcaec04e"}, - "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"}, diff --git a/priv/repo/migrations/20230306112859_instances_add_metadata.exs b/priv/repo/migrations/20230306112859_instances_add_metadata.exs index 898f5220e0..6af9fd3300 100644 --- a/priv/repo/migrations/20230306112859_instances_add_metadata.exs +++ b/priv/repo/migrations/20230306112859_instances_add_metadata.exs @@ -7,8 +7,8 @@ defmodule Pleroma.Repo.Migrations.InstancesAddMetadata do def change do alter table(:instances) do - add(:metadata, :map) - add(:metadata_updated_at, :utc_datetime) + add_if_not_exists(:metadata, :map) + add_if_not_exists(:metadata_updated_at, :utc_datetime) end end end diff --git a/priv/repo/migrations/20240912000000_rename_pleroma_report_notification_type_to_admin_report.exs b/priv/repo/migrations/20240912000000_rename_pleroma_report_notification_type_to_admin_report.exs new file mode 100644 index 0000000000..52cf3fac43 --- /dev/null +++ b/priv/repo/migrations/20240912000000_rename_pleroma_report_notification_type_to_admin_report.exs @@ -0,0 +1,101 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.RenamePleromaReportNotificationTypeToAdminReport do + use Ecto.Migration + + def up do + alter table(:notifications) do + modify(:type, :string) + end + + """ + update notifications + set type = 'admin.report' + where type = 'pleroma:report' + """ + |> execute() + + """ + drop type if exists notification_type + """ + |> execute() + + """ + create type notification_type as enum ( + 'follow', + 'follow_request', + 'mention', + 'move', + 'pleroma:emoji_reaction', + 'pleroma:chat_mention', + 'reblog', + 'favourite', + 'admin.report', + 'poll', + 'status', + 'update', + 'pleroma:participation_accepted', + 'pleroma:participation_request', + 'pleroma:event_reminder', + 'pleroma:event_update', + 'bite' + ) + """ + |> execute() + + """ + alter table notifications + alter column type type notification_type using (type::notification_type) + """ + |> execute() + end + + def down do + alter table(:notifications) do + modify(:type, :string) + end + + """ + update notifications + set type = 'pleroma:report' + where type = 'admin.report' + """ + |> execute() + + """ + drop type if exists notification_type + """ + |> execute() + + """ + create type notification_type as enum ( + 'follow', + 'follow_request', + 'mention', + 'move', + 'pleroma:emoji_reaction', + 'pleroma:chat_mention', + 'reblog', + 'favourite', + 'pleroma:report', + 'poll', + 'status', + 'update', + 'pleroma:participation_accepted', + 'pleroma:participation_request', + 'pleroma:event_reminder', + 'pleroma:event_update', + 'bite' + ) + """ + |> execute() + + """ + alter table notifications + alter column type type notification_type using (type::notification_type) + """ + |> execute() + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220108213213_add_mastofe_settings.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220108213213_add_mastofe_settings.exs new file mode 100644 index 0000000000..ac22ae6594 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220108213213_add_mastofe_settings.exs @@ -0,0 +1,24 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220108213213_add_mastofe_settings.exs + +defmodule Pleroma.Repo.Migrations.AddMastofeSettings do + use Ecto.Migration + + def up, do: :ok + + def down do + """ + UPDATE users SET pleroma_settings_store = jsonb_set( + pleroma_settings_store, + '{glitch-lily}', + mastofe_settings + ) + WHERE mastofe_settings IS NOT NULL; + """ + |> execute() + + alter table(:users) do + remove_if_exists(:mastofe_settings, :map) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220718102634_upgrade_oban_to_v11.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220718102634_upgrade_oban_to_v11.exs new file mode 100644 index 0000000000..eb85978afc --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220718102634_upgrade_oban_to_v11.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.UpgradeObanToV11 do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220805123645_remove_remote_cancelled_follow_requests.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220805123645_remove_remote_cancelled_follow_requests.exs new file mode 100644 index 0000000000..b94a5ab2ed --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220805123645_remove_remote_cancelled_follow_requests.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.RemoveRemoteCancelledFollowRequests do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220831170605_remove_local_cancelled_follows.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220831170605_remove_local_cancelled_follows.exs new file mode 100644 index 0000000000..0289c2a063 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220831170605_remove_local_cancelled_follows.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.RemoveLocalCancelledFollows do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220911195347_add_user_frontend_profiles.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220911195347_add_user_frontend_profiles.exs new file mode 100644 index 0000000000..11c50d1a3c --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220911195347_add_user_frontend_profiles.exs @@ -0,0 +1,17 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs + +defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do + use Ecto.Migration + + def up, do: :ok + + def down do + drop_if_exists(table("user_frontend_setting_profiles")) + drop_if_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + drop_if_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20220916115149_ensure_mastofe_settings.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20220916115149_ensure_mastofe_settings.exs new file mode 100644 index 0000000000..3074051cc8 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20220916115149_ensure_mastofe_settings.exs @@ -0,0 +1,14 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs + +defmodule Pleroma.Repo.Migrations.EnsureMastofeSettings do + use Ecto.Migration + + def up, do: :ok + + def down do + alter table(:users) do + remove_if_exists(:mastofe_settings, :map) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221020135943_add_nodeinfo.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221020135943_add_nodeinfo.exs new file mode 100644 index 0000000000..8afdee76ed --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221020135943_add_nodeinfo.exs @@ -0,0 +1,15 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221020135943_add_nodeinfo.exs + +defmodule Pleroma.Repo.Migrations.AddNodeinfo do + use Ecto.Migration + + def up, do: :ok + + def down do + alter table(:instances) do + remove_if_exists(:nodeinfo, :map) + remove_if_exists(:metadata_updated_at, :naive_datetime) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221123221956_add_has_request_signatures.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221123221956_add_has_request_signatures.exs new file mode 100644 index 0000000000..ce51a6bba2 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221123221956_add_has_request_signatures.exs @@ -0,0 +1,12 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221123221956_add_has_request_signatures.exs + +defmodule Pleroma.Repo.Migrations.AddHasRequestSignatures do + use Ecto.Migration + + def change do + alter table(:instances) do + add(:has_request_signatures, :boolean, default: false, null: false) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221128103145_add_per_user_post_expiry.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221128103145_add_per_user_post_expiry.exs new file mode 100644 index 0000000000..f7a5f28358 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221128103145_add_per_user_post_expiry.exs @@ -0,0 +1,12 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs + +defmodule Pleroma.Repo.Migrations.AddPerUserPostExpiry do + use Ecto.Migration + + def change do + alter table(:users) do + add(:status_ttl_days, :integer, null: true) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221129105331_add_notification_activity_id_index.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221129105331_add_notification_activity_id_index.exs new file mode 100644 index 0000000000..c23cef5f63 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221129105331_add_notification_activity_id_index.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.AddNotificationActivityIdIndex do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221129110627_add_bookmarks_activity_id_index.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221129110627_add_bookmarks_activity_id_index.exs new file mode 100644 index 0000000000..3a5a1277ec --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221129110627_add_bookmarks_activity_id_index.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.AddBookmarksActivityIdIndex do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221129110727_add_report_notes_activity_id_index.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221129110727_add_report_notes_activity_id_index.exs new file mode 100644 index 0000000000..92e0df7e0a --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221129110727_add_report_notes_activity_id_index.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.AddReportNotesActivityIdIndex do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221129112022_add_cascade_to_report_notes_on_activity_delete.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221129112022_add_cascade_to_report_notes_on_activity_delete.exs new file mode 100644 index 0000000000..41d871bbaa --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221129112022_add_cascade_to_report_notes_on_activity_delete.exs @@ -0,0 +1,16 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221129112022_add_cascade_to_report_notes_on_activity_delete.exs + +defmodule Pleroma.Repo.Migrations.AddCascadeToReportNotesOnActivityDelete do + use Ecto.Migration + + def up, do: :ok + + def down do + drop(constraint(:report_notes, "report_notes_activity_id_fkey")) + + alter table(:report_notes) do + modify(:activity_id, references(:activities, type: :uuid)) + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20221211234352_remove_unused_indices.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20221211234352_remove_unused_indices.exs new file mode 100644 index 0000000000..77797aaab1 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20221211234352_remove_unused_indices.exs @@ -0,0 +1,5 @@ +defmodule Pleroma.Repo.Migrations.RemoveUnusedIndices do + use Ecto.Migration + + def change, do: :ok +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20230127143303_rename_index_users_ap_id_coalesce_follower_address_index.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20230127143303_rename_index_users_ap_id_coalesce_follower_address_index.exs new file mode 100644 index 0000000000..54c389e274 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20230127143303_rename_index_users_ap_id_coalesce_follower_address_index.exs @@ -0,0 +1,15 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20230127143303_rename_index_users_ap_id_coalesce_follower_address_index.exs + +defmodule Pleroma.Repo.Migrations.RenameIndexUsersApId_COALESCEFollowerAddressIndex do + alias Pleroma.Repo + + use Ecto.Migration + + def up, do: :ok + + def down do + Repo.query!("ALTER INDEX public.\"aa_users_ap_id_COALESCE_follower_address_index\" + RENAME TO \"users_ap_id_COALESCE_follower_address_index\";") + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20230522213837_add_unfollowed_dm_restrictions.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20230522213837_add_unfollowed_dm_restrictions.exs new file mode 100644 index 0000000000..39d260dd2a --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20230522213837_add_unfollowed_dm_restrictions.exs @@ -0,0 +1,12 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20230522213837_add_unfollowed_dm_restrictions.exs + +defmodule Pleroma.Repo.Migrations.AddUnfollowedDmRestrictions do + use Ecto.Migration + + def change do + alter table(:users) do + add(:accepts_direct_messages_from, :string, default: "everybody") + end + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20240210000000_drop_chat_tables.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20240210000000_drop_chat_tables.exs new file mode 100644 index 0000000000..8d1d0aa2f5 --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20240210000000_drop_chat_tables.exs @@ -0,0 +1,49 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20240210000000_drop_chat_tables.exs + +defmodule Pleroma.Repo.Migrations.DropChatTables do + use Ecto.Migration + + def up, do: :ok + + def down do + # Ecto's default primary key is bigserial, thus configure manually + create table(:chats, primary_key: false) do + add(:id, :uuid, primary_key: true, autogenerated: true) + + add( + :user_id, + references(:users, type: :uuid, on_delete: :delete_all) + # yes, this was nullable + ) + + add( + :recipient, + references(:users, column: :ap_id, type: :string, on_delete: :delete_all) + # yes, this was nullable + ) + + timestamps() + end + + create(index(:chats, [:user_id, :recipient], unique: true)) + + create table(:chat_message_references, primary_key: false) do + add(:id, :uuid, primary_key: true, autogenerated: true) + add(:chat_id, references(:chats, type: :uuid, on_delete: :delete_all), null: false) + add(:object_id, references(:objects, on_delete: :delete_all), null: false) + add(:unread, :boolean, default: true, null: false) + timestamps() + end + + create(index(:chat_message_references, [:chat_id, "id desc"])) + create(unique_index(:chat_message_references, [:object_id, :chat_id])) + + create( + index(:chat_message_references, [:chat_id], + where: "unread = true", + name: "unread_messages_count_index" + ) + ) + end +end diff --git a/priv/repo/optional_migrations/akkoma_rollbacks/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs b/priv/repo/optional_migrations/akkoma_rollbacks/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs new file mode 100644 index 0000000000..48c675476d --- /dev/null +++ b/priv/repo/optional_migrations/akkoma_rollbacks/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs @@ -0,0 +1,34 @@ +# Adapted from Akkoma +# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20240425120000_upload_filter_exiftool_to_exiftool_strip_location_real.exs + +defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExiftoolStripMetadataReal do + use Ecto.Migration + + alias Pleroma.ConfigDB + + def up, do: :ok + + def down, + do: + ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload}) + |> update_filtername( + Pleroma.Upload.Filter.Exiftool.StripMetadata, + Pleroma.Upload.Filter.Exiftool + ) + + defp update_filtername(%{value: value}, from_filtername, to_filtername) do + new_value = + value + |> Keyword.update(:filters, [], fn filters -> + filters + |> Enum.map(fn + ^from_filtername -> to_filtername + filter -> filter + end) + end) + + ConfigDB.update_or_create(%{group: :pleroma, key: Pleroma.Upload, value: new_value}) + end + + defp update_filtername(_, _, _), do: nil +end diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css index a162e0f2ad..183f9156dd 100644 Binary files a/priv/static/instance/static.css and b/priv/static/instance/static.css differ diff --git a/priv/static/static/logo.svg b/priv/static/static/logo.svg new file mode 100644 index 0000000000..68e647e6ca --- /dev/null +++ b/priv/static/static/logo.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 5409bd48dc..691bf5ec22 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -49,7 +49,7 @@ test "creates a report notification only for privileged users" do {:ok, [notification]} = Notification.create_notifications(activity2) assert notification.user_id == moderator_user.id - assert notification.type == "pleroma:report" + assert notification.type == "admin.report" end test "suppresses notifications for own reports" do @@ -65,7 +65,7 @@ test "suppresses notifications for own reports" do refute notification.user_id == reporting_admin.id assert notification.user_id == other_admin.id - assert notification.type == "pleroma:report" + assert notification.type == "admin.report" end test "creates a notification for an emoji reaction" do diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs index 48d4d86ebd..b3c528e32f 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 @@ -282,148 +280,6 @@ test "does not fetch unknown objects when fetch is false" 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/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 6464cbc387..96d5fa06bc 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1338,6 +1338,27 @@ test "forwarded report from mastodon", %{conn: conn} 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 diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 1606ad351b..fdb666317c 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -234,12 +234,14 @@ test "works for bridgy actors" 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 @@ -454,6 +456,35 @@ test "fetches user location information from misskey" do assert user.location == "Poland" 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 index 43258a7f60..8d2a6b4fa9 100644 --- a/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/remote_report_policy_test.exs @@ -13,7 +13,8 @@ test "doesn't impact local report" do activity = %{ "type" => "Flag", - "actor" => "http://localhost:4001/actor" + "actor" => "http://localhost:4001/actor", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:ok, _} = RemoteReportPolicy.filter(activity) @@ -25,7 +26,8 @@ test "rejects anonymous report if `reject_anonymous: true`" do activity = %{ "type" => "Flag", - "actor" => "https://mastodon.social/actor" + "actor" => "https://mastodon.social/actor", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:reject, _} = RemoteReportPolicy.filter(activity) @@ -37,7 +39,47 @@ test "preserves anonymous report if `reject_anonymous: false`" do activity = %{ "type" => "Flag", - "actor" => "https://mastodon.social/actor" + "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) @@ -49,7 +91,8 @@ test "rejects empty message report if `reject_empty_message: true`" do activity = %{ "type" => "Flag", - "actor" => "https://mastodon.social/users/Gargron" + "actor" => "https://mastodon.social/users/Gargron", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:reject, _} = RemoteReportPolicy.filter(activity) @@ -62,6 +105,7 @@ test "rejects empty message report (\"\") if `reject_empty_message: true`" do activity = %{ "type" => "Flag", "actor" => "https://mastodon.social/users/Gargron", + "object" => ["https://mastodon.online/users/Gargron"], "content" => "" } @@ -74,7 +118,8 @@ test "preserves empty message report if `reject_empty_message: false`" do activity = %{ "type" => "Flag", - "actor" => "https://mastodon.social/users/Gargron" + "actor" => "https://mastodon.social/users/Gargron", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:ok, _} = RemoteReportPolicy.filter(activity) @@ -86,7 +131,8 @@ test "preserves anonymous, empty message report with all settings disabled" do activity = %{ "type" => "Flag", - "actor" => "https://mastodon.social/actor" + "actor" => "https://mastodon.social/actor", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:ok, _} = RemoteReportPolicy.filter(activity) @@ -100,7 +146,8 @@ test "reject remote report if `reject_all: true`" do activity = %{ "type" => "Flag", "actor" => "https://mastodon.social/users/Gargron", - "content" => "Transphobia" + "content" => "Transphobia", + "object" => ["https://mastodon.online/users/Gargron"] } assert {:reject, _} = RemoteReportPolicy.filter(activity) 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 1a51b7d301..f49a7b8ff8 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 @@ test "is empty" 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 @@ test "activity has a matching host" 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 @@ test "activity matches with wildcard domain" 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 @@ test "actor has a matching host" 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 @@ test "reject Announce when object would be rejected" do } assert {:reject, _} = SimplePolicy.filter(announce) + # Note: Non-Applicable for id_filter/1 end test "reject by URI object" do @@ -300,6 +305,7 @@ test "reject by URI object" do } assert {:reject, _} = SimplePolicy.filter(announce) + # Note: Non-Applicable for id_filter/1 end end @@ -370,6 +376,8 @@ test "is empty" 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 @@ test "is not empty but activity doesn't have a matching host" 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 @@ test "activity has a matching host" 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 @@ test "activity matches with wildcard domain" 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 @@ test "actor has a matching host" 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/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 651e535ac4..a32e728296 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 @@ test "Does not add an avatar image if the user hasn't set one" 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/akkoma_compat_controller_test.exs b/test/pleroma/web/akkoma_compat_controller_test.exs new file mode 100644 index 0000000000..02b691ca0f --- /dev/null +++ b/test/pleroma/web/akkoma_compat_controller_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AkkomaCompatControllerTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "translation_languages" do + test "returns supported languages list", %{conn: conn} do + clear_config([Pleroma.Language.Translation, :provider], TranslationMock) + + assert %{ + "source" => [%{"code" => "en", "name" => "en"}, %{"code" => "pl", "name" => "pl"}], + "target" => [%{"code" => "en", "name" => "en"}, %{"code" => "pl", "name" => "pl"}] + } = + conn + |> get("/api/v1/akkoma/translation/languages") + |> json_response_and_validate_schema(200) + end + + test "returns empty object when disabled", %{conn: conn} do + clear_config([Pleroma.Language.Translation, :provider], nil) + + assert %{} == + conn + |> get("/api/v1/akkoma/translation/languages") + |> json_response(200) + end + end + + describe "translate" do + test "it translates a status to given language" do + clear_config([Pleroma.Language.Translation, :provider], TranslationMock) + + %{conn: conn} = oauth_access(["read:statuses"]) + another_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(another_user, %{ + status: "Cześć!", + visibility: "public", + language: "pl" + }) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/translations/en") + |> json_response_and_validate_schema(200) + + assert response == %{ + "text" => "!ćśezC", + "detected_language" => "pl" + } + end + end +end diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index 7d196b228e..662235f31f 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -191,4 +191,60 @@ test "returns 404 for tags feed", %{conn: conn} 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/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index c6299f0345..bf08cc4be6 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -125,7 +125,7 @@ test "get oauth_consumer_strategies", %{conn: conn} do describe "instance domain blocks" do setup do - clear_config([:mrf_simple, :reject], [{"fediverse.pl", "uses Soapbox"}]) + clear_config([:mrf_simple, :reject], [{"fediverse.pl", "uses pl-fe"}]) end test "get instance domain blocks", %{conn: conn} do @@ -133,7 +133,7 @@ test "get instance domain blocks", %{conn: conn} do assert [ %{ - "comment" => "uses Soapbox", + "comment" => "uses pl-fe", "digest" => "55e3f44aefe7eb022d3b1daaf7396cabf7f181bf6093c8ea841e30c9fc7d8226", "domain" => "fediverse.pl", "severity" => "suspend" diff --git a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs index 0762e3233d..0a428ddb4e 100644 --- a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs @@ -78,7 +78,7 @@ test "by default, does not contain pleroma:chat_mention" do assert [_] = result end - test "by default, does not contain pleroma:report" do + test "by default, does not contain admin.report" do clear_config([:instance, :moderator_privileges], [:reports_manage_reports]) user = insert(:user) @@ -105,13 +105,13 @@ test "by default, does not contain pleroma:report" do result = conn - |> get("/api/v1/notifications?include_types[]=pleroma:report") + |> get("/api/v1/notifications?include_types[]=admin.report") |> json_response_and_validate_schema(200) assert [_] = result end - test "Pleroma:report is hidden for non-privileged users" do + test "Admin.report is hidden for non-privileged users" do clear_config([:instance, :moderator_privileges], [:reports_manage_reports]) user = insert(:user) @@ -131,7 +131,7 @@ test "Pleroma:report is hidden for non-privileged users" do result = conn - |> get("/api/v1/notifications?include_types[]=pleroma:report") + |> get("/api/v1/notifications?include_types[]=admin.report") |> json_response_and_validate_schema(200) assert [_] = result @@ -140,7 +140,7 @@ test "Pleroma:report is hidden for non-privileged users" do result = conn - |> get("/api/v1/notifications?include_types[]=pleroma:report") + |> get("/api/v1/notifications?include_types[]=admin.report") |> json_response_and_validate_schema(200) assert [] == result 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 7912b1d5f7..51af877424 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 @@ test "returns poll entity for object id", %{user: user, conn: conn} 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/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index d806548fc5..f66d3913a7 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 @@ test "ChatMessage notification" 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 @@ test "Mention notification" 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 @@ test "Favourite notification" 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 @@ test "Reblog notification" 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 @@ test "Follow notification" 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}), @@ -166,6 +171,7 @@ test "Move notification" 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}), @@ -191,6 +197,7 @@ test "EmojiReact notification" 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: "☕", @@ -230,6 +237,7 @@ test "EmojiReact custom emoji notification" 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:", @@ -249,6 +257,7 @@ test "Poll notification" do expected = %{ id: to_string(notification.id), + group_key: "ungrouped-#{to_string(notification.id)}", pleroma: %{is_seen: false, is_muted: false}, type: "poll", account: @@ -275,8 +284,9 @@ test "Report notification" do expected = %{ id: to_string(notification.id), + group_key: "ungrouped-#{to_string(notification.id)}", pleroma: %{is_seen: false, is_muted: false}, - type: "pleroma:report", + type: "admin.report", account: AccountView.render("show.json", %{user: reporting_user, for: moderator_user}), created_at: Utils.to_masto_date(notification.inserted_at), report: ReportView.render("show.json", Report.extract_report_info(activity)) @@ -301,6 +311,7 @@ test "Edit notification" 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}), @@ -323,6 +334,7 @@ test "muted notification" 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}), @@ -346,6 +358,7 @@ test "Subscribed status notification" 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/metadata/providers/feed_test.exs b/test/pleroma/web/metadata/providers/feed_test.exs index e593453dad..40d9d09090 100644 --- a/test/pleroma/web/metadata/providers/feed_test.exs +++ b/test/pleroma/web/metadata/providers/feed_test.exs @@ -15,4 +15,10 @@ test "it renders a link to user's atom feed" do [rel: "alternate", type: "application/atom+xml", href: "/users/lain/feed.atom"], []} ] end + + test "it doesn't render a link to remote user's feed" do + user = insert(:user, nickname: "lain@lain.com", local: false) + + assert Feed.build_tags(%{user: user}) == [] + end end diff --git a/test/pleroma/web/o_auth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs index 07ce2eed84..35b947fd04 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 @@ test "authorizes the existing user using LDAP credentials" 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 @@ test "authorizes the existing user using LDAP credentials" do token = Repo.get_by(Token, token: token) assert token.user_id == user.id - assert_received :close_connection end end @@ -72,10 +67,6 @@ test "creates a new user after successful LDAP authorization" 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 @@ test "creates a new user after successful LDAP authorization" 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 @@ test "disallow authorization for wrong LDAP credentials" 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 @@ test "disallow authorization for wrong LDAP credentials" do }) assert %{"error" => "Invalid credentials"} = json_response(conn, 400) - assert_received :close_connection end end end diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index b8acd01c59..bdbf3de320 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -70,6 +70,24 @@ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} 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 @@ test "check bcrypt hash" 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/utils/colors_test.exs b/test/pleroma/web/utils/colors_test.exs index 91d23f6265..98c5d0745e 100644 --- a/test/pleroma/web/utils/colors_test.exs +++ b/test/pleroma/web/utils/colors_test.exs @@ -25,15 +25,15 @@ test "generates tints from a base color" do } == Colors.get_shades(@base_color) end - test "uses soapbox blue if invalid color provided" do + test "uses pl-fe default color if invalid color provided" do assert %{ - 500 => "4, 130, 216" + 500 => "216, 4, 130" } = Colors.get_shades("255, 255, 127") end end test "shades_to_css/2" do result = Colors.shades_to_css("primary") - assert String.contains?(result, "--color-primary-500: 4, 130, 216;") + assert String.contains?(result, "--color-primary-500: 216, 4, 130;") end end diff --git a/test/pleroma/workers/poll_worker_test.exs b/test/pleroma/workers/poll_worker_test.exs index 749df8affd..a7cbbdb83c 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 @@ test "poll notification job" 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/support/factory.ex b/test/support/factory.ex index db6b04ce78..d56fc9fcea 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -241,6 +241,7 @@ def tombstone_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 @@ def question_factory(attrs \\ %{}) 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 @@ def question_activity_factory(attrs \\ %{}) do %Pleroma.Activity{ data: data, actor: data["actor"], - recipients: data["to"] + recipients: data["to"], + local: user.local } |> Map.merge(attrs) end