diff --git a/.gitignore b/.gitignore
index 355cea069..d8e5ed553 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,9 @@ pleroma.iml
# asdf
.tool-versions
+# mise
+mise.toml
+
# Editor temp files
*~
*#
diff --git a/.woodpecker/changelog.yaml b/.woodpecker/changelog.yaml
index 4f38ce618..64062f17e 100644
--- a/.woodpecker/changelog.yaml
+++ b/.woodpecker/changelog.yaml
@@ -1,9 +1,19 @@
when:
- event: pull_request
+labels:
+ platform: linux/amd64
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
+
steps:
check-changelog:
image: docker.io/alpine:3.23
+ entrypoint: *script_file_entrypoint
commands:
- apk add --no-cache git
- sh ./tools/check-changelog
diff --git a/.woodpecker/docker-combine.yaml b/.woodpecker/docker-combine.yaml
new file mode 100644
index 000000000..6c91d26e9
--- /dev/null
+++ b/.woodpecker/docker-combine.yaml
@@ -0,0 +1,60 @@
+when:
+ - event: push
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
+ - event: tag
+ - event: manual
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ - event: manual
+ branch: stable
+
+depends_on:
+ - docker
+
+skip_clone: true
+
+labels:
+ platform: linux/amd64
+
+steps:
+ docker-develop-combine:
+ image: git.fluffytail.org/phnt/wpc-docker-tagger:latest
+ when:
+ - event: push
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ - event: manual
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ settings: &docker_settings
+ registry: "git.pleroma.social"
+ image: "pleroma/pleroma"
+ architectures: [amd64, arm64]
+ tags:
+ - latest
+ - develop
+ - ${CI_COMMIT_SHA:0:8}
+ username:
+ from_secret: pleroma-ci-user
+ password:
+ from_secret: pleroma-ci-password
+
+ docker-stable-combine:
+ image: git.fluffytail.org/phnt/wpc-docker-tagger:latest
+ when:
+ - evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
+ settings:
+ <<: *docker_settings
+ tags: &stable_docker_tags
+ - latest
+ - stable
+ - ${CI_COMMIT_SHA:0:8}
+
+ docker-stable-tag-combine:
+ image: git.fluffytail.org/phnt/wpc-docker-tagger:latest
+ when:
+ - event: tag
+ - evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
+ settings:
+ <<: *docker_settings
+ tags:
+ - <<: *stable_docker_tags
+ - ${CI_COMMIT_TAG}
diff --git a/.woodpecker/docker.yaml b/.woodpecker/docker.yaml
new file mode 100644
index 000000000..abc6bfa3b
--- /dev/null
+++ b/.woodpecker/docker.yaml
@@ -0,0 +1,96 @@
+when:
+ - event: push
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
+ - event: tag
+ - event: manual
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ - event: manual
+ branch: stable
+
+matrix:
+ platform:
+ - linux/amd64
+ - linux/arm64
+
+# This is needed for the when clauses below.
+labels:
+ platform: ${platform}
+ memory: 'high'
+
+variables:
+ docker_variables: &docker_variables
+ repo: pleroma/pleroma
+ registry: git.pleroma.social
+ username:
+ from_secret: pleroma-ci-user
+ password:
+ from_secret: pleroma-ci-password
+ kaniko_image: &kaniko_image woodpeckerci/plugin-kaniko:2.3.1
+
+steps:
+ docker-develop-amd64:
+ image: woodpeckerci/plugin-kaniko:2.3.1
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ settings:
+ <<: *docker_variables
+ tags:
+ - latest-amd64
+ - develop-amd64
+ - ${CI_COMMIT_SHA:0:8}-amd64
+
+ docker-develop-arm64:
+ image: woodpeckerci/plugin-kaniko:2.3.1
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ settings:
+ <<: *docker_variables
+ tags:
+ - latest-arm64
+ - develop-arm64
+ - ${CI_COMMIT_SHA:0:8}-arm64
+
+ docker-stable-amd64:
+ image: *kaniko_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
+ settings:
+ <<: *docker_variables
+ tags: &amd64_tags
+ - latest-amd64
+ - stable-amd64
+ - ${CI_COMMIT_SHA:0:8}-amd64
+
+ docker-stable-tag-amd64:
+ image: *kaniko_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
+ settings:
+ <<: *docker_variables
+ tags:
+ - <<: *amd64_tags
+ - ${CI_COMMIT_TAG}-amd64
+
+ docker-stable-arm64:
+ image: *kaniko_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
+ settings:
+ <<: *docker_variables
+ tags: &arm64_tags
+ - latest-arm64
+ - stable-arm64
+ - ${CI_COMMIT_SHA:0:8}-arm64
+
+ docker-stable-tag-arm64:
+ image: *kaniko_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
+ settings:
+ <<: *docker_variables
+ tags:
+ - <<: *arm64_tags
+ - ${CI_COMMIT_TAG}-arm64
diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml
index b96d584ee..0ab7441a8 100644
--- a/.woodpecker/lint.yaml
+++ b/.woodpecker/lint.yaml
@@ -1,14 +1,24 @@
when:
- event: pull_request
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
+
+labels:
+ platform: linux/amd64
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
steps:
mix-format:
image: &elixir-image
docker.io/elixir:1.15-alpine
+ entrypoint: *script_file_entrypoint
failure: ignore
commands:
- |
@@ -19,6 +29,7 @@ steps:
credo:
image: *elixir-image
+ entrypoint: *script_file_entrypoint
failure: ignore
environment:
MIX_ENV: test
@@ -55,6 +66,7 @@ steps:
ensure-status:
image: *elixir-image
+ entrypoint: *script_file_entrypoint
commands: |
if test -f fail.stamp; then
echo "One or more previous steps fails. Failing workflow..."
diff --git a/.woodpecker/otp-musl.yaml b/.woodpecker/otp-musl.yaml
new file mode 100644
index 000000000..60558b893
--- /dev/null
+++ b/.woodpecker/otp-musl.yaml
@@ -0,0 +1,292 @@
+when:
+ - event: push
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
+ - event: tag
+ - event: manual
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ - event: manual
+ branch: stable
+
+matrix:
+ platform:
+ - linux/amd64
+ - linux/arm
+ - linux/arm64
+
+# This is needed for the when clauses below.
+# When the platform clause is fixed, this might not be needed anymore
+labels:
+ platform: ${platform}
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
+ build_cmds: &build_cmds
+ - apk add git build-base cmake file-dev openssl vips-dev zip
+ - echo "import Config" > config/prod.secret.exs
+ - mix local.hex --force
+ - mix local.rebar --force
+ - mix deps.get --only prod
+ - mkdir release
+ - export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH}
+ - mix release --path release
+ build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
+ build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3-alpine
+ build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-alpine-3.22.1
+ artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
+ artifacts_uploader_settings: &artifacts_uploader_settings
+ user:
+ from_secret: pleroma-ci-user
+ password:
+ from_secret: pleroma-ci-password
+ owner: 'pleroma'
+ env: &env
+ MIX_ENV: prod
+ VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
+
+steps:
+ otp-develop-amd64-musl:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &amd64_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release
+
+ otp-stable-amd64-musl:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *amd64_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-amd64-musl:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release
+
+ otp-develop-arm-musl:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &arm_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip release
+
+ otp-stable-arm-musl:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *arm_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-arm-musl:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip release
+
+ otp-develop-arm64-musl:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &arm64_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release
+
+ otp-stable-arm64-musl:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *arm64_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-arm64-musl:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release
+
+ upload-artifacts-amd64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ update: 'true'
+
+ upload-latest-amd64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-amd64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-amd64-musl
+ package_version: stable-${CI_COMMIT_SHA:0:8}-amd64-musl
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-amd64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-amd64-musl
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ upload-artifacts-arm-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ update: 'true'
+
+ upload-latest-arm-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-arm-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm-musl
+ package_version: stable-${CI_COMMIT_SHA:0:8}-arm-musl
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-arm-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm-musl
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ upload-artifacts-arm64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ update: 'true'
+
+ upload-latest-arm64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-arm64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm64-musl
+ package_version: stable-${CI_COMMIT_SHA:0:8}-arm64-musl
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-arm64-musl:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm64-musl
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
+ file_name: pleroma.zip
+ update: 'true'
diff --git a/.woodpecker/otp.yaml b/.woodpecker/otp.yaml
new file mode 100644
index 000000000..9a33c228e
--- /dev/null
+++ b/.woodpecker/otp.yaml
@@ -0,0 +1,293 @@
+when:
+ - event: push
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
+ - event: tag
+ - event: manual
+ branch: ${CI_REPO_DEFAULT_BRANCH}
+ - event: manual
+ branch: stable
+
+matrix:
+ platform:
+ - linux/amd64
+ - linux/arm
+ - linux/arm64
+
+# This is needed for the when clauses below.
+# When the platform clause is fixed, this might not be needed anymore
+labels:
+ platform: ${platform}
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
+ build_cmds: &build_cmds
+ - apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git build-essential zip
+ - echo "import Config" > config/prod.secret.exs
+ - mix local.hex --force
+ - mix local.rebar --force
+ - mix deps.get --only prod
+ - mkdir release
+ - export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH}
+ - mix release --path release
+ build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir-amd64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
+ build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3
+ build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir-arm64:1.17.3-erlang-27.3.4.2-ubuntu-noble-20250716
+ artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
+ artifacts_uploader_settings: &artifacts_uploader_settings
+ user:
+ from_secret: pleroma-ci-user
+ password:
+ from_secret: pleroma-ci-password
+ owner: 'pleroma'
+ env: &env
+ MIX_ENV: prod
+ VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
+ DEBIAN_FRONTEND: noninteractive
+
+steps:
+ otp-develop-amd64:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &amd64_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip release
+
+ otp-stable-amd64:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *amd64_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-amd64:
+ image: *build_image_amd64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64.zip release
+
+ otp-develop-arm:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &arm_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip release
+
+ otp-stable-arm:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *arm_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-arm:
+ image: *build_image_arm
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm.zip release
+
+ otp-develop-arm64:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ environment: *env
+ commands: &arm64_build
+ - <<: *build_cmds
+ - zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip release
+
+ otp-stable-arm64:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
+ environment: *env
+ commands: *arm64_build
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ otp-stable-tag-arm64:
+ image: *build_image_arm64
+ entrypoint: *script_file_entrypoint
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ environment: *env
+ commands:
+ - <<: *build_cmds
+ - zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64.zip release
+
+ upload-artifacts-amd64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
+ update: 'true'
+
+ upload-latest-amd64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-amd64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-amd64
+ package_version: stable-${CI_COMMIT_SHA:0:8}-amd64
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-amd64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-amd64
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ upload-artifacts-arm:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
+ update: 'true'
+
+ upload-latest-arm:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-arm:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm
+ package_version: stable-${CI_COMMIT_SHA:0:8}-arm
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-arm:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ upload-artifacts-arm64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64
+ package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
+ file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
+ update: 'true'
+
+ upload-latest-arm64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64
+ package_version: latest
+ file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
+ file_name: pleroma.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-artifacts-tag-arm64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm64
+ package_version: stable-${CI_COMMIT_SHA:0:8}-arm64
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip
+ file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64.zip
+ update: 'true'
+
+ # Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
+ upload-latest-tag-arm64:
+ image: *artifacts_uploader_image
+ when:
+ - evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
+ settings:
+ <<: *artifacts_uploader_settings
+ package_name: pleroma-otp-stable-arm64
+ package_version: latest
+ file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip
+ file_name: pleroma.zip
+ update: 'true'
diff --git a/.woodpecker/unit-testing-elixir-1.15.yaml b/.woodpecker/unit-testing-elixir-1.15.yaml
index 84046be41..a4a8fc266 100644
--- a/.woodpecker/unit-testing-elixir-1.15.yaml
+++ b/.woodpecker/unit-testing-elixir-1.15.yaml
@@ -1,16 +1,26 @@
when:
- event: pull_request
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
depends_on:
- lint
+labels:
+ platform: linux/amd64
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
+
steps:
unit-testing-elixir-1.15:
image: elixir:1.15-alpine
+ entrypoint: *script_file_entrypoint
environment:
MIX_ENV: test
DB_HOST: postgres
diff --git a/.woodpecker/unit-testing-elixir-1.18.yaml b/.woodpecker/unit-testing-elixir-1.18.yaml
index 8dcec62fb..9ad9eebc9 100644
--- a/.woodpecker/unit-testing-elixir-1.18.yaml
+++ b/.woodpecker/unit-testing-elixir-1.18.yaml
@@ -1,16 +1,26 @@
when:
- event: pull_request
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- path: [ "*.ex", "*.eex", "*.exs", "mix.lock", ".woodpecker/**" ]
+ path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
depends_on:
- lint
+labels:
+ platform: linux/amd64
+
+variables:
+ script_file_entrypoint: &script_file_entrypoint
+ - /bin/sh
+ - -c
+ - 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
+
steps:
unit-testing-elixir-1.18:
image: elixir:1.18-otp-27-alpine
+ entrypoint: *script_file_entrypoint
environment:
MIX_ENV: test
DB_HOST: postgres
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adc76c767..f03be3f29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,60 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## 2.10.2
+
+### Security
+
+- ActivityPub: Fixed failed-signature inbox retry handling and signer identity checks to prevent spoofed remote activities from being processed
+
+## 2.10.1
+
+### Changed
+
+- Move avatar_description and header_description fields to the account object
+- Update Bandit to 1.10.4
+- No-op code correctness improvements detected by Elixir 1.19 compiler
+- Downgrade Hackney to 1.20.1
+- Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney
+- Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation
+- Paginate follow requests
+- Moved Phoenix LiveDashboard to /pleroma/live_dashboard
+- Add mute/block expiry to the relationship object
+- Filter indexable activities before inserting indexing jobs into the queue.
+
+### Added
+
+- Allow assigning users to reports
+- Allow fine-grained announce visibilities
+- Add immutable tag on cache-control header for several endpoints that's serving the same exact things.
+- Add reasonable defaults for :database_config_whitelist
+- Support lists `exclusive` param
+- Add v1/instance/domain_blocks endpoint
+- Add /api/v2/instance profile fields limits info used by Mastodon
+- Added Oban Web dashboard located at /pleroma/oban
+- Add instructions on how to run a release in docker, to make it easier to run on older distros.
+
+### Fixed
+
+- Fix the daily email digest job which was not executing
+- Encode custom emoji URLs in EmojiReact activity tags.
+- Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled
+- Fix fetching Hubzilla Actors with alsoKnownAs as string
+- Fix /phoenix/live_dashboard redirect not working when user added a path segment
+- Fix 404 error codes for missing static files
+- Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input.
+- Correct old migrations for expiring activities and user access tokens.
+- Federate `votersCount` correctly
+- DB prune: Check if user follows hashtag with no objects before deletion
+- Stop the rate limiter from crashing when run with wrong settings.
+- Restore embeds route
+- ReverseProxy: Recursively follow redirects until redirect_limit is reached
+- Fix compilation with vips-8.18.0 with bumping to vix 0.36.0
+
+### Removed
+
+- Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)
+
## 2.10
### Security
diff --git a/changelog.d/assign-users.add b/changelog.d/assign-users.add
deleted file mode 100644
index f50ad94c6..000000000
--- a/changelog.d/assign-users.add
+++ /dev/null
@@ -1 +0,0 @@
-Allow assigning users to reports
\ No newline at end of file
diff --git a/changelog.d/avatar-description-mastodon-api.change b/changelog.d/avatar-description-mastodon-api.change
deleted file mode 100644
index 6a454c01e..000000000
--- a/changelog.d/avatar-description-mastodon-api.change
+++ /dev/null
@@ -1 +0,0 @@
-Move avatar_description and header_description fields to the account object
diff --git a/changelog.d/bandit.change b/changelog.d/bandit.change
deleted file mode 100644
index 3831a02c2..000000000
--- a/changelog.d/bandit.change
+++ /dev/null
@@ -1 +0,0 @@
-Update Bandit to 1.10.4
diff --git a/changelog.d/bookmark-folders.ignore b/changelog.d/bookmark-folders.ignore
deleted file mode 100644
index 8705ac00b..000000000
--- a/changelog.d/bookmark-folders.ignore
+++ /dev/null
@@ -1 +0,0 @@
-Various bookmark folders-related improvements
diff --git a/changelog.d/boost-visibilities.add b/changelog.d/boost-visibilities.add
deleted file mode 100644
index 317d9840d..000000000
--- a/changelog.d/boost-visibilities.add
+++ /dev/null
@@ -1 +0,0 @@
-Allow fine-grained announce visibilities
diff --git a/changelog.d/cache-control-immutable.add b/changelog.d/cache-control-immutable.add
deleted file mode 100644
index 516db67bf..000000000
--- a/changelog.d/cache-control-immutable.add
+++ /dev/null
@@ -1 +0,0 @@
-Add immutable tag on cache-control header for several endpoints that's serving the same exact things.
\ No newline at end of file
diff --git a/changelog.d/ci-artifacts.skip b/changelog.d/ci-artifacts.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/context-cleanup.skip b/changelog.d/context-cleanup.skip
new file mode 100644
index 000000000..ae609602d
--- /dev/null
+++ b/changelog.d/context-cleanup.skip
@@ -0,0 +1 @@
+litepub-0.1.jsonld cleanup
diff --git a/changelog.d/credo-aliases-sort-fixes.skip b/changelog.d/credo-aliases-sort-fixes.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/database-config-whitelist.add b/changelog.d/database-config-whitelist.add
deleted file mode 100644
index a78960c98..000000000
--- a/changelog.d/database-config-whitelist.add
+++ /dev/null
@@ -1 +0,0 @@
-Add reasonable defaults for :database_config_whitelist
\ No newline at end of file
diff --git a/changelog.d/elixir-1.19-cherrypicks.change b/changelog.d/elixir-1.19-cherrypicks.change
deleted file mode 100644
index 7e56be008..000000000
--- a/changelog.d/elixir-1.19-cherrypicks.change
+++ /dev/null
@@ -1 +0,0 @@
-No-op code correctness improvements detected by Elixir 1.19 compiler
diff --git a/changelog.d/email_digest.fix b/changelog.d/email_digest.fix
deleted file mode 100644
index cd15874a2..000000000
--- a/changelog.d/email_digest.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix the daily email digest job which was not executing
diff --git a/changelog.d/emoji-reaction-url-escape.fix b/changelog.d/emoji-reaction-url-escape.fix
deleted file mode 100644
index c3a1c8823..000000000
--- a/changelog.d/emoji-reaction-url-escape.fix
+++ /dev/null
@@ -1 +0,0 @@
-Encode custom emoji URLs in EmojiReact activity tags.
diff --git a/changelog.d/exclusive-lists.add b/changelog.d/exclusive-lists.add
deleted file mode 100644
index bbd722f07..000000000
--- a/changelog.d/exclusive-lists.add
+++ /dev/null
@@ -1 +0,0 @@
-Support lists `exclusive` param
diff --git a/changelog.d/gopher-genserver-crash-on-boot.fix b/changelog.d/gopher-genserver-crash-on-boot.fix
deleted file mode 100644
index 3b51662be..000000000
--- a/changelog.d/gopher-genserver-crash-on-boot.fix
+++ /dev/null
@@ -1 +0,0 @@
-Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled
diff --git a/changelog.d/hackney-downgrade.change b/changelog.d/hackney-downgrade.change
deleted file mode 100644
index a98710692..000000000
--- a/changelog.d/hackney-downgrade.change
+++ /dev/null
@@ -1 +0,0 @@
-Downgrade Hackney to 1.20.1
diff --git a/changelog.d/hackney-mediaproxy.change b/changelog.d/hackney-mediaproxy.change
deleted file mode 100644
index 10dfb0775..000000000
--- a/changelog.d/hackney-mediaproxy.change
+++ /dev/null
@@ -1 +0,0 @@
-Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney
diff --git a/changelog.d/hackney.change b/changelog.d/hackney.change
deleted file mode 100644
index 3158cfc77..000000000
--- a/changelog.d/hackney.change
+++ /dev/null
@@ -1 +0,0 @@
-Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation
diff --git a/changelog.d/hubzilla-alsoknownas.fix b/changelog.d/hubzilla-alsoknownas.fix
deleted file mode 100644
index 2a2969807..000000000
--- a/changelog.d/hubzilla-alsoknownas.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix fetching Hubzilla Actors with alsoKnownAs as string
diff --git a/changelog.d/inappropriate-docs.remove b/changelog.d/inappropriate-docs.remove
deleted file mode 100644
index 699c9186a..000000000
--- a/changelog.d/inappropriate-docs.remove
+++ /dev/null
@@ -1 +0,0 @@
-Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)
diff --git a/changelog.d/instance-domain-blocks.add b/changelog.d/instance-domain-blocks.add
deleted file mode 100644
index 85f01c5c2..000000000
--- a/changelog.d/instance-domain-blocks.add
+++ /dev/null
@@ -1 +0,0 @@
-Add v1/instance/domain_blocks endpoint
diff --git a/changelog.d/instance-profile-fields.add b/changelog.d/instance-profile-fields.add
deleted file mode 100644
index 712bd68d9..000000000
--- a/changelog.d/instance-profile-fields.add
+++ /dev/null
@@ -1 +0,0 @@
-Add /api/v2/instance profile fields limits info used by Mastodon
diff --git a/changelog.d/lint-warnings.skip b/changelog.d/lint-warnings.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/live-dashboard-redirect.fix b/changelog.d/live-dashboard-redirect.fix
deleted file mode 100644
index 10588d89e..000000000
--- a/changelog.d/live-dashboard-redirect.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix /phoenix/live_dashboard redirect not working when user added a path segment
diff --git a/changelog.d/map-side-effects.skip b/changelog.d/map-side-effects.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/mfm-backend.add b/changelog.d/mfm-backend.add
new file mode 100644
index 000000000..f815c6828
--- /dev/null
+++ b/changelog.d/mfm-backend.add
@@ -0,0 +1 @@
+Add backend support for Misskey Markdown (MFM) posts
diff --git a/changelog.d/missing-static-file.fix b/changelog.d/missing-static-file.fix
deleted file mode 100644
index c7ef805aa..000000000
--- a/changelog.d/missing-static-file.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix 404 error codes for missing static files
diff --git a/changelog.d/mix-exs-fix.skip b/changelog.d/mix-exs-fix.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/mix-exs-update.skip b/changelog.d/mix-exs-update.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/oauth-registration-redirect_uris.fix b/changelog.d/oauth-registration-redirect_uris.fix
deleted file mode 100644
index 76ace55df..000000000
--- a/changelog.d/oauth-registration-redirect_uris.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input.
diff --git a/changelog.d/oban-web.add b/changelog.d/oban-web.add
deleted file mode 100644
index c59e2ebca..000000000
--- a/changelog.d/oban-web.add
+++ /dev/null
@@ -1 +0,0 @@
-Added Oban Web dashboard located at /pleroma/oban
diff --git a/changelog.d/old-migrations.fix b/changelog.d/old-migrations.fix
deleted file mode 100644
index 49566c896..000000000
--- a/changelog.d/old-migrations.fix
+++ /dev/null
@@ -1 +0,0 @@
-Correct old migrations for expiring activities and user access tokens.
diff --git a/changelog.d/paginate-follow-requests.change b/changelog.d/paginate-follow-requests.change
deleted file mode 100644
index 1a88995b7..000000000
--- a/changelog.d/paginate-follow-requests.change
+++ /dev/null
@@ -1 +0,0 @@
-Paginate follow requests
diff --git a/changelog.d/phoenix-livedashboard-move.change b/changelog.d/phoenix-livedashboard-move.change
deleted file mode 100644
index 116b1523a..000000000
--- a/changelog.d/phoenix-livedashboard-move.change
+++ /dev/null
@@ -1 +0,0 @@
-Moved Phoenix LiveDashboard to /pleroma/live_dashboard
diff --git a/changelog.d/pleroma-fe-link.fix b/changelog.d/pleroma-fe-link.fix
new file mode 100644
index 000000000..de93f86dd
--- /dev/null
+++ b/changelog.d/pleroma-fe-link.fix
@@ -0,0 +1 @@
+Updated Pleroma-FE build URL after Forgejo migration
diff --git a/changelog.d/plug-test-typo.skip b/changelog.d/plug-test-typo.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/poll-voters-count-inflation.fix b/changelog.d/poll-voters-count-inflation.fix
new file mode 100644
index 000000000..7eae41f13
--- /dev/null
+++ b/changelog.d/poll-voters-count-inflation.fix
@@ -0,0 +1 @@
+Fix votersCount inflation when same voter picks multiple options
diff --git a/changelog.d/poll-voters-count.fix b/changelog.d/poll-voters-count.fix
deleted file mode 100644
index 2dbc81b5d..000000000
--- a/changelog.d/poll-voters-count.fix
+++ /dev/null
@@ -1 +0,0 @@
-Federate `votersCount` correctly
diff --git a/changelog.d/prune-hashtag-follow-3376.fix b/changelog.d/prune-hashtag-follow-3376.fix
deleted file mode 100644
index cdb4e9a79..000000000
--- a/changelog.d/prune-hashtag-follow-3376.fix
+++ /dev/null
@@ -1 +0,0 @@
-DB prune: Check if user follows hashtag with no objects before deletion
diff --git a/changelog.d/rate-limiter-hardening.fix b/changelog.d/rate-limiter-hardening.fix
deleted file mode 100644
index a3af8fcc4..000000000
--- a/changelog.d/rate-limiter-hardening.fix
+++ /dev/null
@@ -1 +0,0 @@
-Stop the rate limiter from crashing when run with wrong settings.
diff --git a/changelog.d/reduce-flaky-tests.skip b/changelog.d/reduce-flaky-tests.skip
deleted file mode 100644
index 0375762c0..000000000
--- a/changelog.d/reduce-flaky-tests.skip
+++ /dev/null
@@ -1 +0,0 @@
-Reduce the number of flaky tests by making them sync if they affect the global state, and silence noisy test output.
diff --git a/changelog.d/relationship-expires-at.change b/changelog.d/relationship-expires-at.change
deleted file mode 100644
index 286dba197..000000000
--- a/changelog.d/relationship-expires-at.change
+++ /dev/null
@@ -1 +0,0 @@
-Add mute/block expiry to the relationship object
diff --git a/changelog.d/release-to-docker.add b/changelog.d/release-to-docker.add
deleted file mode 100644
index 5fbf611a5..000000000
--- a/changelog.d/release-to-docker.add
+++ /dev/null
@@ -1 +0,0 @@
-Add instructions on how to run a release in docker, to make it easier to run on older distros.
diff --git a/changelog.d/restore-embeds.fix b/changelog.d/restore-embeds.fix
deleted file mode 100644
index 5a2a1c4fe..000000000
--- a/changelog.d/restore-embeds.fix
+++ /dev/null
@@ -1 +0,0 @@
-Restore embeds route
diff --git a/changelog.d/reverseproxy-recursive-redirect.fix b/changelog.d/reverseproxy-recursive-redirect.fix
deleted file mode 100644
index 744109fd6..000000000
--- a/changelog.d/reverseproxy-recursive-redirect.fix
+++ /dev/null
@@ -1 +0,0 @@
-ReverseProxy: Recursively follow redirects until redirect_limit is reached
diff --git a/changelog.d/search-indexing.change b/changelog.d/search-indexing.change
deleted file mode 100644
index 766934f3f..000000000
--- a/changelog.d/search-indexing.change
+++ /dev/null
@@ -1 +0,0 @@
-Filter indexable activities before inserting indexing jobs into the queue.
diff --git a/changelog.d/search-indexing.skip b/changelog.d/search-indexing.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/twitter-api.skip b/changelog.d/twitter-api.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/changelog.d/update-comment.ignore b/changelog.d/update-comment.ignore
deleted file mode 100644
index 733e813b3..000000000
--- a/changelog.d/update-comment.ignore
+++ /dev/null
@@ -1 +0,0 @@
-Update comment for prepare_object, rename prepare_outgoing
diff --git a/changelog.d/user-view.ignore b/changelog.d/user-view.ignore
deleted file mode 100644
index 37e9a7e09..000000000
--- a/changelog.d/user-view.ignore
+++ /dev/null
@@ -1 +0,0 @@
-Avoid code duplication in UserView
diff --git a/changelog.d/vix-0.36.0.fix b/changelog.d/vix-0.36.0.fix
deleted file mode 100644
index 43a8dd8f8..000000000
--- a/changelog.d/vix-0.36.0.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix compilation with vips-8.18.0 with bumping to vix 0.36.0
diff --git a/changelog.d/woodpecker-pr-pipeline.skip b/changelog.d/woodpecker-pr-pipeline.skip
deleted file mode 100644
index e69de29bb..000000000
diff --git a/config/config.exs b/config/config.exs
index 5bf2c5c2e..2d38e3ebe 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -203,7 +203,8 @@ config :pleroma, :instance,
"text/plain",
"text/html",
"text/markdown",
- "text/bbcode"
+ "text/bbcode",
+ "text/x.misskeymarkdown"
],
autofollowed_nicknames: [],
autofollowing_nicknames: [],
@@ -775,7 +776,7 @@ config :pleroma, :frontends,
"name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" =>
- "https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
+ "https://git.pleroma.social/api/packages/pleroma/generic/pleroma-fe-builds/${ref}/latest.zip",
"ref" => "develop"
},
"fedi-fe" => %{
diff --git a/config/description.exs b/config/description.exs
index c388d17c3..6e4348907 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -815,7 +815,8 @@ config :pleroma, :config_description, [
"text/plain",
"text/html",
"text/markdown",
- "text/bbcode"
+ "text/bbcode",
+ "text/x.misskeymarkdown"
]
},
%{
@@ -1394,7 +1395,13 @@ config :pleroma, :config_description, [
label: "Post Content Type",
type: {:dropdown, :atom},
description: "Default post formatting option",
- suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
+ suggestions: [
+ "text/plain",
+ "text/html",
+ "text/markdown",
+ "text/bbcode",
+ "text/x.misskeymarkdown"
+ ]
},
%{
key: :redirectRootNoLogin,
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 11d5af2fb..4bf2f6b95 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -127,6 +127,13 @@ defmodule Pleroma.Formatter do
Earmark.as_html!(text, %Earmark.Options{compact_output: true, smartypants: false})
end
+ def markdown_to_html(text, opts) do
+ Earmark.as_html!(
+ text,
+ %Earmark.Options{compact_output: true, smartypants: false} |> Map.merge(opts)
+ )
+ end
+
def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags}
end
@@ -135,6 +142,10 @@ defmodule Pleroma.Formatter do
HTML.filter_tags(text)
end
+ def html_escape(text, "text/x.misskeymarkdown") do
+ HTML.filter_tags(text)
+ end
+
def html_escape(text, "text/plain") do
Regex.split(@link_regex, text, include_captures: true)
|> Enum.map_every(2, fn chunk ->
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
index e651d7d9d..e37a3fefe 100644
--- a/lib/pleroma/frontend.ex
+++ b/lib/pleroma/frontend.ex
@@ -75,8 +75,8 @@ defmodule Pleroma.Frontend do
end
defp download_build(frontend_info, dest) do
- Logger.info("Downloading pre-built bundle for #{frontend_info["name"]}")
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
+ Logger.info("Downloading pre-built bundle for #{frontend_info["name"]} from #{url}")
with {:ok, %{status: 200, body: zip_body}} <-
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index f1e07a257..5e9314446 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -372,13 +372,21 @@ defmodule Pleroma.Object do
option
end)
- voters = [actor | object.data["voters"] || []] |> Enum.uniq()
+ existing_voters = object.data["voters"] || []
+ voters = [actor | existing_voters] |> Enum.uniq()
+ new_voter? = actor not in existing_voters
+ existing_voters_count = object.data["votersCount"]
voters_count =
- if Map.has_key?(object.data, "votersCount") do
- object.data["votersCount"] + 1
- else
- length(voters)
+ cond do
+ is_integer(existing_voters_count) and new_voter? ->
+ existing_voters_count + 1
+
+ is_integer(existing_voters_count) ->
+ existing_voters_count
+
+ true ->
+ length(voters)
end
data =
diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex
index af2d35c8f..49400940f 100644
--- a/lib/pleroma/release_tasks.ex
+++ b/lib/pleroma/release_tasks.ex
@@ -5,7 +5,10 @@
defmodule Pleroma.ReleaseTasks do
@repo Pleroma.Repo
- def run(args) do
+ # TODO: Kept for some backwards compatibility with buggy pleroma_ctl,
+ # if a mismatch between pleroma_ctl and Pleroma accidentaly happens.
+ # Remove in the future.
+ def run(args) when is_binary(args) do
[task | args] = String.split(args)
case task do
@@ -16,6 +19,20 @@ defmodule Pleroma.ReleaseTasks do
end
end
+ # HACK: Script arguments need to be received as a list, otherwise (quoted) arguments with
+ # whitespace will be broken. Previously the broken string form above was used,
+ # escaping in the shell does not help.
+ def run(args) when is_list(args) do
+ [task | args] = args
+
+ case task do
+ "migrate" -> migrate(args)
+ "create" -> create()
+ "rollback" -> rollback(args)
+ task -> mix_task(task, args)
+ end
+ end
+
def find_module(task) do
module_name =
task
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 071d634db..0b513ee16 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1677,44 +1677,80 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
show_birthday = !!birthday
- # if WebFinger request was already done, we probably have acct, otherwise
- # we request WebFinger here
- nickname = additional[:nickname_from_acct] || generate_nickname(data)
+ with {:ok, nickname} <- nickname_from_actor(data, additional) do
+ {:ok,
+ %{
+ ap_id: data["id"],
+ uri: get_actor_url(data["url"]),
+ banner: normalize_image(data["image"]),
+ fields: fields,
+ emoji: emojis,
+ is_locked: is_locked,
+ is_discoverable: is_discoverable,
+ invisible: invisible,
+ avatar: normalize_image(data["icon"]),
+ name: data["name"],
+ follower_address: data["followers"],
+ following_address: data["following"],
+ featured_address: featured_address,
+ bio: data["summary"] || "",
+ actor_type: actor_type,
+ also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
+ public_key: public_key,
+ inbox: data["inbox"],
+ shared_inbox: shared_inbox,
+ accepts_chat_messages: accepts_chat_messages,
+ birthday: birthday,
+ show_birthday: show_birthday,
+ pinned_objects: pinned_objects,
+ nickname: nickname
+ }}
+ end
+ end
- %{
- ap_id: data["id"],
- uri: get_actor_url(data["url"]),
- banner: normalize_image(data["image"]),
- fields: fields,
- emoji: emojis,
- is_locked: is_locked,
- is_discoverable: is_discoverable,
- invisible: invisible,
- avatar: normalize_image(data["icon"]),
- name: data["name"],
- follower_address: data["followers"],
- following_address: data["following"],
- featured_address: featured_address,
- bio: data["summary"] || "",
- actor_type: actor_type,
- also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
- public_key: public_key,
- inbox: data["inbox"],
- shared_inbox: shared_inbox,
- accepts_chat_messages: accepts_chat_messages,
- birthday: birthday,
- show_birthday: show_birthday,
- pinned_objects: pinned_objects,
- nickname: nickname
- }
+ defp nickname_from_actor(data, additional) do
+ generated = generated_nickname(data)
+
+ case additional[:nickname_from_acct] do
+ ^generated when is_binary(generated) ->
+ {:ok, generated}
+
+ acct when is_binary(acct) ->
+ with ^acct <- webfinger_nickname(data) do
+ {:ok, acct}
+ else
+ _ -> {:error, {:webfinger_actor_mismatch, acct, data["id"]}}
+ end
+
+ _ ->
+ {:ok, generate_nickname(data)}
+ end
+ end
+
+ defp generated_nickname(%{"preferredUsername" => username, "id" => ap_id})
+ when is_binary(username) and is_binary(ap_id) do
+ case URI.parse(ap_id) do
+ %URI{host: host} when is_binary(host) -> "#{username}@#{host}"
+ _ -> nil
+ end
+ end
+
+ defp generated_nickname(_), do: nil
+
+ defp webfinger_nickname(data) do
+ with generated when is_binary(generated) <- generated_nickname(data),
+ {:ok, %{"subject" => "acct:" <> acct, "ap_id" => ap_id}} <- WebFinger.finger(generated),
+ true <- ap_id == data["id"] do
+ acct
+ end
end
defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do
- generated = "#{username}@#{URI.parse(data["id"]).host}"
+ generated = generated_nickname(data)
if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
- case WebFinger.finger(generated) do
- {:ok, %{"subject" => "acct:" <> acct}} -> acct
+ case webfinger_nickname(data) do
+ acct when is_binary(acct) -> acct
_ -> generated
end
else
@@ -1794,9 +1830,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp collection_private(_data), do: {:ok, true}
def user_data_from_user_object(data, additional \\ []) do
- with {:ok, data} <- MRF.filter(data) do
- {:ok, object_to_user_data(data, additional)}
+ with {:ok, data} <- MRF.filter(data),
+ {:ok, data} <- object_to_user_data(data, additional) do
+ {:ok, data}
else
+ {:error, _} = e -> e
e -> {:error, e}
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 4f1613a07..2bfff6968 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -348,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
- Federator.incoming_ap_doc(%{
+ Federator.incoming_failed_signature_ap_doc(%{
method: conn.method,
req_headers: conn.req_headers,
request_path: conn.request_path,
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index c0626ce4d..844ad2c8f 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -6,9 +6,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.HTML
+ alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.CommonAPI.Utils
import Ecto.Changeset
@@ -26,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
end
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:source, :map)
end
def cast_and_apply(data) do
@@ -80,6 +84,113 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
def fix_attachments(data), do: data
+ defp remote_mention_resolver(
+ %{"id" => ap_id, "tag" => tags},
+ "@" <> nickname = mention,
+ buffer,
+ opts,
+ acc
+ )
+ when is_binary(ap_id) and is_list(tags) do
+ initial_host =
+ ap_id
+ |> URI.parse()
+ |> Map.get(:host)
+
+ with mention_tag when not is_nil(mention_tag) <-
+ Enum.find(tags, &mention_tag?(&1, mention, initial_host)),
+ href when is_binary(href) <- mention_tag["href"],
+ %User{} = user <- User.get_cached_by_ap_id(href) do
+ link = Pleroma.Formatter.mention_from_user(user, opts)
+ {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
+ else
+ _ -> {buffer, acc}
+ end
+ end
+
+ defp remote_mention_resolver(_object, _mention, buffer, _opts, acc), do: {buffer, acc}
+
+ defp mention_tag?(%{"type" => "Mention", "name" => name}, mention, initial_host)
+ when is_binary(name) do
+ name == mention || mention == "#{name}@#{initial_host}"
+ end
+
+ defp mention_tag?(_tag, _mention, _initial_host), do: false
+
+ defp scrub_content(%{"content" => content} = object) when is_binary(content) do
+ Map.put(object, "content", HTML.filter_tags(content))
+ end
+
+ defp scrub_content(object), do: object
+
+ defp mfm_parse_limit do
+ min(Pleroma.Config.get([:instance, :limit]), Pleroma.Config.get([:instance, :remote_limit]))
+ end
+
+ defp normalize_source(%{"source" => source} = object) when is_binary(source) do
+ object
+ |> Map.put("source", %{"content" => source})
+ |> normalize_source()
+ end
+
+ defp normalize_source(%{"source" => source} = object) when is_map(source) do
+ source =
+ case source["content"] do
+ content when is_binary(content) ->
+ if String.length(content) <= mfm_parse_limit() do
+ source
+ else
+ Map.delete(source, "content")
+ end
+
+ nil ->
+ source
+
+ _ ->
+ Map.delete(source, "content")
+ end
+
+ Map.put(object, "source", source)
+ end
+
+ defp normalize_source(object), do: object
+
+ defp fix_misskey_content(%{"htmlMfm" => true, "content" => content} = object)
+ when is_binary(content) do
+ Map.put(object, "content", HTML.filter_tags(content))
+ end
+
+ defp fix_misskey_content(%{"htmlMfm" => true} = object), do: object
+
+ defp fix_misskey_content(
+ %{"source" => %{"mediaType" => "text/x.misskeymarkdown", "content" => content}} = object
+ )
+ when is_binary(content) do
+ mention_handler = fn nick, buffer, opts, acc ->
+ remote_mention_resolver(object, nick, buffer, opts, acc)
+ end
+
+ {linked, _mentions, _tags} =
+ Utils.format_input(content, "text/x.misskeymarkdown", mention_handler: mention_handler)
+
+ Map.put(object, "content", linked)
+ end
+
+ defp fix_misskey_content(%{"source" => %{"mediaType" => "text/x.misskeymarkdown"}} = object),
+ do: scrub_content(object)
+
+ defp fix_misskey_content(%{"_misskey_content" => content} = object) when is_binary(content) do
+ object
+ |> Map.put("source", %{
+ "content" => content,
+ "mediaType" => "text/x.misskeymarkdown"
+ })
+ |> Map.delete("_misskey_content")
+ |> fix_misskey_content()
+ end
+
+ defp fix_misskey_content(object), do: object
+
defp fix(data) do
data
|> CommonFixes.fix_actor()
@@ -88,6 +199,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|> fix_tag()
|> fix_replies()
|> fix_attachments()
+ |> normalize_source()
+ |> fix_misskey_content()
|> CommonFixes.fix_quote_url()
|> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji()
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
index 22cf0cc05..9b8580200 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex
@@ -32,6 +32,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
quote bind_quoted: binding() do
field(:content, :string)
field(:contentMap, ObjectValidators.ContentLanguageMap)
+ field(:htmlMfm, :boolean)
field(:published, ObjectValidators.DateTime)
field(:updated, ObjectValidators.DateTime)
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
index aab90235f..4c0d9dff7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -75,15 +75,40 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
end
end
- # For remote Updates, verify the host is the same.
+ # For remote Updates, verify the Actor is the same
def validate_updating_rights_remote(cng) do
with actor = get_field(cng, :actor),
object = get_field(cng, :object),
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
- actor_uri <- URI.parse(actor),
- object_uri <- URI.parse(object_id),
- true <- actor_uri.host == object_uri.host do
- cng
+ entity <-
+ Object.normalize(object_id, fetch: false) || User.get_cached_by_ap_id(object_id) do
+ case entity do
+ # Actor must own Object to update it
+ %Object{} ->
+ if actor == entity.data["actor"] do
+ cng
+ else
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+ end
+
+ # Actor must only be allowed to update itself
+ %User{} ->
+ if actor == entity.ap_id do
+ cng
+ else
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+ end
+
+ nil ->
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+
+ _ ->
+ cng
+ |> add_error(:object, "Update is neither for Object or Actor")
+ end
else
_e ->
cng
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 43c0f456d..0af4ceaf5 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -120,7 +120,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"https://www.w3.org/ns/activitystreams",
"#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
%{
- "@language" => get_language(data)
+ "@language" => get_language(data),
+ "htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm"
}
]
}
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 16489663a..6072fff6b 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -317,6 +317,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
emoji = Map.merge(emoji, summary_emoji)
+ media_type = Utils.get_content_type(draft.params[:content_type])
{:ok, note_data, _meta} = Builder.note(draft)
object =
@@ -324,14 +325,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> Map.put("emoji", emoji)
|> Map.put("source", %{
"content" => draft.status,
- "mediaType" => Utils.get_content_type(draft.params[:content_type])
+ "mediaType" => media_type
})
+ |> maybe_put("htmlMfm", true, media_type == "text/x.misskeymarkdown")
|> Map.put("generator", draft.params[:generator])
|> Map.put("language", draft.language)
%{draft | object: object}
end
+ defp maybe_put(map, key, value, true), do: Map.put(map, key, value)
+ defp maybe_put(map, _key, _value, _condition), do: map
+
defp preview?(%__MODULE__{} = draft) do
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
%{draft | preview?: preview?}
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 32572a721..26034d685 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -322,6 +322,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.linkify(options)
end
+ def format_input(text, "text/x.misskeymarkdown", options) do
+ text
+ |> Formatter.markdown_to_html(%{breaks: true})
+ |> safe_mfm_to_html()
+ |> Formatter.linkify(options)
+ |> Formatter.html_escape("text/x.misskeymarkdown")
+ end
+
def format_input(text, "text/markdown", options) do
text
|> Formatter.mentions_escape(options)
@@ -330,6 +338,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.html_escape("text/html")
end
+ defp safe_mfm_to_html(html) do
+ html
+ |> MfmParser.Parser.parse()
+ |> MfmParser.Encoder.to_html()
+ rescue
+ _ -> html
+ catch
+ _, _ -> html
+ end
+
def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index 676fc5137..90cd2e54a 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Workers.PublisherWorker
alias Pleroma.Workers.ReceiverWorker
+ alias Pleroma.Workers.SignatureRetryWorker
require Logger
@@ -35,12 +36,21 @@ defmodule Pleroma.Web.Federator do
end
# Client API
- def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
- ReceiverWorker.new(
+ def incoming_failed_signature_ap_doc(%{
+ method: method,
+ params: params,
+ req_headers: req_headers,
+ request_path: request_path,
+ query_string: query_string
+ }) do
+ SignatureRetryWorker.new(
%{
- "op" => "incoming_ap_doc",
+ "op" => "incoming_failed_signature_ap_doc",
+ "method" => method,
"req_headers" => req_headers,
"params" => params,
+ "request_path" => request_path,
+ "query_string" => query_string,
"timeout" => :timer.seconds(20)
},
priority: 2
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 3b4271227..f047804e2 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -71,12 +71,12 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
end)
end
+ defp voters_count(%{data: %{"votersCount" => voters}}) when is_integer(voters), do: voters
+
defp voters_count(%{data: %{"voters" => [_ | _] = voters}}) do
length(voters)
end
- defp voters_count(%{data: %{"votersCount" => voters}}), do: voters
-
defp voters_count(_), do: 0
defp voted_and_own_votes(%{object: object} = params, options) do
diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
index c6d531086..a688b1780 100644
--- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
+++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
@@ -32,8 +32,8 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
# remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
- Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
- conn
+ Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
+ assign(conn, :valid_signature, false)
end
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index e2c950967..3afbe138d 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -4,40 +4,37 @@
defmodule Pleroma.Workers.ReceiverWorker do
alias Pleroma.Instances
- alias Pleroma.Signature
- alias Pleroma.User
alias Pleroma.Web.Federator
+ alias Pleroma.Workers.SignatureRetryWorker
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
@impl true
+ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params} = args} = job) do
+ if signature_retry_job?(args) do
+ perform_signature_retry(job)
+ else
+ perform_incoming(params)
+ end
+ end
- def perform(%Job{
- args: %{
- "op" => "incoming_ap_doc",
- "method" => method,
- "params" => params,
- "req_headers" => req_headers,
- "request_path" => request_path,
- "query_string" => query_string
- }
- }) do
- # Oban's serialization converts our tuple headers to lists.
- # Revert it for the signature validation.
- req_headers = Enum.into(req_headers, [], &List.to_tuple(&1))
+ def perform(%Job{args: %{"op" => "incoming_ap_doc"} = args} = job) do
+ if signature_retry_job?(args) do
+ perform_signature_retry(job)
+ else
+ process_errors(:missing_incoming_ap_doc_params)
+ end
+ end
- conn_data = %Plug.Conn{
- method: method,
- params: params,
- req_headers: req_headers,
- request_path: request_path,
- query_string: query_string
- }
+ defp perform_signature_retry(%Job{args: args} = job) do
+ SignatureRetryWorker.perform(%Job{
+ job
+ | args: Map.put(args, "op", "incoming_failed_signature_ap_doc")
+ })
+ end
- with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
- {:ok, _public_key} <- Signature.refetch_public_key(conn_data),
- {:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
- {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
+ defp perform_incoming(params) do
+ with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
@@ -49,17 +46,8 @@ defmodule Pleroma.Workers.ReceiverWorker do
end
end
- def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
- with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
- unless Instances.reachable?(params["actor"]) do
- domain = URI.parse(params["actor"]).host
- Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
- end
-
- {:ok, res}
- else
- e -> process_errors(e)
- end
+ defp signature_retry_job?(args) do
+ Enum.any?(~w(method req_headers request_path query_string), &Map.has_key?(args, &1))
end
@impl true
@@ -85,10 +73,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:error, {:reject, _} = reason} -> {:cancel, reason}
# HTTP Sigs
{:signature, false} -> {:cancel, :invalid_signature}
+ {:same_actor, false} -> {:cancel, :actor_signature_mismatch}
# Origin / URL validation failed somewhere possibly due to spoofing
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
# Unclear if this can be reached
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
+ :missing_incoming_ap_doc_params -> {:cancel, :missing_incoming_ap_doc_params}
# Catchall
{:error, _} = e -> e
e -> {:error, e}
diff --git a/lib/pleroma/workers/signature_retry_worker.ex b/lib/pleroma/workers/signature_retry_worker.ex
new file mode 100644
index 000000000..2c4c097dd
--- /dev/null
+++ b/lib/pleroma/workers/signature_retry_worker.ex
@@ -0,0 +1,254 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
$[spin.speed=1s 13:37]
" + + {:ok, activity} = + CommonAPI.post(user, %{ + status: post, + content_type: "text/x.misskeymarkdown" + }) + + object = Object.normalize(activity, fetch: false) + + assert object.data["htmlMfm"] == true + + assert object.data["source"] == %{ + "content" => post, + "mediaType" => "text/x.misskeymarkdown" + } + + assert object.data["content"] =~ ~s(class="mfm-spin") + assert object.data["content"] =~ ~s(data-mfm-speed="1s") + assert object.data["content"] =~ "13:37" + refute object.data["content"] =~ "scrub-this" + end + + test "it falls back safely for malformed MFM" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "$[spin.speed=1s=boom malformed]", + content_type: "text/x.misskeymarkdown" + }) + + object = Object.normalize(activity, fetch: false) + + refute object.data["content"] =~ ~s(class="mfm-spin") + assert object.data["content"] =~ "malformed" + end + test "it does not allow replies to direct messages that are not direct messages themselves" do user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index 16281393d..6cb5934de 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -180,4 +180,179 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do assert result[:pleroma][:non_anonymous] == true end + + test "prefers votersCount over voters list when both are present" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Which flavor?", + poll: %{options: ["chocolate", "vanilla"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + voter = insert(:user) + {:ok, _, object} = CommonAPI.vote(object, voter, [0]) + + assert object.data["votersCount"] == 1 + assert length(object.data["voters"]) == 1 + + object = %{ + object + | data: Map.put(object.data, "votersCount", 42) + } + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 42 + end + + test "falls back to voters list when votersCount is absent" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Which flavor?", + poll: %{options: ["chocolate", "vanilla"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + voter = insert(:user) + {:ok, _, object} = CommonAPI.vote(object, voter, [0]) + + assert length(object.data["voters"]) == 1 + + data = Map.delete(object.data, "votersCount") + object = %{object | data: data} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 1 + end + + test "returns 0 when both votersCount and voters are absent" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Which flavor?", + poll: %{options: ["chocolate", "vanilla"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + data = + object.data + |> Map.delete("votersCount") + |> Map.delete("voters") + + object = %{object | data: data} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 0 + end + + test "returns 0 when voters list is empty" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Which flavor?", + poll: %{options: ["chocolate", "vanilla"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + data = + object.data + |> Map.delete("votersCount") + |> Map.put("voters", []) + + object = %{object | data: data} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 0 + end + + test "does not inflate votersCount when same voter picks multiple options" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Pick several", + poll: %{options: ["a", "b", "c"], expires_in: 20, multiple: true} + }) + + object = Object.normalize(activity, fetch: false) + + voter = insert(:user) + {:ok, _, object} = CommonAPI.vote(object, voter, [0, 2]) + + assert object.data["votersCount"] == 1 + assert length(object.data["voters"]) == 1 + end + + test "preserves votersCount from remote source when existing voter picks another option" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Pick several", + poll: %{options: ["a", "b"], expires_in: 20, multiple: true} + }) + + object = Object.normalize(activity, fetch: false) + + voter = insert(:user) + {:ok, _, object} = CommonAPI.vote(object, voter, [0, 1]) + + object = %{object | data: Map.put(object.data, "votersCount", 14)} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 14 + end + + test "returns 0 when votersCount is explicitly 0" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Pick one", + poll: %{options: ["a", "b"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + object = %{object | data: Map.put(object.data, "votersCount", 0)} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == 0 + end + + test "falls back to voters list when votersCount is nil" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Pick one", + poll: %{options: ["a", "b"], expires_in: 20} + }) + + object = Object.normalize(activity, fetch: false) + + voter = insert(:user) + {:ok, _, object} = CommonAPI.vote(object, voter, [0]) + + object = %{object | data: Map.put(object.data, "votersCount", nil)} + + result = PollView.render("show.json", %{object: object}) + + assert result[:voters_count] == length(object.data["voters"]) + end end diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index 33eff1bc5..df713762c 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -47,13 +47,27 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do assert %{valid_signature: false} == conn.assigns end - @tag skip: "known breakage; the testsuite presently depends on it" test "it considers a mapped identity to be invalid when the identity cannot be found" do + actor = "http://niu.moe/users/rye" + conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://niu.moe/users/rye") + build_conn(:post, "/doesntmattter", %{"actor" => actor}) + |> set_signature(actor) |> MappedSignatureToIdentityPlug.call(%{}) - assert %{valid_signature: false} == conn.assigns + assert conn.assigns.valid_signature == false + refute Map.has_key?(conn.assigns, :user) + end + + test "it considers a mapped identity to be invalid when embedded actor identity cannot be found" do + actor = "http://niu.moe/users/rye" + + conn = + build_conn(:post, "/doesntmattter", %{"actor" => %{"id" => actor}}) + |> set_signature(actor) + |> MappedSignatureToIdentityPlug.call(%{}) + + assert conn.assigns.valid_signature == false + refute Map.has_key?(conn.assigns, :user) end end diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 12abc1a27..ea05f38f1 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -11,9 +11,27 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do alias Pleroma.User alias Pleroma.Web.CommonAPI - alias Pleroma.Web.Federator alias Pleroma.Workers.ReceiverWorker + defp signature_headers_for(%User{} = signer) do + [ + {"host", "local.test"}, + {"date", "Thu, 25 Jul 2024 13:33:31 GMT"}, + {"digest", "SHA-256=fake-digest"}, + {"content-type", "application/activity+json"}, + { + "signature", + "keyId=\"#{signer.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\"" + } + ] + end + + defp perform_incoming(params) do + ReceiverWorker.perform(%Oban.Job{ + args: %{"op" => "incoming_ap_doc", "params" => params} + }) + end + test "it does not retry MRF reject" do params = insert(:note).data @@ -81,16 +99,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do insert(:note_activity).data |> Map.put("actor", "https://springfield.social/users/bart") - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:cancel, {:error, :forbidden}} = ReceiverWorker.perform(oban_job) + assert {:cancel, {:error, :forbidden}} = perform_incoming(params) end test "when request returns a 404" do @@ -98,16 +107,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do insert(:note_activity).data |> Map.put("actor", "https://springfield.social/users/troymcclure") - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job) + assert {:cancel, {:error, :not_found}} = perform_incoming(params) end test "when request returns a 410" do @@ -115,16 +115,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do insert(:note_activity).data |> Map.put("actor", "https://springfield.social/users/hankscorpio") - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job) + assert {:cancel, {:error, :not_found}} = perform_incoming(params) end test "when user account is disabled" do @@ -138,86 +129,16 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do {:ok, %User{}} = User.set_activation(user, false) - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:cancel, {:user_active, false}} = ReceiverWorker.perform(oban_job) + assert {:cancel, {:user_active, false}} = perform_incoming(params) end end - test "it can validate the signature" do - Tesla.Mock.mock(fn - %{url: "https://phpc.social/users/denniskoch"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/denniskoch.json"), - headers: [{"content-type", "application/activity+json"}] - } - - %{url: "https://phpc.social/users/denniskoch/collections/featured"} -> - %Tesla.Env{ - status: 200, - headers: [{"content-type", "application/activity+json"}], - body: - File.read!("test/fixtures/users_mock/masto_featured.json") - |> String.replace("{{domain}}", "phpc.social") - |> String.replace("{{nickname}}", "denniskoch") - } - end) - - params = - File.read!("test/fixtures/receiver_worker_signature_activity.json") |> Jason.decode!() - - req_headers = [ - ["accept-encoding", "gzip"], - ["content-length", "5184"], - ["content-type", "application/activity+json"], - ["date", "Thu, 25 Jul 2024 13:33:31 GMT"], - ["digest", "SHA-256=ouge/6HP2/QryG6F3JNtZ6vzs/hSwMk67xdxe87eH7A="], - ["host", "bikeshed.party"], - [ - "signature", - "keyId=\"https://mastodon.social/users/bastianallgeier#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"ymE3vn5Iw50N6ukSp8oIuXJB5SBjGAGjBasdTDvn+ahZIzq2SIJfmVCsIIzyqIROnhWyQoTbavTclVojEqdaeOx+Ejz2wBnRBmhz5oemJLk4RnnCH0lwMWyzeY98YAvxi9Rq57Gojuv/1lBqyGa+rDzynyJpAMyFk17XIZpjMKuTNMCbjMDy76ILHqArykAIL/v1zxkgwxY/+ELzxqMpNqtZ+kQ29znNMUBB3eVZ/mNAHAz6o33Y9VKxM2jw+08vtuIZOusXyiHbRiaj2g5HtN2WBUw1MzzfRfHF2/yy7rcipobeoyk5RvP5SyHV3WrIeZ3iyoNfmv33y8fxllF0EA==\"" - ], - [ - "user-agent", - "http.rb/5.2.0 (Mastodon/4.3.0-nightly.2024-07-25; +https://mastodon.social/)" - ] - ] - - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: req_headers, - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:ok, %Pleroma.Activity{}} = ReceiverWorker.perform(oban_job) - end - test "cancels due to origin containment" do params = insert(:note_activity).data |> Map.put("id", "https://notorigindomain.com/activity") - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) - - assert {:cancel, :origin_containment_failed} = ReceiverWorker.perform(oban_job) + assert {:cancel, :origin_containment_failed} = perform_incoming(params) end test "canceled due to deleted object" do @@ -233,16 +154,114 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do } end) - {:ok, oban_job} = - Federator.incoming_ap_doc(%{ - method: "POST", - req_headers: [], - request_path: "/inbox", - params: params, - query_string: "" - }) + assert {:cancel, _} = perform_incoming(params) + end - assert {:cancel, _} = ReceiverWorker.perform(oban_job) + test "delegates legacy failed-signature metadata jobs instead of processing them as trusted" do + alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") + bob = insert(:user, local: false, ap_id: "https://two.com/users/bob") + object_id = "https://two.com/objects/legacy-forged-note" + + create = %{ + "type" => "Create", + "actor" => bob.ap_id, + "id" => "https://two.com/activities/legacy-forged-create", + "context" => "https://two.com/contexts/legacy-forged-create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [], + "object" => %{ + "type" => "Note", + "id" => object_id, + "actor" => bob.ap_id, + "attributedTo" => bob.ap_id, + "context" => "https://two.com/contexts/legacy-forged-create", + "content" => "forged post", + "published" => "2024-07-25T13:33:31Z", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [] + } + } + + assert {:cancel, :actor_signature_mismatch} = + ReceiverWorker.perform(%Oban.Job{ + args: %{ + "op" => "incoming_ap_doc", + "method" => "POST", + "params" => create, + "req_headers" => signature_headers_for(alice), + "request_path" => "/inbox", + "query_string" => "" + } + }) + + refute Pleroma.Activity.get_by_ap_id(create["id"]) + refute Pleroma.Object.get_by_ap_id(object_id) + end + + test "fails closed for the old persisted failed-signature job shape" do + alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") + bob = insert(:user, local: false, ap_id: "https://two.com/users/bob") + object_id = "https://two.com/objects/old-shape-forged-note" + + create = %{ + "type" => "Create", + "actor" => bob.ap_id, + "id" => "https://two.com/activities/old-shape-forged-create", + "context" => "https://two.com/contexts/old-shape-forged-create", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [], + "object" => %{ + "type" => "Note", + "id" => object_id, + "actor" => bob.ap_id, + "attributedTo" => bob.ap_id, + "context" => "https://two.com/contexts/old-shape-forged-create", + "content" => "forged post", + "published" => "2024-07-25T13:33:31Z", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [] + } + } + + assert {:cancel, :missing_signature_retry_metadata} = + ReceiverWorker.perform(%Oban.Job{ + args: %{ + "op" => "incoming_ap_doc", + "params" => create, + "req_headers" => signature_headers_for(alice), + "timeout" => 20_000 + } + }) + + refute Pleroma.Activity.get_by_ap_id(create["id"]) + refute Pleroma.Object.get_by_ap_id(object_id) + end + + test "fails closed for legacy retry jobs missing one metadata field" do + alice = insert(:user, local: false, ap_id: "https://one.com/users/alice") + params = insert(:note_activity).data + + assert {:cancel, :missing_signature_retry_metadata} = + ReceiverWorker.perform(%Oban.Job{ + args: %{ + "op" => "incoming_ap_doc", + "method" => "POST", + "params" => params, + "req_headers" => signature_headers_for(alice), + "request_path" => "/inbox" + } + }) + end + + test "fails closed for malformed legacy metadata jobs without params" do + assert {:cancel, :missing_signature_retry_metadata} = + ReceiverWorker.perform(%Oban.Job{ + args: %{ + "op" => "incoming_ap_doc", + "req_headers" => [], + "timeout" => 20_000 + } + }) end describe "Server reachability:" do diff --git a/test/pleroma/workers/signature_retry_worker_test.exs b/test/pleroma/workers/signature_retry_worker_test.exs new file mode 100644 index 000000000..94dd5f6c1 --- /dev/null +++ b/test/pleroma/workers/signature_retry_worker_test.exs @@ -0,0 +1,574 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors