Merge branch 'develop' into paradedb-search-backend

This commit is contained in:
lain 2026-05-13 10:02:20 +00:00
commit 7e97216b52
111 changed files with 3801 additions and 350 deletions

3
.gitignore vendored
View file

@ -56,6 +56,9 @@ pleroma.iml
# asdf # asdf
.tool-versions .tool-versions
# mise
mise.toml
# Editor temp files # Editor temp files
*~ *~
*# *#

View file

@ -0,0 +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

View file

@ -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}

96
.woodpecker/docker.yaml Normal file
View file

@ -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

77
.woodpecker/lint.yaml Normal file
View file

@ -0,0 +1,77 @@
when:
- event: pull_request
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
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:
- |
if ! mix format --check-formatted; then
touch fail.stamp
exit 1
fi
credo:
image: *elixir-image
entrypoint: *script_file_entrypoint
failure: ignore
environment:
MIX_ENV: test
commands:
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
- adduser -D -h /home/testuser testuser
- mkdir -p /home/testuser/.mix /home/testuser/.hex
- chown -R testuser:testuser . /home/testuser
- su testuser -c "HOME=/home/testuser mix local.hex --force"
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
- su testuser -c "HOME=/home/testuser mix deps.get"
- |
if ! su testuser -c "HOME=/home/testuser mix analyze"; then
touch fail.stamp
exit 1
fi
# cycles:
# image: *elixir-image
# failure: ignore
# commands:
# - apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
# - adduser -D -h /home/testuser testuser
# - mkdir -p /home/testuser/.mix /home/testuser/.hex
# - chown -R testuser:testuser . /home/testuser
# - su testuser -c "HOME=/home/testuser mix local.hex --force"
# - su testuser -c "HOME=/home/testuser mix local.rebar --force"
# - su testuser -c "HOME=/home/testuser mix compile"
# - |
# if ! su testuser -c "HOME=/home/testuser mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != \"No cycles found\")}'"; then
# touch fail.stamp
# exit 1
# fi
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..."
exit 1
else
echo "All steps passed"
exit 0
fi

292
.woodpecker/otp-musl.yaml Normal file
View file

@ -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'

293
.woodpecker/otp.yaml Normal file
View file

@ -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'

View file

@ -1,10 +1,26 @@
when: when:
- event: - event: pull_request
- pull_request path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
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: steps:
test: unit-testing-elixir-1.15:
image: elixir:1.15-alpine image: elixir:1.15-alpine
entrypoint: *script_file_entrypoint
environment: environment:
MIX_ENV: test MIX_ENV: test
DB_HOST: postgres DB_HOST: postgres
@ -17,7 +33,7 @@ steps:
- su testuser -c "HOME=/home/testuser mix local.hex --force" - su testuser -c "HOME=/home/testuser mix local.hex --force"
- su testuser -c "HOME=/home/testuser mix local.rebar --force" - su testuser -c "HOME=/home/testuser mix local.rebar --force"
- su testuser -c "HOME=/home/testuser mix deps.get" - su testuser -c "HOME=/home/testuser mix deps.get"
- su testuser -c "HOME=/home/testuser mix test" - su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
services: services:
postgres: postgres:

View file

@ -0,0 +1,44 @@
when:
- event: pull_request
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
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
DB_PORT: 5432
commands:
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
- adduser -D -h /home/testuser testuser
- mkdir -p /home/testuser/.mix /home/testuser/.hex
- chown -R testuser:testuser . /home/testuser
- su testuser -c "HOME=/home/testuser mix local.hex --force"
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
- su testuser -c "HOME=/home/testuser mix deps.get"
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
services:
postgres:
image: postgres:13-alpine
environment:
POSTGRES_DB: pleroma_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

View file

@ -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/). 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 ## 2.10
### Security ### Security

View file

@ -1 +0,0 @@
Allow assigning users to reports

View file

@ -1 +0,0 @@
Move avatar_description and header_description fields to the account object

View file

@ -1 +0,0 @@
Various bookmark folders-related improvements

View file

@ -1 +0,0 @@
Allow fine-grained announce visibilities

View file

@ -1 +0,0 @@
Add immutable tag on cache-control header for several endpoints that's serving the same exact things.

View file

@ -0,0 +1 @@
litepub-0.1.jsonld cleanup

View file

@ -1 +0,0 @@
Add reasonable defaults for :database_config_whitelist

View file

@ -1 +0,0 @@
No-op code correctness improvements detected by Elixir 1.19 compiler

View file

@ -1 +0,0 @@
Fix the daily email digest job which was not executing

View file

@ -1 +0,0 @@
Encode custom emoji URLs in EmojiReact activity tags.

View file

@ -1 +0,0 @@
Support lists `exclusive` param

View file

@ -1 +0,0 @@
Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled

View file

@ -1 +0,0 @@
Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney

View file

@ -1 +0,0 @@
Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation

View file

@ -0,0 +1 @@
Fix compatibility with timestamped HTTP Signatures used by GoToSocial

View file

@ -1 +0,0 @@
Fix fetching Hubzilla Actors with alsoKnownAs as string

View file

@ -0,0 +1 @@
Handle reports with just actor ap id as the object

View file

@ -1 +0,0 @@
Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)

View file

@ -1 +0,0 @@
Add v1/instance/domain_blocks endpoint

View file

@ -1 +0,0 @@
Add /api/v2/instance profile fields limits info used by Mastodon

View file

@ -1 +0,0 @@
Fix /phoenix/live_dashboard redirect not working when user added a path segment

View file

@ -0,0 +1 @@
Add backend support for Misskey Markdown (MFM) posts

View file

@ -1 +0,0 @@
Fix 404 error codes for missing static files

View file

@ -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.

View file

@ -1 +0,0 @@
Added Oban Web dashboard located at /pleroma/oban

View file

@ -1 +0,0 @@
Correct old migrations for expiring activities and user access tokens.

View file

@ -1 +0,0 @@
Paginate follow requests

View file

@ -1 +0,0 @@
Moved Phoenix LiveDashboard to /pleroma/live_dashboard

View file

@ -0,0 +1 @@
Updated Pleroma-FE build URL after Forgejo migration

View file

@ -0,0 +1 @@
Fix votersCount inflation when same voter picks multiple options

View file

@ -1 +0,0 @@
Federate `votersCount` correctly

View file

@ -1 +0,0 @@
DB prune: Check if user follows hashtag with no objects before deletion

View file

@ -1 +0,0 @@
Stop the rate limiter from crashing when run with wrong settings.

View file

@ -1 +0,0 @@
Reduce the number of flaky tests by making them sync if they affect the global state, and silence noisy test output.

View file

@ -0,0 +1 @@
Reject incoming reports when both the reporter and reported account are remote

View file

@ -1 +0,0 @@
Add mute/block expiry to the relationship object

View file

@ -1 +0,0 @@
Add instructions on how to run a release in docker, to make it easier to run on older distros.

View file

@ -1 +0,0 @@
Restore embeds route

View file

@ -1 +0,0 @@
ReverseProxy: Recursively follow redirects until redirect_limit is reached

View file

@ -1 +0,0 @@
Filter indexable activities before inserting indexing jobs into the queue.

View file

@ -1 +0,0 @@
Update comment for prepare_object, rename prepare_outgoing

View file

@ -0,0 +1 @@
Improve user search / autocompletion ordering.

View file

@ -1 +0,0 @@
Avoid code duplication in UserView

View file

@ -1 +0,0 @@
Fix compilation with vips-8.18.0 with bumping to vix 0.36.0

View file

@ -203,7 +203,8 @@ config :pleroma, :instance,
"text/plain", "text/plain",
"text/html", "text/html",
"text/markdown", "text/markdown",
"text/bbcode" "text/bbcode",
"text/x.misskeymarkdown"
], ],
autofollowed_nicknames: [], autofollowed_nicknames: [],
autofollowing_nicknames: [], autofollowing_nicknames: [],
@ -775,7 +776,7 @@ config :pleroma, :frontends,
"name" => "pleroma-fe", "name" => "pleroma-fe",
"git" => "https://git.pleroma.social/pleroma/pleroma-fe", "git" => "https://git.pleroma.social/pleroma/pleroma-fe",
"build_url" => "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" "ref" => "develop"
}, },
"fedi-fe" => %{ "fedi-fe" => %{

View file

@ -815,7 +815,8 @@ config :pleroma, :config_description, [
"text/plain", "text/plain",
"text/html", "text/html",
"text/markdown", "text/markdown",
"text/bbcode" "text/bbcode",
"text/x.misskeymarkdown"
] ]
}, },
%{ %{
@ -1394,7 +1395,13 @@ config :pleroma, :config_description, [
label: "Post Content Type", label: "Post Content Type",
type: {:dropdown, :atom}, type: {:dropdown, :atom},
description: "Default post formatting option", 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, key: :redirectRootNoLogin,

View file

@ -127,6 +127,13 @@ defmodule Pleroma.Formatter do
Earmark.as_html!(text, %Earmark.Options{compact_output: true, smartypants: false}) Earmark.as_html!(text, %Earmark.Options{compact_output: true, smartypants: false})
end 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 def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags} {html_escape(text, type), mentions, hashtags}
end end
@ -135,6 +142,10 @@ defmodule Pleroma.Formatter do
HTML.filter_tags(text) HTML.filter_tags(text)
end end
def html_escape(text, "text/x.misskeymarkdown") do
HTML.filter_tags(text)
end
def html_escape(text, "text/plain") do def html_escape(text, "text/plain") do
Regex.split(@link_regex, text, include_captures: true) Regex.split(@link_regex, text, include_captures: true)
|> Enum.map_every(2, fn chunk -> |> Enum.map_every(2, fn chunk ->

View file

@ -75,8 +75,8 @@ defmodule Pleroma.Frontend do
end end
defp download_build(frontend_info, dest) do 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"]) 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}} <- with {:ok, %{status: 200, body: zip_body}} <-
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do

View file

@ -372,13 +372,21 @@ defmodule Pleroma.Object do
option option
end) 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 = voters_count =
if Map.has_key?(object.data, "votersCount") do cond do
object.data["votersCount"] + 1 is_integer(existing_voters_count) and new_voter? ->
else existing_voters_count + 1
length(voters)
is_integer(existing_voters_count) ->
existing_voters_count
true ->
length(voters)
end end
data = data =

View file

@ -5,7 +5,10 @@
defmodule Pleroma.ReleaseTasks do defmodule Pleroma.ReleaseTasks do
@repo Pleroma.Repo @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) [task | args] = String.split(args)
case task do case task do
@ -16,6 +19,20 @@ defmodule Pleroma.ReleaseTasks do
end end
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 def find_module(task) do
module_name = module_name =
task task

View file

@ -104,7 +104,7 @@ defmodule Pleroma.Signature do
|> put_req_header("(request-target)", request_target) |> put_req_header("(request-target)", request_target)
|> put_req_header("@request-target", request_target) |> put_req_header("@request-target", request_target)
@http_signatures_impl.validate_conn(conn) @http_signatures_impl.validate_conn(conn) == true
end end
@spec validate_signature(Plug.Conn.t()) :: boolean() @spec validate_signature(Plug.Conn.t()) :: boolean()

View file

@ -4,6 +4,7 @@
defmodule Pleroma.User.Search do defmodule Pleroma.User.Search do
alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType
alias Pleroma.Instances.Instance
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.User alias Pleroma.User
@ -88,12 +89,13 @@ defmodule Pleroma.User.Search do
|> filter_invisible_users() |> filter_invisible_users()
|> filter_internal_users() |> filter_internal_users()
|> filter_blocked_domains(for_user) |> filter_blocked_domains(for_user)
|> filter_unreachable_users()
|> fts_search(query_string) |> fts_search(query_string)
|> select_top_users(top_user_ids) |> select_top_users(top_user_ids)
|> trigram_rank(query_string) |> trigram_rank(query_string)
|> boost_search_rank(for_user, top_user_ids) |> boost_search_rank(for_user, top_user_ids)
|> subquery() |> subquery()
|> order_by(desc: :search_rank) |> order_by_search_rank(for_user)
|> maybe_restrict_local(for_user) |> maybe_restrict_local(for_user)
|> maybe_restrict_accepting_chat_messages(capabilities) |> maybe_restrict_accepting_chat_messages(capabilities)
|> filter_deactivated_users() |> filter_deactivated_users()
@ -196,6 +198,14 @@ defmodule Pleroma.User.Search do
defp filter_blocked_domains(query, _), do: query defp filter_blocked_domains(query, _), do: query
defp filter_unreachable_users(query) do
from(u in query,
left_join: i in Instance,
on: i.host == fragment("substring(? from '.*://([^/]*)')", u.ap_id),
where: is_nil(i.unreachable_since)
)
end
defp maybe_resolve(true, user, query) do defp maybe_resolve(true, user, query) do
case {limit(), user} do case {limit(), user} do
{:all, _} -> :noop {:all, _} -> :noop
@ -236,6 +246,16 @@ defmodule Pleroma.User.Search do
from(u in subquery(query), from(u in subquery(query),
select_merge: %{ select_merge: %{
search_type:
fragment(
"""
CASE WHEN (?) THEN 2
WHEN (?) THEN 1
ELSE 0 END
""",
u.id in ^top_user_ids,
u.id in ^friends_ids or u.id in ^followers_ids
),
search_rank: search_rank:
fragment( fragment(
""" """
@ -261,6 +281,14 @@ defmodule Pleroma.User.Search do
defp boost_search_rank(query, _for_user, top_user_ids) do defp boost_search_rank(query, _for_user, top_user_ids) do
from(u in subquery(query), from(u in subquery(query),
select_merge: %{ select_merge: %{
search_type:
fragment(
"""
CASE WHEN (?) THEN 2
ELSE 0 END
""",
u.id in ^top_user_ids
),
search_rank: search_rank:
fragment( fragment(
""" """
@ -273,4 +301,22 @@ defmodule Pleroma.User.Search do
} }
) )
end end
defp order_by_search_rank(query, %User{}) do
order_by(
query,
[u],
desc: u.search_type,
desc_nulls_last:
fragment(
"CASE WHEN ? = 1 THEN COALESCE(?, ?) ELSE NULL END",
u.search_type,
u.last_status_at,
u.last_active_at
),
desc: u.search_rank
)
end
defp order_by_search_rank(query, _), do: order_by(query, desc: :search_rank)
end end

View file

@ -1677,44 +1677,80 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
show_birthday = !!birthday show_birthday = !!birthday
# if WebFinger request was already done, we probably have acct, otherwise with {:ok, nickname} <- nickname_from_actor(data, additional) do
# we request WebFinger here {:ok,
nickname = additional[:nickname_from_acct] || generate_nickname(data) %{
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
%{ defp nickname_from_actor(data, additional) do
ap_id: data["id"], generated = generated_nickname(data)
uri: get_actor_url(data["url"]),
banner: normalize_image(data["image"]), case additional[:nickname_from_acct] do
fields: fields, ^generated when is_binary(generated) ->
emoji: emojis, {:ok, generated}
is_locked: is_locked,
is_discoverable: is_discoverable, acct when is_binary(acct) ->
invisible: invisible, with ^acct <- webfinger_nickname(data) do
avatar: normalize_image(data["icon"]), {:ok, acct}
name: data["name"], else
follower_address: data["followers"], _ -> {:error, {:webfinger_actor_mismatch, acct, data["id"]}}
following_address: data["following"], end
featured_address: featured_address,
bio: data["summary"] || "", _ ->
actor_type: actor_type, {:ok, generate_nickname(data)}
also_known_as: normalize_also_known_as(data["alsoKnownAs"]), end
public_key: public_key, end
inbox: data["inbox"],
shared_inbox: shared_inbox, defp generated_nickname(%{"preferredUsername" => username, "id" => ap_id})
accepts_chat_messages: accepts_chat_messages, when is_binary(username) and is_binary(ap_id) do
birthday: birthday, case URI.parse(ap_id) do
show_birthday: show_birthday, %URI{host: host} when is_binary(host) -> "#{username}@#{host}"
pinned_objects: pinned_objects, _ -> nil
nickname: nickname 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 end
defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do 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 if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
case WebFinger.finger(generated) do case webfinger_nickname(data) do
{:ok, %{"subject" => "acct:" <> acct}} -> acct acct when is_binary(acct) -> acct
_ -> generated _ -> generated
end end
else else
@ -1794,9 +1830,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp collection_private(_data), do: {:ok, true} defp collection_private(_data), do: {:ok, true}
def user_data_from_user_object(data, additional \\ []) do def user_data_from_user_object(data, additional \\ []) do
with {:ok, data} <- MRF.filter(data) do with {:ok, data} <- MRF.filter(data),
{:ok, object_to_user_data(data, additional)} {:ok, data} <- object_to_user_data(data, additional) do
{:ok, data}
else else
{:error, _} = e -> e
e -> {:error, e} e -> {:error, e}
end end
end end

View file

@ -348,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end end
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
Federator.incoming_ap_doc(%{ Federator.incoming_failed_signature_ap_doc(%{
method: conn.method, method: conn.method,
req_headers: conn.req_headers, req_headers: conn.req_headers,
request_path: conn.request_path, request_path: conn.request_path,

View file

@ -6,9 +6,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI.Utils
import Ecto.Changeset import Ecto.Changeset
@ -26,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
end end
field(:replies, {:array, ObjectValidators.ObjectID}, default: []) field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
field(:source, :map)
end end
def cast_and_apply(data) do def cast_and_apply(data) do
@ -80,6 +84,113 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
def fix_attachments(data), do: data 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 defp fix(data) do
data data
|> CommonFixes.fix_actor() |> CommonFixes.fix_actor()
@ -88,6 +199,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|> fix_tag() |> fix_tag()
|> fix_replies() |> fix_replies()
|> fix_attachments() |> fix_attachments()
|> normalize_source()
|> fix_misskey_content()
|> CommonFixes.fix_quote_url() |> CommonFixes.fix_quote_url()
|> CommonFixes.fix_likes() |> CommonFixes.fix_likes()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()

View file

@ -32,6 +32,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
quote bind_quoted: binding() do quote bind_quoted: binding() do
field(:content, :string) field(:content, :string)
field(:contentMap, ObjectValidators.ContentLanguageMap) field(:contentMap, ObjectValidators.ContentLanguageMap)
field(:htmlMfm, :boolean)
field(:published, ObjectValidators.DateTime) field(:published, ObjectValidators.DateTime)
field(:updated, ObjectValidators.DateTime) field(:updated, ObjectValidators.DateTime)

View file

@ -75,15 +75,40 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
end end
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 def validate_updating_rights_remote(cng) do
with actor = get_field(cng, :actor), with actor = get_field(cng, :actor),
object = get_field(cng, :object), object = get_field(cng, :object),
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object), {:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
actor_uri <- URI.parse(actor), entity <-
object_uri <- URI.parse(object_id), Object.normalize(object_id, fetch: false) || User.get_cached_by_ap_id(object_id) do
true <- actor_uri.host == object_uri.host do case entity do
cng # 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 else
_e -> _e ->
cng cng

View file

@ -430,6 +430,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end) end)
end end
defp reject_third_party_report(%User{local: false}, %User{local: false} = account) do
{:reject, "[Transmogrifier] third-party report: #{account.ap_id}"}
end
defp reject_third_party_report(_, _), do: :ok
def handle_incoming(data, options \\ []) do def handle_incoming(data, options \\ []) do
data data
|> fix_recursive(&strip_internal_fields/1) |> fix_recursive(&strip_internal_fields/1)
@ -444,9 +450,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
) do ) do
with context <- data["context"] || Utils.generate_context_id(), with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "", content <- data["content"] || "",
objects <- List.wrap(objects),
%User{} = actor <- User.get_cached_by_ap_id(actor), %User{} = actor <- User.get_cached_by_ap_id(actor),
# Reduce the object list to find the reported user. # Reduce the object list to find the reported user.
%User{} = account <- get_reported(objects), %User{} = account <- get_reported(objects),
:ok <- reject_third_party_report(actor, account),
# Remove the reported user from the object list. # Remove the reported user from the object list.
statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
%{ %{

View file

@ -120,7 +120,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"#{Endpoint.url()}/schemas/litepub-0.1.jsonld", "#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
%{ %{
"@language" => get_language(data) "@language" => get_language(data),
"htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm"
} }
] ]
} }

View file

@ -172,7 +172,10 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
type: :object, type: :object,
properties: %{ properties: %{
title: %Schema{type: :string, description: "List title"}, title: %Schema{type: :string, description: "List title"},
exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"} exclusive: %Schema{
type: :boolean,
description: "Whether members of the list should be removed from the “Home” feed"
}
}, },
required: [:title] required: [:title]
}, },
@ -188,7 +191,10 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
type: :object, type: :object,
properties: %{ properties: %{
title: %Schema{type: :string, description: "List title"}, title: %Schema{type: :string, description: "List title"},
exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"} exclusive: %Schema{
type: :boolean,
description: "Whether members of the list should be removed from the “Home” feed"
}
} }
}, },
required: true required: true

View file

@ -317,6 +317,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
emoji = Map.merge(emoji, summary_emoji) emoji = Map.merge(emoji, summary_emoji)
media_type = Utils.get_content_type(draft.params[:content_type])
{:ok, note_data, _meta} = Builder.note(draft) {:ok, note_data, _meta} = Builder.note(draft)
object = object =
@ -324,14 +325,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> Map.put("emoji", emoji) |> Map.put("emoji", emoji)
|> Map.put("source", %{ |> Map.put("source", %{
"content" => draft.status, "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("generator", draft.params[:generator])
|> Map.put("language", draft.language) |> Map.put("language", draft.language)
%{draft | object: object} %{draft | object: object}
end 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 defp preview?(%__MODULE__{} = draft) do
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview]) preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
%{draft | preview?: preview?} %{draft | preview?: preview?}

View file

@ -322,6 +322,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.linkify(options) |> Formatter.linkify(options)
end 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 def format_input(text, "text/markdown", options) do
text text
|> Formatter.mentions_escape(options) |> Formatter.mentions_escape(options)
@ -330,6 +338,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
end 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 def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end end

View file

@ -48,7 +48,8 @@ defmodule Pleroma.Web.Endpoint do
@static_cache_control "public, max-age=1209600, immutable" @static_cache_control "public, max-age=1209600, immutable"
@static_cache_disabled "public, no-cache" @static_cache_disabled "public, no-cache"
@favicon_cache_control "public, max=age=86400, immutable" # cache for a day # cache for a day
@favicon_cache_control "public, max=age=86400, immutable"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Workers.PublisherWorker alias Pleroma.Workers.PublisherWorker
alias Pleroma.Workers.ReceiverWorker alias Pleroma.Workers.ReceiverWorker
alias Pleroma.Workers.SignatureRetryWorker
require Logger require Logger
@ -35,12 +36,21 @@ defmodule Pleroma.Web.Federator do
end end
# Client API # Client API
def incoming_ap_doc(%{params: params, req_headers: req_headers}) do def incoming_failed_signature_ap_doc(%{
ReceiverWorker.new( 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, "req_headers" => req_headers,
"params" => params, "params" => params,
"request_path" => request_path,
"query_string" => query_string,
"timeout" => :timer.seconds(20) "timeout" => :timer.seconds(20)
}, },
priority: 2 priority: 2

View file

@ -71,12 +71,12 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
end) end)
end end
defp voters_count(%{data: %{"votersCount" => voters}}) when is_integer(voters), do: voters
defp voters_count(%{data: %{"voters" => [_ | _] = voters}}) do defp voters_count(%{data: %{"voters" => [_ | _] = voters}}) do
length(voters) length(voters)
end end
defp voters_count(%{data: %{"votersCount" => voters}}), do: voters
defp voters_count(_), do: 0 defp voters_count(_), do: 0
defp voted_and_own_votes(%{object: object} = params, options) do defp voted_and_own_votes(%{object: object} = params, options) do

View file

@ -32,8 +32,8 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
# remove me once testsuite uses mapped capabilities instead of what we do now # remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} -> {:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)") Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}") Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
conn assign(conn, :valid_signature, false)
end end
end end

View file

@ -4,40 +4,37 @@
defmodule Pleroma.Workers.ReceiverWorker do defmodule Pleroma.Workers.ReceiverWorker do
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Workers.SignatureRetryWorker
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity] use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
@impl true @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{ def perform(%Job{args: %{"op" => "incoming_ap_doc"} = args} = job) do
args: %{ if signature_retry_job?(args) do
"op" => "incoming_ap_doc", perform_signature_retry(job)
"method" => method, else
"params" => params, process_errors(:missing_incoming_ap_doc_params)
"req_headers" => req_headers, end
"request_path" => request_path, end
"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))
conn_data = %Plug.Conn{ defp perform_signature_retry(%Job{args: args} = job) do
method: method, SignatureRetryWorker.perform(%Job{
params: params, job
req_headers: req_headers, | args: Map.put(args, "op", "incoming_failed_signature_ap_doc")
request_path: request_path, })
query_string: query_string end
}
with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), defp perform_incoming(params) do
{:ok, _public_key} <- Signature.refetch_public_key(conn_data), with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})) Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
@ -49,17 +46,8 @@ defmodule Pleroma.Workers.ReceiverWorker do
end end
end end
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do defp signature_retry_job?(args) do
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do Enum.any?(~w(method req_headers request_path query_string), &Map.has_key?(args, &1))
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
end end
@impl true @impl true
@ -85,10 +73,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:error, {:reject, _} = reason} -> {:cancel, reason} {:error, {:reject, _} = reason} -> {:cancel, reason}
# HTTP Sigs # HTTP Sigs
{:signature, false} -> {:cancel, :invalid_signature} {:signature, false} -> {:cancel, :invalid_signature}
{:same_actor, false} -> {:cancel, :actor_signature_mismatch}
# Origin / URL validation failed somewhere possibly due to spoofing # Origin / URL validation failed somewhere possibly due to spoofing
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
# Unclear if this can be reached # Unclear if this can be reached
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason} {:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
:missing_incoming_ap_doc_params -> {:cancel, :missing_incoming_ap_doc_params}
# Catchall # Catchall
{:error, _} = e -> e {:error, _} = e -> e
e -> {:error, e} e -> {:error, e}

View file

@ -0,0 +1,254 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.SignatureRetryWorker do
alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator
alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug
require Logger
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
@impl true
def perform(%Job{
args: %{
"op" => "incoming_failed_signature_ap_doc",
"method" => method,
"params" => params,
"req_headers" => req_headers,
"request_path" => request_path,
"query_string" => query_string
}
})
when is_binary(method) and is_map(params) and is_list(req_headers) and
is_binary(request_path) and is_binary(query_string) do
case normalize_req_headers(req_headers) do
{:ok, req_headers} ->
conn_data = %Plug.Conn{
assigns: %{valid_signature: true},
method: method,
params: params,
req_headers: req_headers,
request_path: request_path,
query_string: query_string
}
signature_actor_result = signature_actor_id(conn_data)
with actor_id = Utils.get_ap_id(params["actor"]),
{:signature_actor, {:ok, signature_actor_id}} <-
{:signature_actor, signature_actor_result},
{:same_actor, true} <- {:same_actor, signature_actor_id == actor_id},
{:ok, %User{}} <- User.get_or_fetch_by_ap_id(actor_id),
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, validate_signature(conn_data)},
{:same_actor, true} <- {:same_actor, validate_same_actor(conn_data)},
{: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, retry_log_context(params, request_path, signature_actor_result))
end
e ->
process_errors(e, retry_log_context(params, request_path, nil))
end
end
def perform(%Job{args: %{"op" => "incoming_failed_signature_ap_doc"} = args}) do
process_errors(
:missing_signature_retry_metadata,
retry_log_context(Map.get(args, "params"), Map.get(args, "request_path"), nil)
)
end
def perform(%Job{args: args}) when is_map(args) do
process_errors(
:missing_signature_retry_metadata,
retry_log_context(Map.get(args, "params"), Map.get(args, "request_path"), nil)
)
end
def perform(%Job{}), do: process_errors(:missing_signature_retry_metadata)
@impl true
def timeout(%_{args: %{"timeout" => timeout}}), do: timeout
def timeout(_job), do: :timer.seconds(5)
defp normalize_req_headers(req_headers) do
req_headers
|> Enum.reduce_while({:ok, []}, fn
{key, value}, {:ok, acc} when is_binary(key) and is_binary(value) ->
{:cont, {:ok, [{key, value} | acc]}}
[key, value], {:ok, acc} when is_binary(key) and is_binary(value) ->
{:cont, {:ok, [{key, value} | acc]}}
_, _ ->
{:halt, {:error, :invalid_signature_retry_metadata}}
end)
|> case do
{:ok, headers} -> {:ok, Enum.reverse(headers)}
error -> error
end
end
defp validate_same_actor(conn_data) do
case MappedSignatureToIdentityPlug.call(conn_data, []) do
%Plug.Conn{assigns: %{valid_signature: true}} ->
true
_ ->
false
end
end
defp validate_signature(conn_data) do
Signature.validate_signature(conn_data)
rescue
_ -> false
catch
_, _ -> false
end
defp signature_actor_id(conn_data) do
Signature.get_actor_id(conn_data)
rescue
_ -> {:error, :invalid_signature}
catch
_, _ -> {:error, :invalid_signature}
end
defp process_errors(errors, context \\ %{})
defp process_errors({:error, {:error, _} = error}, context), do: process_errors(error, context)
defp process_errors(errors, context) do
result =
case errors do
# User fetch failures
{:error, :not_found} = reason ->
{:cancel, reason}
{:error, :forbidden} = reason ->
{:cancel, reason}
# Inactive user
{:error, {:user_active, false} = reason} ->
{:cancel, reason}
# Validator will error and return a changeset error
# e.g., duplicate activities or if the object was deleted
{:error, {:validate, {:error, _changeset} = reason}} ->
{:cancel, reason}
# Duplicate detection during Normalization
{:error, :already_present} ->
{:cancel, :already_present}
# MRFs will return a reject
{:error, {:reject, _} = reason} ->
{:cancel, reason}
# HTTP Sigs
{:signature_actor, {:error, _}} ->
{:cancel, :invalid_signature}
{: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}
# Fail closed if the retry cannot reconstruct the original request.
:missing_signature_retry_metadata ->
{:cancel, :missing_signature_retry_metadata}
{:error, :invalid_signature_retry_metadata} ->
{:cancel, :invalid_signature_retry_metadata}
# Catchall
{:error, _} = e ->
e
e ->
{:error, e}
end
log_signature_retry_rejection(result, context)
result
end
defp retry_log_context(params, request_path, signature_actor_result) when is_map(params) do
signature_actor =
case signature_actor_result do
{:ok, actor} when is_binary(actor) -> actor
actor when is_binary(actor) -> actor
_ -> nil
end
%{
activity_id: params["id"],
payload_actor: Utils.get_ap_id(params["actor"]),
request_path: request_path,
signature_actor: signature_actor,
type: params["type"]
}
end
defp retry_log_context(_params, request_path, signature_actor_result) do
signature_actor =
case signature_actor_result do
{:ok, actor} when is_binary(actor) -> actor
actor when is_binary(actor) -> actor
_ -> nil
end
%{
activity_id: nil,
payload_actor: nil,
request_path: request_path,
signature_actor: signature_actor,
type: nil
}
end
defp log_signature_retry_rejection({:cancel, reason}, context)
when reason in [
:actor_signature_mismatch,
:invalid_signature,
:invalid_signature_retry_metadata,
:missing_signature_retry_metadata,
:origin_containment_failed
] do
Logger.warning(
"Failed-signature inbox retry rejected " <>
"reason=#{inspect(reason)} " <>
"payload_actor=#{inspect(context[:payload_actor])} " <>
"signature_actor=#{inspect(context[:signature_actor])} " <>
"activity_id=#{inspect(context[:activity_id])} " <>
"type=#{inspect(context[:type])} " <>
"request_path=#{inspect(context[:request_path])}"
)
end
defp log_signature_retry_rejection(_result, _context), do: :ok
end

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do def project do
[ [
app: :pleroma, app: :pleroma,
version: version("2.10.0"), version: version("2.10.2"),
elixir: "~> 1.15", elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(), compilers: Mix.compilers(),
@ -160,6 +160,7 @@ defmodule Pleroma.Mixfile do
{:sweet_xml, "~> 0.7.5"}, {:sweet_xml, "~> 0.7.5"},
{:earmark, "1.4.46"}, {:earmark, "1.4.46"},
{:bbcode_pleroma, "~> 0.2.0"}, {:bbcode_pleroma, "~> 0.2.0"},
{:pleroma_mfm_parser, "~> 0.2.1"},
{:cors_plug, "~> 2.0"}, {:cors_plug, "~> 2.0"},
{:web_push_encryption, "~> 0.3.1"}, {:web_push_encryption, "~> 0.3.1"},
{:swoosh, "~> 1.16.12"}, {:swoosh, "~> 1.16.12"},
@ -172,7 +173,7 @@ defmodule Pleroma.Mixfile do
{:timex, "~> 3.7"}, {:timex, "~> 3.7"},
{:ueberauth, "~> 0.10"}, {:ueberauth, "~> 0.10"},
{:linkify, "~> 0.5.3"}, {:linkify, "~> 0.5.3"},
{:http_signatures, "~> 0.1.2"}, {:http_signatures, "~> 0.1.3"},
{:telemetry, "~> 1.0.0", override: true}, {:telemetry, "~> 1.0.0", override: true},
{:poolboy, "~> 1.5"}, {:poolboy, "~> 1.5"},
{:prom_ex, "~> 1.9"}, {:prom_ex, "~> 1.9"},
@ -197,7 +198,7 @@ defmodule Pleroma.Mixfile do
{:elixir_make, "~> 0.7.8", override: true}, {:elixir_make, "~> 0.7.8", override: true},
{:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash}, {:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash},
{:exile, "~> 0.10.0"}, {:exile, "~> 0.10.0"},
{:bandit, "~> 1.5.7"}, {:bandit, "~> 1.10"},
{:websock_adapter, "~> 0.5.8"}, {:websock_adapter, "~> 0.5.8"},
{:oban_live_dashboard, "~> 0.1.1"}, {:oban_live_dashboard, "~> 0.1.1"},
{:multipart, "~> 0.4.0", optional: true}, {:multipart, "~> 0.4.0", optional: true},
@ -211,7 +212,7 @@ defmodule Pleroma.Mixfile do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.9", only: :test}, {:mock, "~> 0.3.9", only: :test},
{:covertool, "~> 2.0", only: :test}, {:covertool, "~> 2.0", only: :test},
{:hackney, "~> 1.25.0", override: true}, {:hackney, "~> 1.20.1", override: true},
{:mox, "~> 1.2", only: :test}, {:mox, "~> 1.2", only: :test},
{:websockex, "~> 0.4.3", only: :test}, {:websockex, "~> 0.4.3", only: :test},
{:benchee, "~> 1.4", only: :benchmark}, {:benchee, "~> 1.4", only: :benchmark},

View file

@ -1,7 +1,7 @@
%{ %{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
"argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"}, "argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"},
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.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", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"}, "bandit": {:hex, :bandit, "1.10.4", "02b9734c67c5916a008e7eb7e2ba68aaea6f8177094a5f8d95f1fb99069aac17", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "a5faf501042ac1f31d736d9d4a813b3db4ef812e634583b6a457b0928798a51d"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "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"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
@ -13,7 +13,7 @@
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e7b7cc34cc16b383461b966484c297e4ec9aeef6", [ref: "e7b7cc34cc16b383461b966484c297e4ec9aeef6"]},
"castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"},
@ -59,10 +59,10 @@
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
"gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
"hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"}, "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
@ -80,7 +80,7 @@
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "mimerl": {:hex, :mimerl, "1.5.0", "f35aca6f23242339b3666e0ac0702379e362b469d0aea167f6cc713547e777ed", [:rebar3], [], "hexpm", "db648ce065bae14ea84ca8b5dd123f42f49417cef693541110bf6f9e9be9ecc4"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"}, "mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
@ -112,6 +112,7 @@
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"pleroma_mfm_parser": {:hex, :pleroma_mfm_parser, "0.2.1", "87a40fec4e2e035447df34e1c98e1b816b6a448d49baea73eb2273531ac5ce66", [:mix], [], "hexpm", "ec4845461da95d63ab3d592086d65114c45057a1f2d48d768f3def45b0d7696f"},
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
@ -128,6 +129,7 @@
"prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]}, "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"},
"quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"},
"quic": {:hex, :quic, "0.10.2", "4b390507a85f65ce47808f3df1a864e0baf9adb7a1b4ea9f4dcd66fe9d0cb166", [:rebar3], [], "hexpm", "7c196a66973c877a59768a5687f0a0610ff11817254d0a4e45cc4e3a16b1d00b"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
@ -144,7 +146,7 @@
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
"tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"}, "tesla": {:hex, :tesla, "1.15.3", "3a2b5c37f09629b8dcf5d028fbafc9143c0099753559d7fe567eaabfbd9b8663", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "98bb3d4558abc67b92fb7be4cd31bb57ca8d80792de26870d362974b58caeda7"},
"thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"}, "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},

View file

@ -82,12 +82,50 @@ defmodule Pleroma.HTML.Scrubber.Default do
"recipients-inline", "recipients-inline",
"quote-inline", "quote-inline",
"invisible", "invisible",
"ellipsis" "ellipsis",
"mfm-center",
"mfm-flip",
"mfm-font",
"mfm-blur",
"mfm-rotate",
"mfm-x2",
"mfm-x3",
"mfm-x4",
"mfm-position",
"mfm-scale",
"mfm-fg",
"mfm-bg",
"mfm-jelly",
"mfm-twitch",
"mfm-shake",
"mfm-spin",
"mfm-jump",
"mfm-bounce",
"mfm-rainbow",
"mfm-tada",
"mfm-sparkle"
]) ])
Meta.allow_tag_with_this_attribute_values(:p, "class", ["quote-inline"]) Meta.allow_tag_with_this_attribute_values(:p, "class", ["quote-inline"])
Meta.allow_tag_with_these_attributes(:span, ["lang"]) Meta.allow_tag_with_these_attributes(:span, [
"lang",
"data-mfm-h",
"data-mfm-v",
"data-mfm-x",
"data-mfm-y",
"data-mfm-alternate",
"data-mfm-speed",
"data-mfm-deg",
"data-mfm-left",
"data-mfm-serif",
"data-mfm-monospace",
"data-mfm-cursive",
"data-mfm-fantasy",
"data-mfm-emoji",
"data-mfm-math",
"data-mfm-color"
])
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])

View file

@ -4,46 +4,55 @@
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
"https://purl.archive.org/socialweb/webfinger", "https://purl.archive.org/socialweb/webfinger",
{ {
"Emoji": "toot:Emoji", "as": "https://www.w3.org/ns/activitystreams#",
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org#",
"vcard": "http://www.w3.org/2006/vcard/ns#",
"fedibird": "http://fedibird.com/ns#",
"litepub": "http://litepub.social/ns#",
"sm": "http://smithereen.software/ns#",
"toot": "http://joinmastodon.org/ns#",
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"Hashtag": "as:Hashtag", "Hashtag": "as:Hashtag",
"PropertyValue": "schema:PropertyValue", "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"quoteUrl": "as:quoteUrl",
"sensitive": "as:sensitive",
"atomUri": "ostatus:atomUri", "atomUri": "ostatus:atomUri",
"conversation": { "conversation": {
"@id": "ostatus:conversation", "@id": "ostatus:conversation",
"@type": "@id" "@type": "@id"
}, },
"discoverable": "toot:discoverable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers", "PropertyValue": "schema:PropertyValue",
"capabilities": "litepub:capabilities",
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
"fedibird": "http://fedibird.com/ns#",
"value": "schema:value", "value": "schema:value",
"sensitive": "as:sensitive",
"litepub": "http://litepub.social/ns#", "quoteUri": "fedibird:quoteUri",
"invisible": "litepub:invisible",
"capabilities": "litepub:capabilities",
"ChatMessage": "litepub:ChatMessage",
"directMessage": "litepub:directMessage", "directMessage": "litepub:directMessage",
"EmojiReact": "litepub:EmojiReact",
"formerRepresentations": "litepub:formerRepresentations",
"invisible": "litepub:invisible",
"listMessage": { "listMessage": {
"@id": "litepub:listMessage", "@id": "litepub:listMessage",
"@type": "@id" "@type": "@id"
}, },
"quoteUrl": "as:quoteUrl",
"quoteUri": "fedibird:quoteUri",
"oauthRegistrationEndpoint": { "oauthRegistrationEndpoint": {
"@id": "litepub:oauthRegistrationEndpoint", "@id": "litepub:oauthRegistrationEndpoint",
"@type": "@id" "@type": "@id"
}, },
"EmojiReact": "litepub:EmojiReact",
"ChatMessage": "litepub:ChatMessage", "nonAnonymous": "sm:nonAnonymous",
"alsoKnownAs": {
"@id": "as:alsoKnownAs", "discoverable": "toot:discoverable",
"@type": "@id" "Emoji": "toot:Emoji"
},
"vcard": "http://www.w3.org/2006/vcard/ns#",
"formerRepresentations": "litepub:formerRepresentations",
"sm": "http://smithereen.software/ns#",
"nonAnonymous": "sm:nonAnonymous"
} }
] ]
} }

View file

@ -78,14 +78,19 @@ update() {
RELEASE_ROOT=$(dirname "$SCRIPTPATH") RELEASE_ROOT=$(dirname "$SCRIPTPATH")
uri="https://git.pleroma.social" uri="https://git.pleroma.social"
project_id="2" project_name="pleroma"
project_branch="${BRANCH:-$(detect_branch)}" package_base="${uri}/api/packages/${project_name}/generic"
flavour="${FLAVOUR:-$(detect_flavour)}" if [ -n "$FULL_URI" ]; then
full_uri="$FULL_URI"
else
project_branch="${BRANCH:-$(detect_branch)}"
flavour="${FLAVOUR:-$(detect_flavour)}"
full_uri="${package_base}"/pleroma-otp-"${project_branch}"-"${flavour}"/latest/pleroma.zip
fi
tmp="${TMP_DIR:-/tmp}" tmp="${TMP_DIR:-/tmp}"
artifact="$tmp/pleroma.zip" artifact="$tmp/pleroma.zip"
full_uri="${FULL_URI:-${uri}/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=${flavour}}"
echo "Downloading the artifact from ${full_uri} to ${artifact}" echo "Downloading the artifact from ${full_uri} to ${artifact}"
curl "$full_uri" -o "${artifact}" curl -fL "$full_uri" -o "${artifact}"
echo "Unpacking ${artifact} to ${tmp}" echo "Unpacking ${artifact} to ${tmp}"
unzip -q "$artifact" -d "$tmp" unzip -q "$artifact" -d "$tmp"
echo "Copying files over to $RELEASE_ROOT" echo "Copying files over to $RELEASE_ROOT"
@ -137,7 +142,14 @@ else
SCRIPT=$(realpath "$0") SCRIPT=$(realpath "$0")
SCRIPTPATH=$(dirname "$SCRIPT") SCRIPTPATH=$(dirname "$SCRIPT")
FULL_ARGS="$*" # HACK: Script arguments need to be sent as an array to Mix tasks, otherwise they will break (quoted) arguments with whitespace.
# Previously it was sent as string, which would get split on whitespace on the task side.
# Encode as Elixir binary literals to avoid string escaping and interpolation issues.
PREPARED_ARGS=""
for arg in "$@"; do
bytes=$(printf '%s' "$arg" | od -An -v -tu1 | tr -s '[:space:]' ',' | sed 's/^,//; s/,$//')
PREPARED_ARGS="$PREPARED_ARGS <<$bytes>>,"
done
ACTION="$1" ACTION="$1"
if [ $# -gt 0 ]; then if [ $# -gt 0 ]; then
@ -154,8 +166,8 @@ else
if [ "$ACTION" = "update" ]; then if [ "$ACTION" = "update" ]; then
update "$@" update "$@"
elif [ "$ACTION" = "migrate" ] || [ "$ACTION" = "rollback" ] || [ "$ACTION" = "create" ] || [ "$ACTION $SUBACTION" = "instance gen" ] || [ "$PLEROMA_CTL_RPC_DISABLED" = true ]; then elif [ "$ACTION" = "migrate" ] || [ "$ACTION" = "rollback" ] || [ "$ACTION" = "create" ] || [ "$ACTION $SUBACTION" = "instance gen" ] || [ "$PLEROMA_CTL_RPC_DISABLED" = true ]; then
"$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run("'"$FULL_ARGS"'")' "$SCRIPTPATH"/pleroma eval 'Pleroma.ReleaseTasks.run(['"${PREPARED_ARGS%%,}"'])'
else else
"$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run("'"$FULL_ARGS"'")' "$SCRIPTPATH"/pleroma rpc 'Pleroma.ReleaseTasks.run(['"${PREPARED_ARGS%%,}"'])'
fi fi
fi fi

View file

@ -144,7 +144,13 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
quarantined_instances: [], quarantined_instances: [],
managed_config: true, managed_config: true,
static_dir: "instance/static/", static_dir: "instance/static/",
allowed_post_formats: ["text/plain", "text/html", "text/markdown", "text/bbcode"], allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown",
"text/bbcode",
"text/x.misskeymarkdown"
],
autofollowed_nicknames: [], autofollowed_nicknames: [],
max_pinned_statuses: 1, max_pinned_statuses: 1,
attachment_links: false, attachment_links: false,

View file

@ -0,0 +1,293 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.PleromaCtlTest do
use ExUnit.Case, async: false
@pleroma_ctl Path.expand("../../rel/files/bin/pleroma_ctl", __DIR__)
setup do
tmp_dir =
Path.join(System.tmp_dir!(), "pleroma_ctl_test_#{System.unique_integer([:positive])}")
release_root = Path.join(tmp_dir, "release")
bin_dir = Path.join(release_root, "bin")
File.mkdir_p!(bin_dir)
File.cp!(@pleroma_ctl, Path.join(bin_dir, "pleroma_ctl"))
File.chmod!(Path.join(bin_dir, "pleroma_ctl"), 0o755)
on_exit(fn -> File.rm_rf!(tmp_dir) end)
{:ok, tmp_dir: tmp_dir, release_root: release_root, bin_dir: bin_dir}
end
test "update downloads branch-scoped latest OTP package", %{
tmp_dir: tmp_dir,
bin_dir: bin_dir,
release_root: release_root
} do
stubs_dir = Path.join(tmp_dir, "stubs")
curl_args_path = Path.join(tmp_dir, "curl.args")
File.mkdir_p!(stubs_dir)
write_update_stubs(stubs_dir, curl_args_path, "unused", "glibc")
update_tmp_dir = Path.join(tmp_dir, "update_tmp")
File.mkdir_p!(update_tmp_dir)
{output, status} =
System.cmd(
Path.join(bin_dir, "pleroma_ctl"),
[
"update",
"--branch",
"develop",
"--flavour",
"amd64",
"--tmp-dir",
update_tmp_dir,
"--no-rm"
],
env: [{"PATH", stubs_dir <> ":" <> System.get_env("PATH", "")}],
stderr_to_stdout: true
)
assert status == 0, output
assert File.exists?(Path.join(release_root, "bin/marker"))
assert ["-fL", url, "-o", artifact_path] =
curl_args_path
|> File.read!()
|> String.split("\n", trim: true)
assert url ==
"https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-develop-amd64/latest/pleroma.zip"
assert artifact_path == Path.join(update_tmp_dir, "pleroma.zip")
end
test "update detects stable branch and local flavour", %{
tmp_dir: tmp_dir,
bin_dir: bin_dir,
release_root: release_root
} do
stubs_dir = Path.join(tmp_dir, "stubs")
curl_args_path = Path.join(tmp_dir, "curl.args")
File.mkdir_p!(stubs_dir)
write_update_stubs(stubs_dir, curl_args_path, "x86_64", "glibc")
write_start_erl_data(release_root, "2.10.0")
{output, status} =
System.cmd(
Path.join(bin_dir, "pleroma_ctl"),
["update", "--tmp-dir", create_update_tmp_dir(tmp_dir), "--no-rm"],
env: [{"PATH", stubs_dir <> ":" <> System.get_env("PATH", "")}],
stderr_to_stdout: true
)
assert status == 0, output
assert curl_url(curl_args_path) ==
"https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-stable-amd64/latest/pleroma.zip"
end
test "update detects develop branch and musl arm flavour", %{
tmp_dir: tmp_dir,
bin_dir: bin_dir,
release_root: release_root
} do
stubs_dir = Path.join(tmp_dir, "stubs")
curl_args_path = Path.join(tmp_dir, "curl.args")
File.mkdir_p!(stubs_dir)
write_update_stubs(stubs_dir, curl_args_path, "armv7l", "musl")
write_start_erl_data(release_root, "2.10.0.develop")
{output, status} =
System.cmd(
Path.join(bin_dir, "pleroma_ctl"),
["update", "--tmp-dir", create_update_tmp_dir(tmp_dir), "--no-rm"],
env: [{"PATH", stubs_dir <> ":" <> System.get_env("PATH", "")}],
stderr_to_stdout: true
)
assert status == 0, output
assert curl_url(curl_args_path) ==
"https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-develop-arm-musl/latest/pleroma.zip"
end
test "update with zip URL bypasses branch and flavour detection", %{
tmp_dir: tmp_dir,
bin_dir: bin_dir,
release_root: release_root
} do
stubs_dir = Path.join(tmp_dir, "stubs")
curl_args_path = Path.join(tmp_dir, "curl.args")
File.mkdir_p!(stubs_dir)
write_update_stubs(stubs_dir, curl_args_path, "unsupported-arch", "unsupported-libc")
write_start_erl_data(release_root, "2.10.0.custombranch")
custom_url = "https://example.test/custom.zip"
{output, status} =
System.cmd(
Path.join(bin_dir, "pleroma_ctl"),
[
"update",
"--zip-url",
custom_url,
"--tmp-dir",
create_update_tmp_dir(tmp_dir),
"--no-rm"
],
env: [{"PATH", stubs_dir <> ":" <> System.get_env("PATH", "")}],
stderr_to_stdout: true
)
assert status == 0, output
assert curl_url(curl_args_path) == custom_url
end
test "passes arguments with spaces and Elixir string metacharacters", %{
tmp_dir: tmp_dir,
bin_dir: bin_dir
} do
capture_path = Path.join(tmp_dir, "captured_args")
eval_path = Path.join(tmp_dir, "pleroma_ctl_eval.exs")
write_executable(Path.join(bin_dir, "pleroma"), """
#!/bin/sh
{
printf '%s\n' 'defmodule Pleroma.ReleaseTasks do'
printf '%s\n' ' def run(args), do: File.write!(System.fetch_env!("PLEROMA_CTL_CAPTURE"), :erlang.term_to_binary(args))'
printf '%s\n' 'end'
printf '%s\n' "$2"
} > "$PLEROMA_CTL_EVAL_FILE"
exec elixir "$PLEROMA_CTL_EVAL_FILE"
""")
{output, status} =
System.cmd(
Path.join(bin_dir, "pleroma_ctl"),
[
"user",
"",
"has space",
~s(has "quote"),
~s(has \\ backslash),
~S(#{:not_interpolated})
],
env: [
{"PLEROMA_CTL_CAPTURE", capture_path},
{"PLEROMA_CTL_EVAL_FILE", eval_path}
],
stderr_to_stdout: true
)
assert status == 0, output
assert capture_path
|> File.read!()
|> :erlang.binary_to_term() == [
"user",
"",
"has space",
~s(has "quote"),
~s(has \\ backslash),
~S(#{:not_interpolated})
]
end
defp write_executable(path, contents) do
File.write!(path, contents)
File.chmod!(path, 0o755)
end
defp write_start_erl_data(release_root, version) do
releases_dir = Path.join(release_root, "releases")
File.mkdir_p!(releases_dir)
File.write!(Path.join(releases_dir, "start_erl.data"), "erts-15.0 #{version}\n")
end
defp create_update_tmp_dir(tmp_dir) do
update_tmp_dir = Path.join(tmp_dir, "update_tmp")
File.mkdir_p!(update_tmp_dir)
update_tmp_dir
end
defp write_update_stubs(stubs_dir, curl_args_path, arch, libc) do
write_executable(Path.join(stubs_dir, "curl"), """
#!/bin/sh
printf '%s\n' "$@" > "#{curl_args_path}"
while [ $# -gt 0 ]; do
case "$1" in
-o)
artifact="$2"
shift 2
;;
*)
shift
;;
esac
done
: > "$artifact"
""")
write_executable(Path.join(stubs_dir, "unzip"), """
#!/bin/sh
while [ $# -gt 0 ]; do
case "$1" in
-d)
dest="$2"
shift 2
;;
*)
shift
;;
esac
done
mkdir -p "$dest/release/bin"
printf 'marker' > "$dest/release/bin/marker"
""")
write_executable(Path.join(stubs_dir, "uname"), """
#!/bin/sh
printf '%s\n' '#{arch}'
""")
write_executable(Path.join(stubs_dir, "getconf"), getconf_stub(libc))
write_executable(Path.join(stubs_dir, "ldd"), """
#!/bin/sh
printf '%s\n' 'musl libc (mock)'
""")
end
defp getconf_stub("glibc") do
"""
#!/bin/sh
printf '%s\n' 'glibc 2.40'
"""
end
defp getconf_stub(_libc) do
"""
#!/bin/sh
exit 1
"""
end
defp curl_url(curl_args_path) do
["-fL", url, "-o", _artifact_path] =
curl_args_path
|> File.read!()
|> String.split("\n", trim: true)
url
end
end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.SignatureTest do
import Mock import Mock
alias Pleroma.Signature alias Pleroma.Signature
alias Pleroma.StubbedHTTPSignaturesMock, as: HTTPSignaturesMock
setup do setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end) mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -103,6 +104,18 @@ defmodule Pleroma.SignatureTest do
end end
end end
describe "validate_signature/1" do
test "treats HTTP signature errors as failed validation" do
conn = %Plug.Conn{method: "GET", request_path: "/inbox", req_headers: []}
Mox.expect(HTTPSignaturesMock, :validate_conn, fn _conn ->
{:error, :request_target_header}
end)
assert Signature.validate_signature(conn) == false
end
end
describe "key_id_to_actor_id/1" do describe "key_id_to_actor_id/1" do
test "it properly deduces the actor id for misskey" do test "it properly deduces the actor id for misskey" do
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") == assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==

View file

@ -0,0 +1,195 @@
# Pleroma: A lightweight social networking server
# Copyright © Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.SearchTest do
use Pleroma.DataCase, async: false
import Pleroma.Factory
alias Pleroma.Instances
alias Pleroma.Repo
alias Pleroma.User
describe "search/2 mention suggestions" do
test "prioritizes followed/follower users before others" do
user = insert(:user)
related =
insert(:user,
local: false,
nickname: "hj@real.example",
ap_id: "https://real.example/users/hj",
last_status_at: ~N[2020-01-01 00:00:00]
)
other = insert(:user, nickname: "hj", last_status_at: ~N[2020-01-02 00:00:00])
{:ok, _related, _user} = User.follow(related, user)
results = User.search("hj", for_user: user) |> Enum.map(& &1.id)
assert results == [related.id, other.id]
end
test "orders followed/follower users by most recent activity" do
user = insert(:user)
older =
insert(:user,
local: false,
nickname: "ali@remote.example",
ap_id: "https://remote.example/users/ali",
last_status_at: ~N[2020-01-01 00:00:00]
)
newer =
insert(:user,
local: false,
nickname: "alia@remote.example",
ap_id: "https://remote.example/users/alia",
last_status_at: ~N[2020-01-02 00:00:00]
)
{:ok, _user, _older} = User.follow(user, older)
{:ok, _user, _newer} = User.follow(user, newer)
assert [newer.id, older.id] ==
User.search("ali", for_user: user)
|> Enum.map(& &1.id)
end
test "groups followed/follower users first and sorts them by recency" do
user = insert(:user)
following_newest =
insert(:user,
local: false,
nickname: "mentiontesta@related.example",
ap_id: "https://related.example/users/mentiontesta",
last_status_at: ~N[2020-01-03 00:00:00]
)
follower_middle =
insert(:user,
local: false,
nickname: "mentiontestb@related.example",
ap_id: "https://related.example/users/mentiontestb",
last_status_at: ~N[2020-01-02 00:00:00]
)
mutual_oldest =
insert(:user,
local: false,
nickname: "mentiontestc@related.example",
ap_id: "https://related.example/users/mentiontestc",
last_status_at: ~N[2020-01-01 00:00:00]
)
unrelated_newer =
insert(:user,
local: false,
nickname: "mentiontestd@unrelated.example",
ap_id: "https://unrelated.example/users/mentiontestd",
last_status_at: ~N[2020-01-04 00:00:00]
)
{:ok, _user, _following_newest} = User.follow(user, following_newest)
{:ok, _follower_middle, _user} = User.follow(follower_middle, user)
{:ok, _user, _mutual_oldest} = User.follow(user, mutual_oldest)
{:ok, _mutual_oldest, _user} = User.follow(mutual_oldest, user)
results = User.search("mentiontest", for_user: user)
assert Enum.map(results, & &1.id) ==
[following_newest.id, follower_middle.id, mutual_oldest.id, unrelated_newer.id]
end
test "uses last_active_at when last_status_at is missing" do
user = insert(:user)
older =
insert(:user,
local: false,
nickname: "activefallbacka@remote.example",
ap_id: "https://remote.example/users/activefallbacka",
last_status_at: nil,
last_active_at: ~N[2020-01-01 00:00:00]
)
newer =
insert(:user,
local: false,
nickname: "activefallbackb@remote.example",
ap_id: "https://remote.example/users/activefallbackb",
last_status_at: nil,
last_active_at: ~N[2020-01-02 00:00:00]
)
{:ok, _user, _older} = User.follow(user, older)
{:ok, _user, _newer} = User.follow(user, newer)
assert [newer.id, older.id] ==
User.search("activefallback", for_user: user)
|> Enum.map(& &1.id)
end
test "does not return deactivated users even if related" do
user = insert(:user)
active =
insert(:user,
local: false,
nickname: "deactivatedtesta@remote.example",
ap_id: "https://remote.example/users/deactivatedtesta",
last_status_at: ~N[2020-01-02 00:00:00]
)
deactivated =
insert(:user,
local: false,
nickname: "deactivatedtestb@remote.example",
ap_id: "https://remote.example/users/deactivatedtestb",
last_status_at: ~N[2020-01-03 00:00:00]
)
{:ok, _user, _active} = User.follow(user, active)
{:ok, _user, _deactivated} = User.follow(user, deactivated)
Repo.update!(Ecto.Changeset.change(deactivated, is_active: false))
results = User.search("deactivatedtest", for_user: user) |> Enum.map(& &1.id)
assert results == [active.id]
end
test "does not return users from unreachable instances" do
user = insert(:user)
{:ok, _instance} = Instances.set_unreachable("dead.example")
dead =
insert(:user,
local: false,
nickname: "ali@dead.example",
ap_id: "https://dead.example/users/ali",
last_status_at: ~N[2020-01-02 00:00:00]
)
alive =
insert(:user,
local: false,
nickname: "ali@alive.example",
ap_id: "https://alive.example/users/ali",
last_status_at: ~N[2020-01-02 00:00:00]
)
{:ok, _user, _alive} = User.follow(user, alive)
{:ok, _user, _dead} = User.follow(user, dead)
results = User.search("ali", for_user: user) |> Enum.map(& &1.id)
assert results == [alive.id]
end
end
end

View file

@ -876,17 +876,17 @@ defmodule Pleroma.UserTest do
describe "get_or_fetch/1 remote users with tld, while BE is running on a subdomain" do describe "get_or_fetch/1 remote users with tld, while BE is running on a subdomain" do
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true) setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
test "for mastodon" do test "fetches a mastodon split-domain nickname" do
ap_id = "a@mastodon.example" nickname = "a@mastodon.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id) {:ok, fetched_user} = User.get_or_fetch(nickname)
assert fetched_user.ap_id == "https://sub.mastodon.example/users/a" assert fetched_user.ap_id == "https://sub.mastodon.example/users/a"
assert fetched_user.nickname == "a@mastodon.example" assert fetched_user.nickname == "a@mastodon.example"
end end
test "for pleroma" do test "fetches a pleroma split-domain nickname" do
ap_id = "a@pleroma.example" nickname = "a@pleroma.example"
{:ok, fetched_user} = User.get_or_fetch(ap_id) {:ok, fetched_user} = User.get_or_fetch(nickname)
assert fetched_user.ap_id == "https://sub.pleroma.example/users/a" assert fetched_user.ap_id == "https://sub.pleroma.example/users/a"
assert fetched_user.nickname == "a@pleroma.example" assert fetched_user.nickname == "a@pleroma.example"
@ -936,6 +936,89 @@ defmodule Pleroma.UserTest do
assert fetched_user == "not found nonexistent" assert fetched_user == "not found nonexistent"
end end
test "does not rename an existing remote actor from rogue WebFinger data" do
clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
actor_id = "https://legit-actor.example/users/alice"
Tesla.Mock.mock(fn
%{url: "https://evil-webfinger.example/.well-known/host-meta"} ->
{:ok, %Tesla.Env{status: 404}}
%{
url:
"https://evil-webfinger.example/.well-known/webfinger?resource=acct:claimed@evil-webfinger.example"
} ->
Tesla.Mock.json(%{
"subject" => "acct:claimed@evil-webfinger.example",
"links" => [
%{
"rel" => "self",
"type" => "application/activity+json",
"href" => actor_id
}
]
})
%{url: ^actor_id} ->
{:ok,
%Tesla.Env{
status: 200,
headers: [{"content-type", "application/activity+json"}],
body:
Jason.encode!(%{
"id" => actor_id,
"type" => "Person",
"preferredUsername" => "alice",
"name" => "Alice",
"summary" => "",
"inbox" => "https://legit-actor.example/users/alice/inbox",
"outbox" => "https://legit-actor.example/users/alice/outbox",
"followers" => "https://legit-actor.example/users/alice/followers",
"following" => "https://legit-actor.example/users/alice/following"
})
}}
%{url: "https://legit-actor.example/.well-known/host-meta"} ->
{:ok, %Tesla.Env{status: 404}}
%{
url:
"https://legit-actor.example/.well-known/webfinger?resource=acct:alice@legit-actor.example"
} ->
Tesla.Mock.json(%{
"subject" => "acct:alice@legit-actor.example",
"links" => [
%{
"rel" => "self",
"type" => "application/activity+json",
"href" => actor_id
}
]
})
end)
assert {:error, {:webfinger_actor_mismatch, "claimed@evil-webfinger.example", ^actor_id}} =
ActivityPub.make_user_from_nickname("claimed@evil-webfinger.example")
refute User.get_by_ap_id(actor_id)
refute User.get_by_nickname("claimed@evil-webfinger.example")
orig_user =
insert(:user,
local: false,
nickname: "alice@legit-actor.example",
ap_id: actor_id
)
assert {:error, {:webfinger_actor_mismatch, "claimed@evil-webfinger.example", ^actor_id}} =
ActivityPub.make_user_from_nickname("claimed@evil-webfinger.example")
assert {:error, _} = User.get_or_fetch_by_nickname("claimed@evil-webfinger.example")
assert User.get_by_id(orig_user.id).nickname == "alice@legit-actor.example"
refute User.get_by_nickname("claimed@evil-webfinger.example")
end
test "updates an existing user, if stale" do test "updates an existing user, if stale" do
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800) a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Workers.ReceiverWorker alias Pleroma.Workers.ReceiverWorker
alias Pleroma.Workers.SignatureRetryWorker
import Pleroma.Factory import Pleroma.Factory
@ -36,6 +37,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
setup do: clear_config([:instance, :federating], true) setup do: clear_config([:instance, :federating], true)
defp assign_valid_signature_for_actor(conn, %User{ap_id: actor_id}) do
assign_valid_signature_for_actor(conn, actor_id)
end
defp assign_valid_signature_for_actor(conn, actor) do
actor_id = Utils.get_ap_id(actor)
conn
|> assign(:valid_signature, true)
|> put_req_header("signature", "keyId=\"#{actor_id}#main-key\"")
end
defp expect_signature_retry_from(%User{} = signer) do
signer_json = UserView.render("user.json", %{user: signer}) |> Map.delete("featured")
Tesla.Mock.mock(fn
%{url: url} when url == signer.ap_id ->
%Tesla.Env{
status: 200,
body: Jason.encode!(signer_json),
headers: HttpRequestMock.activitypub_object_headers()
}
env ->
apply(HttpRequestMock, :request, [env])
end)
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end)
end
describe "/relay" do describe "/relay" do
setup do: clear_config([:instance, :allow_relay]) setup do: clear_config([:instance, :allow_relay])
@ -688,7 +719,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
@ -716,7 +747,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
@ -726,6 +757,199 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"]) assert Activity.get_by_ap_id(data["id"])
end end
test "does not create a forged post after failed signature retry", %{conn: conn} 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/inbox-forged-note"
data = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/inbox-forged-create",
"context" => "https://two.com/contexts/inbox-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/inbox-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
expect_signature_retry_from(alice)
conn =
conn
|> assign(:valid_signature, false)
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"])
refute Object.get_by_ap_id(object_id)
end
test "does not create a forged like after failed signature retry", %{conn: conn} 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")
note = insert(:note)
data = %{
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/inbox-forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
expect_signature_retry_from(alice)
conn =
conn
|> assign(:valid_signature, false)
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"])
end
test "does not delete an object after failed signature retry", %{conn: conn} 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")
note = insert(:note)
object_id = note.data["id"]
data = %{
"type" => "Delete",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/inbox-forged-delete",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => object_id
}
expect_signature_retry_from(alice)
conn =
conn
|> assign(:valid_signature, false)
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"])
assert %Object{data: %{"type" => "Note"}} = Object.get_by_ap_id(object_id)
end
test "does not create a forged post signed by a different actor", %{conn: conn} 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/inbox-signed-forged-note"
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/inbox-signed-forged-create",
"context" => "https://two.com/contexts/inbox-signed-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/inbox-signed-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
expect_signature_retry_from(alice)
conn =
conn
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("date", "Thu, 25 Jul 2024 13:33:31 GMT")
|> put_req_header("digest", "SHA-256=fake-digest")
|> put_req_header(
"signature",
"keyId=\"#{alice.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
)
|> post("/inbox", data)
assert conn.assigns.valid_signature == false
assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"])
refute Object.get_by_ap_id(object_id)
end
test "does not create a forged like signed by a different actor", %{conn: conn} 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")
note = insert(:note)
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/inbox-signed-forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
expect_signature_retry_from(alice)
conn =
conn
|> put_req_header("content-type", "application/activity+json")
|> put_req_header("date", "Thu, 25 Jul 2024 13:33:31 GMT")
|> put_req_header("digest", "SHA-256=fake-digest")
|> put_req_header(
"signature",
"keyId=\"#{alice.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
)
|> post("/inbox", data)
assert conn.assigns.valid_signature == false
assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"])
end
test "accept follow activity", %{conn: conn} do test "accept follow activity", %{conn: conn} do
clear_config([:instance, :federating], true) clear_config([:instance, :federating], true)
relay = Relay.get_actor() relay = Relay.get_actor()
@ -742,7 +966,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == assert "ok" ==
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(followed_relay)
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", accept) |> post("/inbox", accept)
|> json_response(200) |> json_response(200)
@ -822,16 +1046,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
test "Unknown activity types are discarded", %{conn: conn} do test "Unknown activity types are discarded", %{conn: conn} do
unknown_types = ["Poke", "Read", "Dazzle"] unknown_types = ["Poke", "Read", "Dazzle"]
actor =
insert(:user, local: false, ap_id: "https://unknown.mastodon.instance/users/somebody")
Enum.each(unknown_types, fn bad_type -> Enum.each(unknown_types, fn bad_type ->
params = params =
%{ %{
"type" => bad_type, "type" => bad_type,
"actor" => "https://unknown.mastodon.instance/users/somebody" "actor" => actor.ap_id
} }
|> Jason.encode!() |> Jason.encode!()
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(actor)
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", params) |> post("/inbox", params)
|> json_response(400) |> json_response(400)
@ -900,7 +1127,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == assert "ok" ==
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
|> json_response(200) |> json_response(200)
@ -921,7 +1148,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == assert "ok" ==
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
|> json_response(200) |> json_response(200)
@ -990,7 +1217,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == assert "ok" ==
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
|> json_response(200) |> json_response(200)
@ -1009,7 +1236,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == assert "ok" ==
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data) |> post("/inbox", data)
|> json_response(200) |> json_response(200)
@ -1040,7 +1267,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1061,7 +1288,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1082,7 +1309,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1106,7 +1333,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1133,7 +1360,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1163,7 +1390,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data) |> post("/users/#{recipient.nickname}/inbox", data)
@ -1228,7 +1455,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
} }
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{recipient.nickname}/inbox", data) |> post("/users/#{recipient.nickname}/inbox", data)
|> json_response(200) |> json_response(200)
@ -1318,7 +1545,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
} }
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data) |> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200) |> json_response(200)
@ -1372,7 +1599,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
} }
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{reported_user.nickname}/inbox", data) |> post("/users/#{reported_user.nickname}/inbox", data)
|> json_response(200) |> json_response(200)
@ -1405,7 +1632,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1428,7 +1655,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -1451,7 +1678,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
conn = conn =
conn conn
|> assign(:valid_signature, true) |> assign_valid_signature_for_actor(data["actor"])
|> put_req_header("content-type", "application/activity+json") |> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data) |> post("/users/#{user.nickname}/inbox", data)
@ -2571,6 +2798,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
setup do: clear_config([:media_proxy]) setup do: clear_config([:media_proxy])
setup do: clear_config([Pleroma.Upload]) setup do: clear_config([Pleroma.Upload])
# majic's libmagic port is unavailable on local Darwin runs; Linux CI still runs this test.
@tag :skip_darwin
test "POST /api/ap/upload_media", %{conn: conn} do test "POST /api/ap/upload_media", %{conn: conn} do
user = insert(:user) user = insert(:user)

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
defp regexes_match!([],[]), do: true defp regexes_match!([], []), do: true
defp regexes_match!([authority | authority_rest], [checked | checked_rest]) do defp regexes_match!([authority | authority_rest], [checked | checked_rest]) do
authority.source == checked.source and regexes_match!(authority_rest, checked_rest) authority.source == checked.source and regexes_match!(authority_rest, checked_rest)

View file

@ -149,6 +149,171 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
end end
test "a Misskey MFM note is rendered from source content" do
user = insert(:user, ap_id: "https://misskey.example/users/alice")
note = %{
"id" => "https://misskey.example/notes/1",
"type" => "Note",
"actor" => user.ap_id,
"attributedTo" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" => "original content",
"context" => Utils.generate_context_id(),
"source" => %{
"content" => "$[spin.speed=1s mfm goes here] <script>alert('xss')</script>",
"mediaType" => "text/x.misskeymarkdown"
}
}
%{valid?: true, changes: %{content: content, source: source}} =
ArticleNotePageValidator.cast_and_validate(note)
assert source["mediaType"] == "text/x.misskeymarkdown"
assert content =~ ~s(class="mfm-spin")
assert content =~ ~s(data-mfm-speed="1s")
assert content =~ "mfm goes here"
refute content =~ "original content"
refute content =~ "<script"
end
test "a Misskey MFM note resolves only cached AP mention tags" do
remote_user = insert(:user, ap_id: "https://misskey.example/users/carol")
local_user = insert(:user, nickname: "local_user")
note = %{
"id" => "https://misskey.example/notes/3",
"type" => "Note",
"actor" => remote_user.ap_id,
"attributedTo" => remote_user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" => "original content",
"context" => Utils.generate_context_id(),
"tag" => [
%{
"type" => "Mention",
"name" => "@local_user",
"href" => local_user.ap_id
},
%{
"type" => "Mention",
"name" => "@uncached",
"href" => "https://misskey.example/users/uncached"
}
],
"source" => %{
"content" => "@local_user @uncached $[spin hello]",
"mediaType" => "text/x.misskeymarkdown"
}
}
%{valid?: true, changes: %{content: content}} =
ArticleNotePageValidator.cast_and_validate(note)
assert content =~ local_user.ap_id
assert content =~ "@uncached"
end
test "a Misskey MFM note drops oversized source content instead of parsing it" do
user = insert(:user, ap_id: "https://misskey.example/users/oversized")
note = %{
"id" => "https://misskey.example/notes/4",
"type" => "Note",
"actor" => user.ap_id,
"attributedTo" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" => "<span class=\"mfm-spin\">safe fallback</span>",
"context" => Utils.generate_context_id(),
"source" => %{
"content" => String.duplicate("x", 5_001),
"mediaType" => "text/x.misskeymarkdown"
}
}
%{valid?: true, changes: %{content: content, source: source}} =
ArticleNotePageValidator.cast_and_validate(note)
assert content == "<span class=\"mfm-spin\">safe fallback</span>"
refute Map.has_key?(source, "content")
end
test "a note drops oversized non-MFM source content" do
user = insert(:user, ap_id: "https://example.com/users/source")
note = %{
"id" => "https://example.com/notes/1",
"type" => "Note",
"actor" => user.ap_id,
"attributedTo" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" => "regular content",
"context" => Utils.generate_context_id(),
"source" => %{
"content" => String.duplicate("x", 5_001),
"mediaType" => "text/markdown"
}
}
%{valid?: true, changes: %{source: source}} = ArticleNotePageValidator.cast_and_validate(note)
assert source == %{"mediaType" => "text/markdown"}
end
test "a Misskey MFM note with legacy _misskey_content is rendered" do
user = insert(:user, ap_id: "https://misskey.example/users/legacy")
note = %{
"id" => "https://misskey.example/notes/5",
"type" => "Note",
"actor" => user.ap_id,
"attributedTo" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" => "original content",
"context" => Utils.generate_context_id(),
"_misskey_content" => "$[spin legacy]"
}
%{valid?: true, changes: %{content: content, source: source}} =
ArticleNotePageValidator.cast_and_validate(note)
assert source == %{"content" => "$[spin legacy]", "mediaType" => "text/x.misskeymarkdown"}
assert content =~ ~s(class="mfm-spin")
assert content =~ "legacy"
end
test "a Misskey MFM note with htmlMfm is scrubbed but not rendered from source content" do
user = insert(:user, ap_id: "https://misskey.example/users/bob")
note = %{
"id" => "https://misskey.example/notes/2",
"type" => "Note",
"actor" => user.ap_id,
"attributedTo" => user.ap_id,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"content" =>
"<span class=\"mfm-spin\">already rendered</span><script>alert('xss')</script>",
"htmlMfm" => true,
"context" => Utils.generate_context_id(),
"source" => %{
"content" => String.duplicate("x", 5_001),
"mediaType" => "text/x.misskeymarkdown"
}
}
%{valid?: true, changes: %{content: content, htmlMfm: true, source: source}} =
ArticleNotePageValidator.cast_and_validate(note)
assert content == "<span class=\"mfm-spin\">already rendered</span>alert(&#39;xss&#39;)"
refute Map.has_key?(source, "content")
end
test "a Note with validated likes collection validates" do test "a Note with validated likes collection validates" do
insert(:user, ap_id: "https://pol.social/users/mkljczk") insert(:user, ap_id: "https://pol.social/users/mkljczk")

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateHandlingTest do
assert {:ok, _update, []} = ObjectValidator.validate(valid_update, []) assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
end end
test "returns an error if the object can't be updated by the actor", %{ test "returns an error if the object can't be updated by the actor (different domain)", %{
valid_update: valid_update valid_update: valid_update
} do } do
other_user = insert(:user, local: false) other_user = insert(:user, local: false)
@ -41,27 +41,72 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateHandlingTest do
assert {:error, _cng} = ObjectValidator.validate(update, []) assert {:error, _cng} = ObjectValidator.validate(update, [])
end end
test "validates as long as the object is same-origin with the actor", %{ test "returns an error if the object can't be updated by the actor (same domain)", %{
user: user,
valid_update: valid_update valid_update: valid_update
} do } do
other_user = insert(:user) user_ap_id = user.ap_id
user_domain = URI.parse(user_ap_id).host
other_user = insert(:user, local: false, domain: user_domain)
update = update =
valid_update valid_update
|> Map.put("actor", other_user.ap_id) |> Map.put("actor", other_user.ap_id)
assert {:ok, _update, []} = ObjectValidator.validate(update, []) assert {:error, _cng} = ObjectValidator.validate(update, [])
end end
test "validates if the object is not of an Actor type" do test "validates if the object is not of an Actor type", %{user: user} do
note = insert(:note) note = insert(:note, user: user)
updated_note = note.data |> Map.put("content", "edited content") updated_note = note.data |> Map.put("content", "edited content")
other_user = insert(:user)
{:ok, update, _} = Builder.update(other_user, updated_note) {:ok, update, _} = Builder.update(user, updated_note)
assert {:ok, _update, _} = ObjectValidator.validate(update, []) assert {:ok, _update, _} = ObjectValidator.validate(update, [])
end end
test "returns an error if the remote update target is unknown" do
remote_user = insert(:user, local: false, ap_id: "https://example.com/users/alice")
update = %{
"type" => "Update",
"actor" => remote_user.ap_id,
"id" => "https://example.com/activities/update-unknown-object",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://example.com/objects/unknown",
"actor" => remote_user.ap_id,
"content" => "edited content",
"published" => "2024-07-25T13:33:31Z",
"updated" => "2024-07-25T13:34:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert {:error, %Ecto.Changeset{} = cng} = ObjectValidator.validate(update, local: false)
refute cng.valid?
assert Keyword.has_key?(cng.errors, :object)
end
test "returns an error if the remote update target IRI is unknown" do
remote_user = insert(:user, local: false, ap_id: "https://example.com/users/alice")
update = %{
"type" => "Update",
"actor" => remote_user.ap_id,
"id" => "https://example.com/activities/update-unknown-object-iri",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => "https://example.com/objects/unknown-iri"
}
assert {:error, %Ecto.Changeset{} = cng} = ObjectValidator.validate(update, local: false)
refute cng.valid?
assert Keyword.has_key?(cng.errors, :object)
end
end end
describe "update note" do describe "update note" do

Some files were not shown because too many files have changed in this diff Show more